如何避免“self.x = x; self.y = y; __init__中的self.z = z“模式?
我看到类似的模式
def __init__(self, x, y, z): ... self.x = x self.y = y self.z = z ...
相当频繁,往往有更多的参数。 有没有避免这种乏味的重复性的好方法? 该类应该从namedtuple
inheritance吗?
装饰解决scheme,保持签名:
import decorator import inspect import sys @decorator.decorator def simple_init(func, self, *args, **kws): """ @simple_init def __init__(self,a,b,...,z) dosomething() behaves like def __init__(self,a,b,...,z) self.a = a self.b = b ... self.z = z dosomething() """ #init_argumentnames_without_self = ['a','b',...,'z'] if sys.version_info.major == 2: init_argumentnames_without_self = inspect.getargspec(func).args[1:] else: init_argumentnames_without_self = tuple(inspect.signature(func).parameters.keys())[1:] positional_values = args keyword_values_in_correct_order = tuple(kws[key] for key in init_argumentnames_without_self if key in kws) attribute_values = positional_values + keyword_values_in_correct_order for attribute_name,attribute_value in zip(init_argumentnames_without_self,attribute_values): setattr(self,attribute_name,attribute_value) # call the original __init__ func(self, *args, **kws) class Test(): @simple_init def __init__(self,a,b,c,d=4): print(self.a,self.b,self.c,self.d) #prints 1 3 2 4 t = Test(1,c=2,b=3) #keeps signature #prints ['self', 'a', 'b', 'c', 'd'] if sys.version_info.major == 2: print(inspect.getargspec(Test.__init__).args) else: print(inspect.signature(Test.__init__))
编辑
看来有几个人担心提出这个解决scheme,所以我会提供一个非常明确的免责声明。 你不应该使用这个解决scheme。 我只提供它作为信息,所以你知道这个语言是可以做到的。 答案的其余部分只是显示了语言function,并不赞同以这种方式使用它们。
原来的答案
显式复制参数到属性中没有任何问题。 如果在ctor中有太多的参数,它有时被认为是一种代码味道,也许你应该将这些参数分组到一个较less的对象中。 其他时候,这是必要的,没有错。 无论如何,明确的做法是要走的路。
但是,既然你在问如何做(而不是这是否应该完成),那么一个解决scheme是这样的:
class A: def __init__(self, **kwargs): for key in kwargs: setattr(self, key, kwargs[key]) a = A(l=1, d=2) al # will return 1 ad # will return 2
正如其他人所说的那样,重复也不错,但是在某些情况下,一个名称可能非常适合这种types的问题。 这避免了使用locals()或kwargs,这通常是一个坏主意。
from collections import namedtuple # declare a new object type with three properties; xyz # the first arg of namedtuple is a typename # the second arg is comma-separated or space-separated property names XYZ = namedtuple("XYZ", "x, y, z") # create an object of type XYZ. properties are in order abc = XYZ("one", "two", 3) print abc.x print abc.y print abc.z
我发现它的使用有限,但是你可以像其他任何对象一样inheritance一个namedtuple(例子继续):
class MySuperXYZ(XYZ): """ I add a helper function which returns the original properties """ def properties(self): return self.x, self.y, self.z abc2 = MySuperXYZ(4, "five", "six") print abc2.x print abc2.y print abc2.z print abc2.properties()
显式比隐式更好…所以你可以使它更简洁:
def __init__(self,a,b,c): for k,v in locals().items(): if k != "self": setattr(self,k,v)
更好的问题是你应该?
…如果你想要一个命名的元组,我会build议使用一个namedtuple(记得元组有附加的特定条件)…也许你想要一个ordereddict,甚至只是一个字典…
为了扩大gruszczy
的答案,我使用了一个模式,如:
class X: x = None y = None z = None def __init__(self, **kwargs): for (k, v) in kwargs.items(): if hasattr(self, k): setattr(self, k, v) else: raise TypeError('Unknown keyword argument: {:s}'.format(k))
我喜欢这个方法,因为它:
- 避免重复
- 在构build对象时抵制拼写错误
- 可以很好地与子类(可以只是
super().__init(...)
) - 允许在类级别(属于它们的属性)而不是在
X.__init__
在Python 3.6之前,这不能控制属性的设置顺序,如果某些属性是具有访问其他属性的设置器的属性,则这可能是一个问题。
它可能会稍微改善一点,但我是我自己的代码的唯一用户,所以我不担心任何forms的input卫生。 也许一个AttributeError
会更合适。
你也可以这样做:
locs = locals() for arg in inspect.getargspec(self.__init__)[0][1:]: setattr(self, arg, locs[arg])
当然,你将不得不导入inspect
模块。
这是一个没有任何额外import的解决scheme。
助手function
小帮手function使其更加方便和可重复使用:
def auto_init(local_name_space): """Set instance attributes from arguments. """ self = local_name_space.pop('self') for name, value in local_name_space.items(): setattr(self, name, value)
应用
你需要用locals()
来调用它:
class A: def __init__(self, x, y, z): auto_init(locals())
testing
a = A(1, 2, 3) print(a.__dict__)
输出:
{'y': 2, 'z': 3, 'x': 1}
不改变locals()
如果你不想改变locals()
使用这个版本:
def auto_init(local_name_space): """Set instance attributes from arguments. """ for name, value in local_name_space.items(): if name != 'self': setattr(local_name_space['self'], name, value)
我的0.02 $。 这是非常接近乔兰·比斯利的答案,但更优雅:
def __init__(self, a, b, c, d, e, f): vars(self).update((k, v) for k, v in locals().items() if v is not self)
另外,MikeMüller的答案(对我来说是最好的答案)可以用这种技术来减less:
def auto_init(ns): self = ns.pop('self') vars(self).update(ns)
刚刚从你的__init__
调用auto_init(locals())
这是用Python做事情的一种自然的方式。 不要试图发明更聪明的东西,这会导致你的团队中没有人会理解的过于聪明的代码。 如果你想成为一个团队的球员,然后继续这样写。
一个有趣的库处理这个(并避免了很多其他的样板)是attrs 。 例如,你的例子可以简化为这个(假设这个类被称为MyClass
):
import attr @attr.s class MyClass: x = attr.ib() y = attr.ib() z = attr.ib()
你甚至不需要__init__
方法,除非它还有其他的东西。 这是Glyph Lefkowitz的一个很好的介绍 。