无法为 namedtuple 的子类设置属性
Posted
技术标签:
【中文标题】无法为 namedtuple 的子类设置属性【英文标题】:Can't set attribute for subclasses of namedtuple 【发布时间】:2013-10-15 22:54:03 【问题描述】:看起来this 或this 是一些相关的线程,但还没有弄清楚:)
我正在尝试创建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
但是,这不起作用:
>>> 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
经过一番探索(例如,参见this 线程),我尝试使用构造函数而不是初始化器:
>>> 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)
它似乎构造了一个对象,但我无法读取它的属性:
>>> 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'
我哪里错了?如何创建具有多个构造函数或初始化程序的子类?
【问题讨论】:
为什么你有双重__init__
和__new__
方法?只有第二个重要,它会覆盖第一个。 Python 不会“重载”方法签名。
无重载...所以这意味着我以不同方式(取决于重载的构造函数)创建 C 实例的最初目标实际上是不可行的?
完全可行,只是使用不同的范例。
在您的回答下方查看我的评论。你说使用工厂方法可行,但不能使用多个“构造函数”?
你可以只使用一个构造函数,但是构造函数可以根据你传入的参数改变行为。
【参考方案1】:
命名元组是不可变的,因此您不能在 __init__
初始化程序中操作它们。您唯一的选择是覆盖 __new__
方法:
class C(namedtuple('C', 'x, y')):
__slots__ = ()
def __new__(cls, obj):
return super(C, cls).__new__(cls, obj.x, obj.y)
请注意,因为__new__
是新实例的工厂方法,您确实需要返回新创建的实例。如果您在__new__
方法中不使用return
,则默认返回值是None
,这会给您带来错误。
带有x
和y
属性的对象的演示:
>>> 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 不支持方法重载;通常你要么使用可选的关键字参数,要么使用额外的类方法作为工厂方法。
例如,datetime
module 有几个这样的工厂方法可以让您创建不适合标准构造函数的对象。 datetime.datetime.fromtimestamp()
从单个数值创建 datetime.datetime
实例,datetime.datetime.fromordinal()
也是如此;除了他们以不同的方式解释数字。
如果您想支持可变参数,请执行以下操作:
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)
这里,y
是一个可选参数,如果调用者未提供,则默认为 None
:
>>> C(3, 5):
C(x=3, y=5)
>>> C(O)
C(x=10, y=20)
使用类方法的替代方法是:
class C(namedtuple('C', 'x, y')):
@classmethod
def from_attributes(cls, obj):
return cls(obj.x, obj.y)
现在有两种工厂方法;一个默认,一个命名:
>>> C(3, 5):
C(x=3, y=5)
>>> C.from_attributes(O)
C(x=10, y=20)
【讨论】:
谢谢马丁。在不重载的情况下,我决定使用构造函数(接收x
和y
)和第二个工厂方法(接收obj
)。不漂亮,也许我更喜欢 C++ 风格的构造函数重载,但我想这与 Python 一样多。
使用_replace方法怎么样? docs.python.org/2/library/…
@YonatanSimson:使用它怎么样?你能详细说明吗?请注意,instance._replace()
返回一个新实例,原始实例保持不变。 ._replace()
不会让工厂函数更高效。
@MartijnPieters 是的。你可以这样做:c = c._replace(x=30)
。我觉得即使有一些开销,也最好使用现有的功能。如果没有相同级别的测试和置信度,编写自己的方法可能会产生相同的间接成本
@YonatanSimson:你会如何在这里使用它?用户要求使用工厂方法来创建新对象。 _replace()
在现有实例上效果很好,但这不是这里所要求的。【参考方案2】:
两件事:第一,据我所知,您在这里并没有真正从 namedtuple 中得到什么。所以也许你应该换成普通班。另外,你不能超载
其次,可能有助于解决您的问题的其他可能性:
Factory design pattern - 不是将不同的参数放在构造函数中,而是有一个类,它接受不同类型的参数并在对象外部使用适当的参数调用构造函数。 recordtype - 一个可变的命名元组,它允许使用默认值,但也可以让您以最初想要的方式编写子类。 bunch - 不完全是命名元组,但可以让您创建一些随意的对象。
【讨论】:
【参考方案3】:有一种解决方法可以更改命名元组的属性。
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")
【讨论】:
这将创建一个命名元组的新实例,并且所有对命名元组的现有引用仍将访问原始而不是新的。因此,他们不会看到变化。 不能将新实例分配给旧变量吗? 如果您在运行时跟踪指向原始元组的 所有 变量,那么可以。【参考方案4】:我建议你使用_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)
【讨论】:
以上是关于无法为 namedtuple 的子类设置属性的主要内容,如果未能解决你的问题,请参考以下文章
访问 URLSessionConfiguration 子类的属性时无法识别的选择器