Правильно перегрузить __add__ именованного кортежа
Я пытаюсь перегрузить __add__
метод на экземплярах namedtuple, и у меня возникли некоторые проблемы.
Параметры, введенные в мои именованные кортежи, генерируются динамически. Четыре параметра всегда одинаковы и в одинаковом порядке, но остальные могут быть любыми и в любом количестве. Поэтому мне нужно иметь возможность динамически определять фабрику классов 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
Что я делаю неправильно? Я также попытался создать подкласс namedtuple('Row', ...), как указано в документации https://docs.python.org/2/library/collections.html, но я не смог получить это Работа. Я не мог заставить его динамически изменять названные параметры.
Вот этот провал
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 ответа
Спасибо за ответы. Я вынужден использовать именованные кортежи, потому что я имею дело с результатами, возвращаемыми SQLAlchemy, который возвращает вещи как KeyedTuples, который является их версией именованного кортежа. Поэтому я должен использовать 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)
__add__
метод, который вы определили, является методом, который доступен только для экземпляров типа класса Row
,
Когда вы преодолели __new__
метод вашего Row
класс, вы возвращаете объект типа namedtuple(...)
не Row
, Поэтому дальнейшие манипуляции с этими объектами не будут иметь доступа к вашему __add__
метод, потому что они не Row
с, они namedtuple()
s.
Как уже упоминалось @user2357112, кажется, что вы сами себе все усложняете, и может быть лучше просто использовать словари. Если вам нужен неизменяемый, хешируемый тип для каждой из ваших строк, чтобы вы могли создавать наборы и использовать их в качестве ключей словаря, преобразуйте свои словари в именованные кортежи прямо перед их использованием.