正确重载 namedtuple 的 __add__
correctly overloading the __add__ of a namedtuple
我试图在 namedtuple 实例上重载 __add__
方法,但遇到了一些麻烦。
输入到我的命名元组中的参数是动态生成的。四个参数始终相同且顺序相同,但其余参数可以是任何数量。所以我需要能够动态定义我的 namedtuple class 工厂。在我创建了几个实例之后,我希望能够将它们一起添加到一个新的 namedtuple 实例中,并将所有唯一参数放在一起。但是我无法正确重载 __add__
方法。它似乎不起作用。
例如,如果我有 3 个 namedtuple 实例
e = Row(a=1, b=2, c=3, d=4)
m = Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
t = Row(a=1, b=2, c=3, d=4, param3='val', param4=10)
我希望能够添加它们,例如 e + m + t
其中 returns
Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='val', param4=10)
这是我当前的代码
class Row(object):
''' Creates a new namedtuple object '''
__slots__ = ()
def __new__(cls, *args, **kwargs):
''' make a new Row instance '''
default = namedtuple('Row', 'a, b, c, d')
newcols = set(args) - set(default._fields)
finalfields = default._fields + tuple(newcols) if newcols else default._fields
return namedtuple('Row', finalfields)
def __add__(self, other):
''' This is the new add '''
self_dict = self._asdict()
other_dict = other._asdict()
self_dict.update(other_dict)
new_fields = tuple(self_dict.keys())
new_row = namedtuple('Row', new_fields)
return new_row(**self_dict)
有了这个,我可以正确地动态生成新的命名元组,并实例化它们
e = Row()
m = Row(*['a', 'b', 'c', 'd', 'param1', 'param2'])
e._fields
('a', 'b', 'c', 'd')
m._fields
('a', 'b', 'c', 'd', 'param1', 'param2')
e2 = e(1, 2, 3, 4)
m2 = m(1, 2, 3, 4, 'a', 'b')
e2
Row(a=1, b=2, c=3, d=4)
type(e2)
__main__.Row
m2
Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
但是当我添加它们时,我的重载 __add__
永远不会被调用,我似乎只是得到一个常规的元组对象
w = e2 + m2
print(w)
(1, 2, 3, 4, 1, 2, 3, 4, 'a', 'b')
type(w)
tuple
我的 __add__
方法在我的实例对象上似乎没有激活。
Row.__add__?
Signature: Row.__add__(self, other)
Docstring: This is the new add
File: <ipython-input-535-817d9f528ae7>
Type: instancemethod
e.__add__?
Type: wrapper_descriptor
String form: <slot wrapper '__add__' of 'tuple' objects>
Docstring: x.__add__(y) <==> x+y
e2.__add__?
Type: method-wrapper
String form: <method-wrapper '__add__' of Row object at 0x122614050>
Docstring: x.__add__(y) <==> x+y
我做错了什么?我还尝试了 subclassing namedtuple('Row', ...),如文档 https://docs.python.org/2/library/collections.html#collections.namedtuple 中所示,但我无法让它工作。我无法让它动态更改命名参数。
这是失败
BaseRow = namedtuple('BaseRow', 'a, b, c, d')
class Row(BaseRow):
__slots__ = ()
def __new__(cls, *args, **kwargs):
new_fields = set(kwargs.keys()) - set(cls._fields)
cls._fields += tuple(new_fields)
obj = super(Row, cls).__new__(cls, *args, **kwargs)
return obj
e = Row(a=1, b=2, c=3, d=4, param1='a')
TypeError: __new__() got an unexpected keyword argument 'param1'
您定义的 __add__
方法是仅供 class 类型 Row
的实例访问的方法。
当您覆盖 Row
class 的 __new__
方法时,您 return 是 namedtuple(...)
类型的对象,而不是 Row
.因此,对这些对象的进一步操作将无法访问您的 __add__
方法,因为它们不是 Row
,而是 namedtuple()
。
正如 @user2357112 所提到的,您似乎在给自己找麻烦,最好还是使用字典。如果您的每一行都需要一个不可变的、可散列的类型,以便您可以创建集合并将它们用作字典键,请在以这种方式使用它们之前将您的字典转换为命名元组。
感谢您的回复。我有点被迫使用命名元组,因为我正在处理 SQLAlchemy 返回的结果,returns 作为 KeyedTuples,这是他们的命名元组版本。所以我必须使用 namedtuple 以便我的通用函数可以同时使用两者。我敢肯定这打破了元组的整个精神。
为了后代,我是这样解决的。由于 namedtuple 实际上只是一个生成 classes 的函数,我只是简单地编写了自己的函数,它将以相同的方式动态生成一个新的 namedtuple 对象,并为每个 [=] 重载 __add__
方法19=] 即生成。
def mytuple(name, params=None, **kwargs):
# check the params input
if params and isinstance(params, six.string_types):
params = params.split(',') if ',' in params else [params]
params = [p.strip() for p in params]
# create default namedtuple and find new columns
default = namedtuple(name, 'a, b, c, d')
newcols = [col for col in params if col not in default._fields] if params else None
finalfields = default._fields + tuple(newcols) if newcols else default._fields
nt = namedtuple(name, finalfields, **kwargs)
def new_add(self, other):
''' Overloaded add to combine tuples without duplicates '''
self_dict = self._asdict()
other_dict = other._asdict()
self_dict.update(other_dict)
new_fields = tuple(self_dict.keys())
new_row = mytuple(self.__class__.__name__, new_fields)
return new_row(**self_dict)
# append new properties and overloaded methods
nt.__add__ = new_add
return nt
像这样使用
# create first version
nt = mytuple('Row', 'a, b, c, d')
e = nt(1,2,3,4)
e
Row(a=1, b=2, c=3, d=4)
# create second version
nt = mytuple('Row', 'a, b, c, d, param1, param2')
m = nt(1,2,3,4,'a','b')
m
Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
# create third version
nt = mytuple('Row', 'a, b, c, d, param3, param4')
s = nt(1,2,3,4,'stuff',10.2345)
s
Row(a=1, b=2, c=3, d=4, param3='stuff', param4=10.2345)
# add them together
d = e + m + s
d
Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='stuff', param4=10.2345)
我试图在 namedtuple 实例上重载 __add__
方法,但遇到了一些麻烦。
输入到我的命名元组中的参数是动态生成的。四个参数始终相同且顺序相同,但其余参数可以是任何数量。所以我需要能够动态定义我的 namedtuple class 工厂。在我创建了几个实例之后,我希望能够将它们一起添加到一个新的 namedtuple 实例中,并将所有唯一参数放在一起。但是我无法正确重载 __add__
方法。它似乎不起作用。
例如,如果我有 3 个 namedtuple 实例
e = Row(a=1, b=2, c=3, d=4)
m = Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
t = Row(a=1, b=2, c=3, d=4, param3='val', param4=10)
我希望能够添加它们,例如 e + m + t
其中 returns
Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='val', param4=10)
这是我当前的代码
class Row(object):
''' Creates a new namedtuple object '''
__slots__ = ()
def __new__(cls, *args, **kwargs):
''' make a new Row instance '''
default = namedtuple('Row', 'a, b, c, d')
newcols = set(args) - set(default._fields)
finalfields = default._fields + tuple(newcols) if newcols else default._fields
return namedtuple('Row', finalfields)
def __add__(self, other):
''' This is the new add '''
self_dict = self._asdict()
other_dict = other._asdict()
self_dict.update(other_dict)
new_fields = tuple(self_dict.keys())
new_row = namedtuple('Row', new_fields)
return new_row(**self_dict)
有了这个,我可以正确地动态生成新的命名元组,并实例化它们
e = Row()
m = Row(*['a', 'b', 'c', 'd', 'param1', 'param2'])
e._fields
('a', 'b', 'c', 'd')
m._fields
('a', 'b', 'c', 'd', 'param1', 'param2')
e2 = e(1, 2, 3, 4)
m2 = m(1, 2, 3, 4, 'a', 'b')
e2
Row(a=1, b=2, c=3, d=4)
type(e2)
__main__.Row
m2
Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
但是当我添加它们时,我的重载 __add__
永远不会被调用,我似乎只是得到一个常规的元组对象
w = e2 + m2
print(w)
(1, 2, 3, 4, 1, 2, 3, 4, 'a', 'b')
type(w)
tuple
我的 __add__
方法在我的实例对象上似乎没有激活。
Row.__add__?
Signature: Row.__add__(self, other)
Docstring: This is the new add
File: <ipython-input-535-817d9f528ae7>
Type: instancemethod
e.__add__?
Type: wrapper_descriptor
String form: <slot wrapper '__add__' of 'tuple' objects>
Docstring: x.__add__(y) <==> x+y
e2.__add__?
Type: method-wrapper
String form: <method-wrapper '__add__' of Row object at 0x122614050>
Docstring: x.__add__(y) <==> x+y
我做错了什么?我还尝试了 subclassing namedtuple('Row', ...),如文档 https://docs.python.org/2/library/collections.html#collections.namedtuple 中所示,但我无法让它工作。我无法让它动态更改命名参数。
这是失败
BaseRow = namedtuple('BaseRow', 'a, b, c, d')
class Row(BaseRow):
__slots__ = ()
def __new__(cls, *args, **kwargs):
new_fields = set(kwargs.keys()) - set(cls._fields)
cls._fields += tuple(new_fields)
obj = super(Row, cls).__new__(cls, *args, **kwargs)
return obj
e = Row(a=1, b=2, c=3, d=4, param1='a')
TypeError: __new__() got an unexpected keyword argument 'param1'
您定义的 __add__
方法是仅供 class 类型 Row
的实例访问的方法。
当您覆盖 Row
class 的 __new__
方法时,您 return 是 namedtuple(...)
类型的对象,而不是 Row
.因此,对这些对象的进一步操作将无法访问您的 __add__
方法,因为它们不是 Row
,而是 namedtuple()
。
正如 @user2357112 所提到的,您似乎在给自己找麻烦,最好还是使用字典。如果您的每一行都需要一个不可变的、可散列的类型,以便您可以创建集合并将它们用作字典键,请在以这种方式使用它们之前将您的字典转换为命名元组。
感谢您的回复。我有点被迫使用命名元组,因为我正在处理 SQLAlchemy 返回的结果,returns 作为 KeyedTuples,这是他们的命名元组版本。所以我必须使用 namedtuple 以便我的通用函数可以同时使用两者。我敢肯定这打破了元组的整个精神。
为了后代,我是这样解决的。由于 namedtuple 实际上只是一个生成 classes 的函数,我只是简单地编写了自己的函数,它将以相同的方式动态生成一个新的 namedtuple 对象,并为每个 [=] 重载 __add__
方法19=] 即生成。
def mytuple(name, params=None, **kwargs):
# check the params input
if params and isinstance(params, six.string_types):
params = params.split(',') if ',' in params else [params]
params = [p.strip() for p in params]
# create default namedtuple and find new columns
default = namedtuple(name, 'a, b, c, d')
newcols = [col for col in params if col not in default._fields] if params else None
finalfields = default._fields + tuple(newcols) if newcols else default._fields
nt = namedtuple(name, finalfields, **kwargs)
def new_add(self, other):
''' Overloaded add to combine tuples without duplicates '''
self_dict = self._asdict()
other_dict = other._asdict()
self_dict.update(other_dict)
new_fields = tuple(self_dict.keys())
new_row = mytuple(self.__class__.__name__, new_fields)
return new_row(**self_dict)
# append new properties and overloaded methods
nt.__add__ = new_add
return nt
像这样使用
# create first version
nt = mytuple('Row', 'a, b, c, d')
e = nt(1,2,3,4)
e
Row(a=1, b=2, c=3, d=4)
# create second version
nt = mytuple('Row', 'a, b, c, d, param1, param2')
m = nt(1,2,3,4,'a','b')
m
Row(a=1, b=2, c=3, d=4, param1='a', param2='b')
# create third version
nt = mytuple('Row', 'a, b, c, d, param3, param4')
s = nt(1,2,3,4,'stuff',10.2345)
s
Row(a=1, b=2, c=3, d=4, param3='stuff', param4=10.2345)
# add them together
d = e + m + s
d
Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='stuff', param4=10.2345)