Python 无法为 namedtuple 的子类设置属性

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19257497/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 13:16:34  来源:igfitidea点击:

Can't set attribute for subclasses of namedtuple

pythonclasspython-3.xnamedtuple

提问by Jens

It looks like thisor thisare somewhat related threads, but still haven't figured things out :)

看起来这个这个有点相关的线程,但仍然没有弄清楚:)

I'm trying to create a subclass of namedtupleand provide different initializers so that I can construct objects in different ways. For example:

我正在尝试创建一个子类namedtuple并提供不同的初始值设定项,以便我可以以不同的方式构造对象。例如:

>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
...     __slots__ = ()
...     def __init__(self, obj) : # Initialize a C instance by copying values from obj
...         self.x = obj.a
...         self.y = obj.b
...     def __init__(self, x, y) : # Initialize a C instance from the parameters
...         self.x = x
...         self.y = y

However, that doesn't work:

但是,这不起作用:

>>> c = C(1, 2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in __init__
AttributeError: can't set attribute

After some poking around (for example, see thisthread) I tried to use constructors instead of initializers:

经过一番摸索(例如,请参阅线程),我尝试使用构造函数而不是初始值设定项:

>>> from collections import namedtuple
>>> class C(namedtuple("C", "x, y")) :
...     __slots__ = ()
...     def __new__(cls, obj) :
...       self = super(C, cls).__new__(cls, obj.a, obj.b)
...     def __new__(cls, x, y) :
...       self = super(C, cls).__new__(cls, x, y)

which seemed to construct an object but then I can't read its attributes:

这似乎构造了一个对象,但后来我无法读取它的属性:

>>> c = C(1,2)
>>> c.x, c.y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'x'

Where am I going wrong here? How can I create a subclass with multiple constructors or initializers?

我哪里出错了?如何创建具有多个构造函数或初始值设定项的子类?

采纳答案by Martijn Pieters

Named tuples are immutable, so you cannot manipulate them in the __init__initializer. Your only option is to override the __new__method:

命名元组是不可变的,因此您不能在__init__初始化程序中操作它们。您唯一的选择是覆盖该__new__方法:

class C(namedtuple('C', 'x, y')):
    __slots__ = ()
    def __new__(cls, obj):
        return super(C, cls).__new__(cls, obj.x, obj.y)

Note that because __new__is a factory method for new instances, you do need to returnthe newly created instance. If you do not use returnin the __new__method, the default return value is None, which gives you your error.

请注意,由于__new__是新实例的工厂方法,因此您确实需要返回新创建的实例。如果您不在return__new__方法中使用,则默认返回值是None,这会给您带来错误。

Demo with an object with xand yattributes:

带有xy属性的对象的演示:

>>> class C(namedtuple('C', 'x, y')):
...     __slots__ = ()
...     def __new__(cls, obj):
...         return super(C, cls).__new__(cls, obj.x, obj.y)
... 
>>> O.x, O.y
(10, 20)
>>> C(O)
C(x=10, y=20)

Python does not support method overloading; generally you either use optional keyword arguments or extra class methods as factory methods.

Python 不支持方法重载;通常,您要么使用可选的关键字参数,要么使用额外的类方法作为工厂方法。

The datetimemodule, for example, has several such factory methods to let you create objects that do not fit the standard constructor. datetime.datetime.fromtimestamp()creates a datetime.datetimeinstance from a single numeric value, and so does datetime.datetime.fromordinal(); except that they interpret the number in different ways.

datetime模块,例如,有几个这样的工厂方法,让你创建不符合标准的构造函数对象。从单个数值datetime.datetime.fromtimestamp()创建一个datetime.datetime实例,也是如此datetime.datetime.fromordinal();除了他们以不同的方式解释数字。

If you wanted to support variable arguments, do:

如果您想支持可变参数,请执行以下操作:

class C(namedtuple('C', 'x, y')):
    __slots__ = ()

    def __new__(cls, x, y=None):
        if y is None:
            # assume attributes
            x, y = x.x, x.y
        return super(C, cls).__new__(cls, x, y)

Here, yis an optional argument, defaulting to Noneif not supplied by the caller:

这里,y是一个可选参数,None如果调用者没有提供,则默认为:

>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)

The alternative, using a class method, would be:

使用类方法的替代方法是:

class C(namedtuple('C', 'x, y')):
    @classmethod
    def from_attributes(cls, obj):
        return cls(obj.x, obj.y)

Now there are two factory methods; one default and one named:

现在有两种工厂方法;一个默认值和一个命名为:

>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)

回答by Corley Brigman

Two things: one, you're not really getting much out of namedtuple here, as far as i can tell. So maybe you should just switch to a normal class. Also, you can't overload the

两件事:第一,据我所知,你并没有真正从这里的 namedtuple 中得到多少。所以也许你应该切换到普通班级。此外,你不能超载

Second, other possibilities which might help with your problem:

其次,其他可能有助于解决您的问题的可能性:

Factory design pattern- instead of putting the different parameters in the constructor, have a class that takes different kinds of parameters and calls the constructor with appropriate arguments, outside the object. recordtype- a mutable namedtuple, that allows defaults but would also let you write your subclass the way you originally wanted. bunch- not exactly a named tuple, but lets you create somewhat arbitrary objects.

工厂设计模式- 不是将不同的参数放在构造函数中,而是拥有一个类,该类采用不同类型的参数并在对象外部使用适当的参数调用构造函数。 recordtype- 一个可变的命名元组,它允许默认值,但也可以让您按照最初想要的方式编写子类。 一堆- 不完全是一个命名元组,但可以让你创建一些任意的对象。

回答by chahuja

There is a workaround to changing the attribute of a namedtuple.

有一种解决方法可以更改命名元组的属性。

import collections

def updateTuple(NamedTuple,nameOfNamedTuple):
    ## Convert namedtuple to an ordered dictionary, which can be updated
    NamedTuple_asdict = NamedTuple._asdict()

    ## Make changes to the required named attributes
    NamedTuple_asdict['path']= 'www.google.com'

    ## reconstruct the namedtuple using the updated ordered dictionary
    updated_NamedTuple = collections.namedtuple(nameOfNamedTuple, NamedTuple_asdict.keys())(**NamedTuple_asdict)

    return updated_NamedTuple

Tuple = collections.namedtuple("Tuple", "path")
NamedTuple = Tuple(path='www.yahoo.com')
NamedTuple = updateTuple(NamedTuple, "Tuple")

回答by Yonatan Simson

I suggest you use the the _replacemethod

我建议你使用_replace方法

from collections import namedtuple
C = namedtuple('C', 'x, y')
c = C(x=10, y=20)
# c.x = 30 won't work
c = c._replace(x=30)