What is the best way to do automatic attribute assignment in Python, and is it a good idea?
而不是每次定义类时都这样编写代码:
1 2 3 4 5 6 7 8 9 | 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 |
我可以使用这个方法来自动分配属性。
1 2 3 4 | class Foo(object): @autoassign def __init__(self, a, b, c, d, e, f, g): pass |
号
两个问题:
关于autoassign代码,有一些事情让我很烦(主要是风格上的,但还有一个更严重的问题):
1 2 3 4 5 6 7 | 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' |
号
似乎有一个相当大的用于设置的代码量以东十一〔四〕羊羔中的羊羔,i过滤器和许多条件。
1 2 3 4 5 6 7 8 9 | 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) |
我想可能有一个更简单的方法。(见以下)。
itertools.starmap(assigned.setdefault,
defaults): pass
1 2 | for key,value in defaults.iteritems(): assigned.setdefault(key,value) |
。
这里是一个替代的更简单的实现,它具有与autoassign相同的功能(例如,can do include和excludes),并解决了上述问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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 |
下面是我用来检查其行为的单元测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | 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.使用
一个缺点是:许多IDE解析
Is there a better way to achieve similar convenience?
号
我不知道这是否一定更好,但你可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Foo(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) >>> foo = Foo(a = 1, b = 'bar', c = [1, 2]) >>> foo.a 1 >>> foo.b 'bar' >>> foo.c [1, 2] >>> |
彼得·诺维格的Python:不常回答的问题。
从Python3.7+中,您可以使用一个数据类,它可以实现您想要的以及更多的功能。
它允许您为类定义字段,这些字段是自动分配的属性。
看起来是这样的:
1 2 3 4 5 6 | @dataclass class Foo: a: str b: int c: str ... |
。
注意这里需要类型提示,这就是我在示例中使用
在这个包裹里你现在可以找到
@autoargs 灵感来源于答案3653049@autoprops 将@autoargs 生成的字段转换为@property 字段,用于与验证库(如强制执行或PyContracts)结合使用。
请注意,这已经针对Python3.5进行了验证。+
这是Judy2K的一个简单实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | from inspect import signature def auto_args(f): sig = signature(f) # Get a signature object for the target: def replacement(self, *args, **kwargs): # Parse the provided arguments using the target's signature: bound_args = sig.bind(self, *args, **kwargs) # Save away the arguments on `self`: for k, v in bound_args.arguments.items(): if k != 'self': setattr(self, k, v) # Call the actual constructor for anything else: f(self, *args, **kwargs) return replacement class MyClass: @auto_args def __init__(self, a, b, c=None): pass m = MyClass('A', 'B', 'C') print(m.__dict__) # {'a': 'A', 'b': 'B', 'c': 'C'} |
类似于上述,尽管不同…以下内容非常简短,涉及
1 2 3 4 | def autoassign(lcls): for key in lcls.keys(): if key!="self": lcls["self"].__dict__[key]=lcls[key] |
使用方法如下:
1 2 3 | class Test(object): def __init__(self, a, b, *args, **kwargs): autoassign(locals()) |
。
如果有很多变量,可以传递一个配置dict或对象。
1 2 3 4 | class MyClass(object): def __init__(self, **kwargs): for key, value in kwargs.iteritems(): setattr(self, key, value) |
。
您不能使用*参数,但可以存储在一些实例列表中(如self.args,不知道)。