如何避免__init__中的"self.x = x; self.y = y; self.z = z"模式?

Max*_*axB 169 python namedtuple python-2.7 python-decorators python-attrs

我看到像这样的模式

def __init__(self, x, y, z):
    ...
    self.x = x
    self.y = y
    self.z = z
    ...
Run Code Online (Sandbox Code Playgroud)

经常使用更多参数.有没有一种好方法可以避免这种乏味的重复性?该类应该继承namedtuple吗?

gru*_*czy 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
Run Code Online (Sandbox Code Playgroud)

  • 这种方法的问题在于没有记录`A .__ init__`实际上期望的参数,也没有错误检查错误的参数名称. (44认同)
  • 好的答案+1 ......虽然`self .__ dict __.update(kwargs)`可能稍微更加pythonic (16认同)
  • @JoranBeasley用`kwargs`盲目地更新实例字典会让你开始相当于SQL注入攻击.如果你的对象有一个名为`my_method`的方法,并且你将一个名为`my_method`的参数传递给构造函数,那么`update()`字典,你只是覆盖了该方法. (7认同)
  • 正如其他人所说,这个建议的编程风格很差.它隐藏了重要信息.你可以展示它,但你应该明确地阻止OP使用它. (3认同)
  • @Pedro gruzczy和JoranBeasley的语法之间有语义差异吗? (3认同)

Sip*_*hor 89

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

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__))
Run Code Online (Sandbox Code Playgroud)

  • "......明确比隐含更好.简单比复杂更好......" - 禅的Python (14认同)
  • -1坦率地说,这太可怕了.我不知道这段代码一目了然,它实际上是代码量的十倍.聪明感觉很酷,但这是对你明显聪明的误用. (8认同)
  • 对于是喜欢这个还是讨厌它我感到非常不满.我非常感谢保留签名. (4认同)
  • @alexis"decorator.decorator"装饰器自动包装该功能 (3认同)
  • 很好的答案,但不适用于python2.7:没有`签名` (2认同)

Jor*_*ley 29

显性比隐含更好...所以你肯定可以使它更简洁:

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

更好的问题是你吗?

...如果你想要一个命名元组我会建议使用一个名字元组(记住元组有一些附加条件)...也许你想要一个有序的甚至只是一个字典...

  • 那是gnibbler (5认同)
  • 对于稍微高效的测试,`if k!="self":`可以改为`如果v不是self:`,便宜的身份测试,而不是字符串比较.我认为技术上`__init__`可以在施工后第二次调用,并将'self`作为后续参数传递,但我真的不想想那种怪物会做什么.:-) (4认同)
  • @bernie(或者它是bemie?),有时候很难 (3认同)

A S*_*ipt 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
Run Code Online (Sandbox Code Playgroud)

我发现它的使用有限,但你可以像任何其他对象一样继承一个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()
Run Code Online (Sandbox Code Playgroud)

  • 这些*是*元组,因此你的`properties`方法可以写成`return tuple(self)`,如果将来更多的字段添加到namedtuple定义中,它更易于维护. (5认同)

ger*_*rit 20

为了扩展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))
Run Code Online (Sandbox Code Playgroud)

我喜欢这种方法,因为它:

  • 避免重复
  • 在构造物体时抵抗错别字
  • 适用于子类化(可以super().__init(...))
  • 允许在类级别(它们所属的位置)而不是在类中记录属性 X.__init__

在Python 3.6之前,这无法控制属性的设置顺序,如果某些属性是具有访问其他属性的setter的属性,则可能会出现问题.

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


zon*_*ndo 10

你也可以这样做:

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

当然,您必须导入该inspect模块.


Mik*_*ler 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)
Run Code Online (Sandbox Code Playgroud)

应用

您需要调用它locals():

class A:
    def __init__(self, x, y, z):
        auto_init(locals())
Run Code Online (Sandbox Code Playgroud)

测试

a = A(1, 2, 3)
print(a.__dict__)
Run Code Online (Sandbox Code Playgroud)

输出:

{'y': 2, 'z': 3, 'x': 1}
Run Code Online (Sandbox Code Playgroud)

没有改变 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)
Run Code Online (Sandbox Code Playgroud)


ger*_*rit 8

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
Run Code Online (Sandbox Code Playgroud)

除其他外,将添加一个__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
Run Code Online (Sandbox Code Playgroud)

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

如果您的类又大又复杂,使用dataclass. 我是在 Python 3.7.0 发布当天写这篇文章的,因此使用模式尚未确定。


Raf*_*afG 7

处理这个(并避免了很多其他的样板)一个有趣的库ATTRS.例如,您的示例可以简化为此(假设调用该类MyClass):

import attr

@attr.s
class MyClass:
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()
Run Code Online (Sandbox Code Playgroud)

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

  • @gerrit这在[attrs包的文档](https://www.attrs.org/en/stable/why.html#data-classes)中讨论。老实说,差异似乎不再那么大了。 (2认同)

bgu*_*ach 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)
Run Code Online (Sandbox Code Playgroud)

此外,MikeMüller的答案(对我来说最好的答案)可以通过这种技术减少:

def auto_init(ns):
    self = ns.pop('self')
    vars(self).update(ns)
Run Code Online (Sandbox Code Playgroud)

auto_init(locals())来自你的电话__init__