在 Python 中进行自动属性分配的最佳方法是什么,这是一个好主意吗?
问题描述
而不是每次定义类时都编写这样的代码:
Instead of writing code like this every time I define a class:
class Foo(object):
def __init__(self, a, b, c, d, e, f, g):
self.a = a
self.b = b
self.c = c
self.d = d
self.e = e
self.f = f
self.g = g
我可以使用这个自动属性分配方法.
class Foo(object):
@autoassign
def __init__(self, a, b, c, d, e, f, g):
pass
两个问题:
- 是否存在与此快捷方式相关的缺点或陷阱?
- 有没有更好的方法来实现类似的便利?
解决方案
关于自动分配代码的一些问题让我感到困惑(主要是风格,但还有一个更严重的问题):
There are some things about the autoassign code that bug me (mostly stylistic, but one more serious problem):
autoassign
不分配'args' 属性:
autoassign
does not assign an 'args' attribute:
class Foo(object):
@autoassign
def __init__(self,a,b,c=False,*args):
pass
a=Foo('IBM','/tmp',True, 100, 101)
print(a.args)
# AttributeError: 'Foo' object has no attribute 'args'
autoassign
就像一个装饰器.但是 autoassign(*argnames)
调用返回装饰器的函数.为了实现这个魔力,autoassign
需要先测试它的类型争论.如果可以选择,我喜欢功能而不是测试其参数的类型.
autoassign
acts like a decorator.
But autoassign(*argnames)
calls a
function which returns a decorator.
To achieve this magic, autoassign
needs to test the type of its first
argument. If given a choice, I
prefer functions not test
the type of its arguments.
好像有很多用于设置的代码量筛子
,lambdas中的lambdas,ifilters,还有很多条件.
There seems to be a considerable
amount of code devoted to setting up
sieve
, lambdas within lambdas,
ifilters, and lots of conditions.
if kwargs:
exclude, f = set(kwargs['exclude']), None
sieve = lambda l:itertools.ifilter(lambda nv: nv[0] not in exclude, l)
elif len(names) == 1 and inspect.isfunction(names[0]):
f = names[0]
sieve = lambda l:l
else:
names, f = set(names), None
sieve = lambda l: itertools.ifilter(lambda nv: nv[0] in names, l)
我认为可能有更简单的方法.(看下面).
I think there might be a simpler way. (See below).
for _ initertools.starmap(assigned.setdefault,默认值):通过
.我不认为map
或 starmap
旨在调用函数,其唯一目的是它们的副作用.本来可以用平凡写得更清楚:
for _ in
itertools.starmap(assigned.setdefault,
defaults): pass
. I don't think
map
or starmap
was meant to call
functions, whose only purpose is their
side effects. It could have been
written more clearly with the mundane:
for key,value in defaults.iteritems():
assigned.setdefault(key,value)
这是一个替代的更简单的实现,它具有与自动分配相同的功能(例如,可以包含和排除),并且解决了上述问题:
Here is an alternative simpler implementation which has the same functionality as autoassign (e.g. can do includes and excludes), and which addresses the above points:
import inspect
import functools
def autoargs(*include, **kwargs):
def _autoargs(func):
attrs, varargs, varkw, defaults = inspect.getargspec(func)
def sieve(attr):
if kwargs and attr in kwargs['exclude']:
return False
if not include or attr in include:
return True
else:
return False
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
# handle default values
if defaults:
for attr, val in zip(reversed(attrs), reversed(defaults)):
if sieve(attr):
setattr(self, attr, val)
# handle positional arguments
positional_attrs = attrs[1:]
for attr, val in zip(positional_attrs, args):
if sieve(attr):
setattr(self, attr, val)
# handle varargs
if varargs:
remaining_args = args[len(positional_attrs):]
if sieve(varargs):
setattr(self, varargs, remaining_args)
# handle varkw
if kwargs:
for attr, val in kwargs.items():
if sieve(attr):
setattr(self, attr, val)
return func(self, *args, **kwargs)
return wrapper
return _autoargs
这是我用来检查其行为的单元测试:
And here is the unit test I used to check its behavior:
import sys
import unittest
import utils_method as um
class Test(unittest.TestCase):
def test_autoargs(self):
class A(object):
@um.autoargs()
def __init__(self,foo,path,debug=False):
pass
a=A('rhubarb','pie',debug=True)
self.assertTrue(a.foo=='rhubarb')
self.assertTrue(a.path=='pie')
self.assertTrue(a.debug==True)
class B(object):
@um.autoargs()
def __init__(self,foo,path,debug=False,*args):
pass
a=B('rhubarb','pie',True, 100, 101)
self.assertTrue(a.foo=='rhubarb')
self.assertTrue(a.path=='pie')
self.assertTrue(a.debug==True)
self.assertTrue(a.args==(100,101))
class C(object):
@um.autoargs()
def __init__(self,foo,path,debug=False,*args,**kw):
pass
a=C('rhubarb','pie',True, 100, 101,verbose=True)
self.assertTrue(a.foo=='rhubarb')
self.assertTrue(a.path=='pie')
self.assertTrue(a.debug==True)
self.assertTrue(a.verbose==True)
self.assertTrue(a.args==(100,101))
def test_autoargs_names(self):
class C(object):
@um.autoargs('bar','baz','verbose')
def __init__(self,foo,bar,baz,verbose=False):
pass
a=C('rhubarb','pie',1)
self.assertTrue(a.bar=='pie')
self.assertTrue(a.baz==1)
self.assertTrue(a.verbose==False)
self.assertRaises(AttributeError,getattr,a,'foo')
def test_autoargs_exclude(self):
class C(object):
@um.autoargs(exclude=('bar','baz','verbose'))
def __init__(self,foo,bar,baz,verbose=False):
pass
a=C('rhubarb','pie',1)
self.assertTrue(a.foo=='rhubarb')
self.assertRaises(AttributeError,getattr,a,'bar')
def test_defaults_none(self):
class A(object):
@um.autoargs()
def __init__(self,foo,path,debug):
pass
a=A('rhubarb','pie',debug=True)
self.assertTrue(a.foo=='rhubarb')
self.assertTrue(a.path=='pie')
self.assertTrue(a.debug==True)
if __name__ == '__main__':
unittest.main(argv = sys.argv + ['--verbose'])
PS.使用 autoassign
或 autoargs
与 IPython 代码完成兼容.
PS. Using autoassign
or autoargs
is compatible with IPython code completion.
相关文章