我看到类似的模式
def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...
经常出现这种情况,通常还带有更多的参数。有没有好的方法来避免这种繁琐的重复?应该让类继承自namedtuple
吗?
我看到类似的模式
def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...
经常出现这种情况,通常还带有更多的参数。有没有好的方法来避免这种繁琐的重复?应该让类继承自namedtuple
吗?
免责声明:似乎有几个人对于呈现这个解决方案感到担忧,因此我将提供非常清晰的免责声明。您不应该使用这个解决方案。我只提供它作为信息,让您知道语言是有能力实现这一点的。其余的答案只是展示了语言的能力,而不是赞成以这种方式使用它们。
明确地将参数复制到属性中并没有什么问题。如果构造函数中有太多的参数,有时会被视为代码异味,也许你应该将这些参数分组成更少的对象。其他时候,这是必要的,然后这样做就没有任何问题了。无论如何,明确地完成这个任务是正确的方法。
然而,既然您正在询问如何完成它(而不是是否应该这样做),那么一个解决方案是:
class A:
def __init__(self, **kwargs):
for key in kwargs:
setattr(self, key, kwargs[key])
a = A(l=1, d=2)
a.l # will return 1
a.d # will return 2
self.__dict__.update(kwargs)
可能更符合Python语言的习惯用法。 - Joran BeasleyA.__init__
实际需要哪些参数,并且也没有检查输入参数名是否有误。 - MWBkwargs
更新实例字典会让你的代码容易受到类似于SQL注入攻击的攻击。 如果你的对象有一个名为my_method
的方法,并且你在构造函数中传递了一个名为my_method
的参数,然后又使用update()
方法更新字典,那么你刚刚覆盖了这个方法。请注意保护你的代码免受此类攻击。 - Pedro编辑:如果您使用的是Python 3.7+,只需使用dataclasses
一个保留签名的装饰器解决方案:
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__))
显式优于隐式... 所以,确实可以使它更加简洁:
def __init__(self,a,b,c):
for k,v in locals().items():
if k != "self":
setattr(self,k,v)
更好的问题是:你应该吗?
...话虽如此,如果你想要一个命名元组,我建议使用namedtuple(记住元组有特定的条件附加在它们上面)...也许你想要一个OrderedDict甚至只是一个dict...if k != "self":
改为使用便宜的身份测试if v is not self:
,而不是进行字符串比较。我想从技术上讲,在构建后可能会再次调用__init__
并将self
作为接下来的参数传递,但我真的不想去想那种怪物。 :-) - ShadowRangerlocals
的返回值:set_fields_from_locals(locals())
。然后它就不会比更神奇的基于装饰器的解决方案更长了。 - Lii如其他人所提到的,重复并不是坏事,但在某些情况下,namedtuple可能非常适合这种类型的问题。这避免了使用locals()或kwargs,它们通常是一个坏主意。
from collections import namedtuple
# declare a new object type with three properties; x y z
# 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
我发现它的用处有限,但你可以像任何其他对象一样继承一个具名元组(示例继续):
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()
properties
方法可以简单地编写为return tuple(self)
,如果将来将更多字段添加到命名元组定义中,则更易于维护。 - PaulMcGXYZ = namedtuple("XYZ", "x y z")
同样有效。 - PaulMcGnamedtuple
,特别是在数学代码中,其中一个函数可能高度参数化,并且有一堆系数只有在一起才有意义。 - detlynamedtuple
的问题在于它们是只读的。你不能执行 abc.x += 1
或者类似的操作。 - hamstergeneclass 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))
我喜欢这种方法,因为它:
super().__init(...)
)X.__init__
中在 Python 3.6 之前,这无法控制属性设置的顺序,如果某些属性是具有设置器且访问其他属性的属性,则可能会出现问题。
它可能还可以改进一下,但我是自己代码的唯一用户,所以我不担心任何形式的输入处理。也许AttributeError
更合适。
你也可以这样做:
locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])
当然,你需要导入 inspect
模块。
这是一种不需要任何额外导入的解决方案。
一个小的辅助函数使其更加方便和可重用:
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())
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)
locals()
不应被修改(这可能会影响解释器,在您的情况下,从调用函数的作用域中删除 self
)。 - MWBself
在 __init__
中仍然可用。 - Mike Müller在 Python 3.7 中,你可以使用位于 dataclasses
模块中的 dataclass
装饰器(滥用)。以下是官方文档的描述:
如果你的类很大且复杂,使用This module provides a decorator and functions for automatically adding generated special methods such as
__init__()
and__repr__()
to user-defined classes. It was originally described in PEP 557.The member variables to use in these generated methods are defined using PEP 526 type annotations. For example this code:
@dataclass class InventoryItem: '''Class for keeping track of an item in inventory.''' name: str unit_price: float quantity_on_hand: int = 0 def total_cost(self) -> float: return self.unit_price * self.quantity_on_hand
Will add, among other things, a
__init__()
that looks like:
def __init__(self, name: str, unit_price: float, quantity_on_hand: int=0): self.name = name self.unit_price = unit_price self.quantity_on_hand = quantity_on_hand
Note that this method is automatically added to the class: it is not directly specified in the InventoryItem definition shown above.
dataclass
可能不合适。 我是在发布Python 3.7.0当天写这篇文章的,因此使用模式尚未得到很好的确定。dataclasses
模块对attr
的功能有多少冗余? - gerritdef __init__(self, a, b, c, d, e, f):
vars(self).update((k, v) for k, v in locals().items() if v is not self)
def auto_init(ns):
self = ns.pop('self')
vars(self).update(ns)
locals()
(未定义行为)。 - MWB
__slots__
来实现这个目的;虽然相对来说有点不符合 Python 的风格(更加冗长以实现内存节省),但我还是喜欢这种方法,主要是为了避免因为打错属性名而导致自动生成一个新的属性。 - ShadowRangerini <shortcut> x, y, z): <shortcut>
就可以完成了。 - Gere