2017-08-18 3 views
0

J'essaye de surcharger la méthode __add__ sur des instances namedtuple et j'ai un peu de problème.surcharger correctement le __add__ d'un namedtuple

Les paramètres entrés dans mes namedtuples sont générés dynamiquement. Quatre paramètres sont toujours les mêmes et dans le même ordre, mais le reste peut être n'importe quoi et n'importe quel nombre. Je dois donc pouvoir définir dynamiquement ma fabrique de classes namedtuple. Et après avoir créé plusieurs instances, j'aimerais pouvoir les ajouter ensemble dans une nouvelle instance namedtuple, avec tous les paramètres uniques ensemble. Mais j'ai de la difficulté à surcharger correctement la méthode __add__. Cela ne semble pas fonctionner.

Ainsi, par exemple, si j'ai 3 instances 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) 

Je voudrais pouvoir les ajouter comme e + m + t qui retourne

Row(a=1, b=2, c=3, d=4, param1='a', param2='b', param3='val', param4=10)

Voici mon code actuel

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) 

Avec ceci, je peux correc TLY générer dynamiquement de nouvelles namedtuples, et les instancier

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') 

mais quand je les ajoute, ma surcharge __add__ ne sera jamais appelé et je semble juste obtenir un objet régulier de tuple retour sur

w = e2 + m2 
print(w) 
(1, 2, 3, 4, 1, 2, 3, 4, 'a', 'b') 
type(w) 
tuple 

Ma __add__ méthode ne semble pas être actif sur mes objets d'instance.

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 

Qu'est-ce que je fais mal? J'ai aussi essayé de sous-classer namedtuple ('Row', ...), comme indiqué dans les docs https://docs.python.org/2/library/collections.html#collections.namedtuple, mais je n'ai pas réussi à faire marcher ça. Je n'ai pas réussi à changer dynamiquement les paramètres nommés.

est que l'échec

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' 
+0

C'est ... contrairement à beaucoup de décisions de conception namedtuple, et peu susceptible de se révéler bien. Je recommande d'utiliser simplement des dicts. – user2357112

Répondre

0

Merci pour les réponses. Je suis obligé d'utiliser namedtuples parce que je traite des résultats retournés par SQLAlchemy, qui retourne des choses comme KeyedTuples, qui est leur version d'un namedtuple. Je dois donc utiliser namedtuple pour que mes fonctions générales puissent fonctionner avec les deux. Je suis sûr que cela brise toute l'éthique des tuples.

Pour la postérité, voici comment je l'ai résolu. Puisque namedtuple est en fait juste une fonction qui génère des classes, j'ai simplement écrit ma propre fonction, qui générera dynamiquement un nouvel objet namedtuple, de la même manière, et surcharger la méthode __add__ pour chaque classe qui est générée.

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 

Il est utilisé comme si

# 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) 
1

La méthode __add__ que vous avez défini est une méthode qui est accessible uniquement aux instances de type classe .

Lorsque vous la méthode __new__ redéfini de votre classe , vous retournez un objet de type namedtuple(...), non . Par conséquent, une manipulation ultérieure de ces objets n'aura pas accès à votre méthode __add__ car ils ne sont pas s, ils sont namedtuple() s. En tant que @ utilisateur2357112 mentionné, il semble que vous vous fassiez des choses difficiles et que vous fassiez mieux d'utiliser simplement des dictionnaires. Si vous avez besoin d'un type modifiable et immuable pour chacune de vos lignes afin de pouvoir créer des ensembles et les utiliser comme des clés de dictionnaire, convertissez vos dictionnaires en tuples nommés juste avant de les utiliser de cette façon.