如何避免 _ _ init _ _ 中的“ self. x = x; self. y = y; self. z = z”模式?

我看到了类似

def __init__(self, x, y, z):
...
self.x = x
self.y = y
self.z = z
...

非常频繁,通常有更多的参数。有没有什么好办法可以避免这种单调乏味的重复呢?该类应该从 namedtuple继承吗?

15354 次浏览

明示比暗示好。 当然,你可以说得更简洁些:

def __init__(self,a,b,c):
for k,v in locals().items():
if k != "self":
setattr(self,k,v)

更好的问题是: 你应该这样做吗?

如果你想要一个有名字的元组,我建议你使用一个有名字的元组(记住元组有一定的附加条件) ... ... 也许你想要一个有序的词汇表,甚至只是一个词汇表... ..。

免责声明: 似乎有几个人关心提出这个解决方案,所以我将提供一个非常清楚的免责声明。您不应该使用此解决方案。我只是提供信息,所以你知道语言能做到这一点。答案的其余部分只是展示了语言能力,而不是支持以这种方式使用它们。


显式地将参数复制到属性中并没有什么问题。如果在 ctor 中有太多的参数,有时会认为这是一种代码味道,也许您应该将这些参数分组成更少的对象。其他时候,它是必要的,没有什么错。不管怎么说,明确地去做才是正确的做法。

然而,既然你问的是如何实现(而不是是否应该实现) ,那么一个解决方案是:

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

你也可以这样做:

locs = locals()
for arg in inspect.getargspec(self.__init__)[0][1:]:
setattr(self, arg, locs[arg])

当然,您必须导入 inspect模块。

正如其他人所提到的,重复并不坏,但在某些情况下,命名元组可能非常适合这种类型的问题。这样可以避免使用 local ()或 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

我发现它的用途有限,但是您可以像继承任何其他对象一样继承 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()

编辑: 如果您有 python 3.7 + ,只需使用 < a href = “ https://docs.python.org/3/library/dataclasses.html”rel = “ nofollow noReferrer”> dataclass

一个保留签名的装饰解决方案:

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__))

为了扩展 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))

我喜欢这个方法是因为:

  • 避免重复
  • 在构造一个对象时能够抵抗打字错误
  • 工作良好的子类(可以只是 super().__init(...))
  • 允许在类级别(它们所属的位置)而不是在 X.__init__中记录属性

在 Python 3.6之前,这不能控制属性的设置顺序,如果某些属性是带有访问其他属性的 setter 的属性,这可能是一个问题。

它可能还需要进一步改进,但我是自己代码的唯一用户,因此我不担心任何形式的输入环境卫生。也许 AttributeError更合适。

这是一个不需要任何额外导入的解决方案。

辅助功能

一个小型的 helper 函数使它更加方便和可重用:

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)

我的0.02美元。这个答案非常接近乔伦•比斯利(Jordan Beasley) ,但更加优雅:

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)

另外,Mike Müller 的回答(我认为是最好的一个)可以通过这种方法得到简化:

def auto_init(ns):
self = ns.pop('self')
vars(self).update(ns)

从你的 __init__呼叫 auto_init(locals())

在 Python 中这是一种很自然的做事方式。不要试图发明更聪明的东西,它会导致过度聪明的代码,你的团队中没有人会理解。如果你想成为一个有团队精神的人,那就继续这样写吧。

吸引是一个有趣的库,它可以处理这个问题(并避免许多其他样板文件)。例如,您的示例可以简化为以下内容(假设该类名为 MyClass) :

import attr


@attr.s
class MyClass:
x = attr.ib()
y = attr.ib()
z = attr.ib()

你甚至不再需要 __init__方法,除非它还能做其他事情。

Python 3.7开始

在 Python 3.7中,您可以(ab)使用从 dataclasses模块中提供的 dataclass装饰器:

该模块提供了一个修饰符和函数,用于自动将生成的特殊方法(如 __init__()__repr__())添加到用户定义的类中。它最初是在 PEP 557中描述的。

在这些生成的方法中使用的成员变量是使用 PEP 526类型注释定义的:

@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

将添加一个 __init__(),它看起来像:

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

请注意,此方法是自动添加到类中的: 它不是在上面显示的 InventoryItem 定义中直接指定的。

如果您的类很大而且很复杂,那么使用 dataclass是不合适的。我是在 Python3.7.0发布的当天写这篇文章的,因此使用模式还没有很好地建立起来。