正确重载了一个namedtuple的__add__

时间:2017-08-18 03:43:18

标签: python overloading namedtuple

我试图在namedtuple实例上重载__add__方法,但我遇到了一些麻烦。

输入我的namedtuples的参数是动态生成的。四个参数总是相同且顺序相同,但其余参数可以是任何数字。所以我需要能够动态定义我的namedtuple类工厂。在我创建了几个实例之后,我希望能够将它们一起添加到一个新的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一样添加

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

我做错了什么?我还尝试了对文档https://docs.python.org/2/library/collections.html#collections.namedtuple中指明的namedtuple(&#39; Row&#39;,...)进行子类化,但是我无法让它工作。我无法动态更改命名参数。

这是失败

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'

2 个答案:

答案 0 :(得分:1)

您定义的__add__方法是只能由类类型Row的实例访问的方法。

当您覆盖__new__类的Row方法时,会返回namedtuple(...)类型的对象,而不是Row。因此,对这些对象的进一步操作将无法访问您的__add__方法,因为它们不是Row,而是namedtuple() s。

作为一个@ user2357112提到,看起来你正在为自己制造困难,而且仅仅使用字典可能会更好。如果您需要为每个行创建一个不可变的,可散列的类型,那么您可以创建集合并将它们用作字典键,在使用它们之前将字典转换为命名元组。

答案 1 :(得分:0)

感谢您的回复。我有点被迫使用了namedtuples因为我正在处理SQLAlchemy返回的结果,它返回的东西是KeyedTuples,这是他们的namedtuple的版本。所以我必须使用namedtuple,以便我的一般功能可以同时使用。我确信这打破了元组的整个精神。

对后人来说,这就是我解决它的方式。由于namedtuple实际上只是一个生成类的函数,我只是编写了自己的函数,它将以相同的方式动态生成一个新的namedtuple对象,并将__add__方法重载到生成的每个类。

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)