171

我看到像这样的模式

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

非常频繁,通常带有更多参数。有没有避免这种乏味重复的好方法?类应该继承自namedtuple吗?

4

11 回答 11

108

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


将参数显式复制到属性中并没有什么问题。如果 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
于 2016-02-04T01:29:28.520 回答
87

编辑:如果你有 python 3.7+,只需使用数据类

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

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__))
于 2016-02-04T07:24:51.343 回答
29

正如其他人所提到的,重复并不坏,但在某些情况下,命名元组可能非常适合此类问题。这避免了使用 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()
于 2016-02-04T02:48:05.917 回答
29

显式优于隐式......所以当然,你可以让它更简洁:

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

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

...也就是说,如果您想要一个命名元组,我建议您使用一个命名元组(记住元组附加了某些条件)...也许您想要一个 OrderedDict 甚至只是一个 dict ...

于 2016-02-04T01:27:34.417 回答
22

为了扩展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 之前,这无法控制设置属性的顺序,如果某些属性是具有访问其他属性的设置器的属性,这可能是一个问题。

它可能会有所改进,但我是我自己代码的唯一用户,所以我不担心任何形式的输入卫生。也许 anAttributeError会更合适。

于 2016-02-04T08:31:27.877 回答
10

你也可以这样做:

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

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

于 2016-02-04T01:35:38.840 回答
8

这是一个没有任何额外导入的解决方案。

辅助函数

一个小的辅助函数使它更方便和可重用:

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)
于 2016-02-06T09:59:31.930 回答
7

一个有趣的库处理这个(并避免了很多其他样板)是attrs。例如,您的示例可以简化为(假设该类被称为MyClass):

import attr

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

你甚至__init__不再需要一个方法,除非它也做其他事情。这是Glyph Lefkowitz 的精彩介绍

于 2017-01-06T10:04:24.060 回答
5

我的 0.02 美元。它非常接近 Joran 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)

auto_init(locals())以及来自您的公正电话__init__

于 2016-02-10T09:26:32.137 回答
4

Python 3.7 以上

在 Python 3.7 中,您可以(ab)使用装饰器,该dataclass装饰器可从dataclasses模块中获得。从文档中:

该模块提供了一个装饰器和函数,用于自动将生成的特殊方法添加到用户定义的类中__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. 我是在 Python 3.7.0 发布的那天写这篇文章的,所以使用模式还没有很好地建立起来。

于 2018-06-27T13:56:10.753 回答
4

这是在 Python 中做事的一种自然方式。不要试图发明更聪明的东西,它会导致你的团队中没有人能理解的过于聪明的代码。如果你想成为一个团队合作者,然后继续这样写。

于 2016-02-21T01:35:46.163 回答