2009-12-09 3 views
3

Voici une maquette de ce que je veux faire:Python: Comment modifier dynamiquement les méthodes des objets dict et list?

alist = [1,2,3,4,5] # create a vanilla python list object 
replacef (alist)  # replace __setitem__, extend,... with custom functions 
alist[0]=2   # now the custom __setitem__ is called 

Ceci est pour un projet DSL où la syntaxe doit être aussi proche de la liste python normale que possible, le sous-classement ainsi et de faire l'appel de l'utilisateur quelque chose comme alist = MyList (1,2,3,4,5) n'est pas souhaitable. En outre, parce que le DSL doit coexister avec d'autres bibliothèques, liste et changer le monde dict est pas une option ...

J'ai essayé de créer instancemethods et les mettre directement sur l'objet comme alist.append = myFunc, mais dit Python ces propriétés sont en lecture seule. La modification de l'attribut __class__ ne semble pas non plus autorisée.

Est ce que j'essaye de faire même possible en Python?

Mise à jour: voici quelques conclusions sur ce qui est possible avec une sous-classe de l'objet:

Vu:

class y(object): 
    def __init__(self,a,b,c): 
    self.a = a 
    self.b = b 
    self.c = c 
def f(self): 
    print self 
    print self.a 
    print self.b 
    print self.c 

class x(object): 
def __init__(self,a,b,c): 
    self.a = a 
    self.b = b 
    self.c = c 
def f(self): 
    print "x.f()" 

>>> objy = y(1,2,3) 
>>> objx = x(4,5,6) 
>>> objy.f() 
    <__main__.y object at 0x02612650> 
    1 
    2 
    3 
>>> objx.f() 
    x.f() 

Il est possible de changer la classe de objx à la volée comme ceci:

>>> objx.__class__ = y 
>>> objx.f() 
<__main__.y object at 0x02612D90> 
4 
5 
6 

en outre, on peut ajouter dynamiquement/modifier les méthodes d'objets à la volée, un peu comme en javascript:

>>> def g(self,p): 
     print self 
     print p 
>>> import new 
>>> objy.g = new.instancemethod(g,objy,y) 
>>> objy.g(42) 
<__main__.y object at 0x02612650> 
42 

Cependant, les deux approches échouent avec des objets qui sont mis en œuvre en C comme dict et liste:

>>> def mypop(self): 
     print "mypop" 
     list.mypop(self) 


>>> alist.pop = new.instancemethod(mypop,alist,list) 

AttributeError: 'list' object attribute 'pop' is read-only 

>>> x = [1,2,3,4,5] 
>>> x.__class__ = mylist 

TypeError: __class__ assignment: only for heap types 

L'approche proposée d'utiliser quelque chose comme alist = replacef (alist) ne fonctionnerait pas ici, parce que replacef pourrait être appelé à partir d'un appel __setattribute__ comme ceci:

alist = [1,2,3,4,5] 
aDSLObject.items = alist # __setattribute__ calls replacef on alist 
alist[0] = .... 

Ainsi, alors qu'il est possible de changer les méthodes d'objet, il semble en effet dynamique comme il est impossible t o modifier le comportement des objets implémentés en C - ou est-ce que je manque quelque chose?

Répondre

4

Peut-être que vous pouvez faire la chose "alist = MyList (1,2,3,4,5)" à l'intérieur de la fonction replaceref?

def replacef(l): 
    return MyList(l) # Return instance of list subclass that has custom __setitem__ 

alist = [1,2,3,4,5] 
alist = replaceref(alist) 

Cet utilisateur moyen serait toujours utiliser la syntaxe de liste standard lors de la définition d'une liste, mais obtiendrait la nouvelle setitem après replaceref appel.

+0

+1: Solution douce à un problème étrange. Une syntaxe de constructeur légèrement différente ('MyList' ou' [') n'est pas un obstacle à l'acceptation, alors pourquoi s'inquiéter? Mais cela résout. –

+0

Merci pour votre réponse. C'est en effet un problème étrange je suppose :) Idéalement, l'utilisateur de la DSL ne devrait pas avoir à connaître l'implémentation sous-jacente. J'aime bien l'approche. Peut-être y a-t-il un moyen de changer ce que 'alist' pointe sans assignation directe, comme un 'pointeur vers un pointeur' en C? –

+2

Non, je ne pense pas que ce soit possible. La chose la plus proche est probablement de changer le contenu de la variable mutable, par exemple la liste. BTW si vous avez réussi à augmenter les occurrences de la liste avec un nouveau comportement sans changer la classe - ce serait un peu effrayant. S'il y a du code qui utilise type() et isinstance() et autres, il ne remarquerait pas la différence et causerait peut-être des effets secondaires involontaires ... –

2

"Explicit est mieux que implicite." Vous devez simplement documenter que vos utilisateurs doivent utiliser MyList() au lieu de list(). Vous et vos utilisateurs serez plus heureux à long terme.

Ruby permet ce que vous voulez, soit dit en passant. Avec Ruby, vous pouvez ouvrir des objets globaux et ajouter de nouveaux comportements. Je trouve ça effrayant, et Ruby court plus lentement que Python, donc je ne vous invite pas vraiment à passer à Ruby. Donc, fournissez MyList(), et fournissez un moyen de prendre une liste existante et l'envelopper dans MyList().

Vous pouvez écrire MyList() juste prendre un tas d'arguments, et faire une liste très pratique:

class MyList(object): 
    def __init__(self, *args): 
     self.lst = args 
    # add other methods to MyList() here 

alors vous pourriez appeler cela avec MyList(1, 2, 3, 4) et l'instance aurait un membre appelé LST avec la valeur: [1, 2, 3, 4]

Mais maintenant, si vous voulez faire une instance MyList d'une liste, comment faites-vous cela? Si vous faites ceci:

new_list = [1, 3, 5] 
x = MyList(new_list) 

alors vous venez de définir x à: [[1, 3, 5]]

Je vous recommande de faire MyList() juste prendre un argument de la liste et l'enregistrer:

def ensure_list(seq): 
    try: 
     seq[0] 
     return seq 
    except TypeError: 
     return list(seq) 

class MyList(object): 
    def __init__(self, seq): 
     self.lst = ensure_list(seq) 
    # add other methods to MyList() here 

Je viens edited cette réponse. À l'origine, j'ai eu un appel à list() où j'ai maintenant mis un appel à ensure_list(). ensure_list() vérifie si vous pouvez indexer son argument; Si vous le pouvez, il s'agit déjà d'une liste ou de quelque chose qui peut agir comme une liste et elle est retournée telle quelle. Si vous ne pouvez pas l'indexer, alors ensure_list() appelle list(seq) pour essayer de le transformer en liste. Cela fonctionnera avec les itérateurs et les générateurs, les forçant à s'étendre à une liste.

+0

Merci pour votre réponse. Vous avez un point qui rend parfois les choses plus explicites est plus facile à comprendre. Je pense aux conséquences d'un appel de wrapper explicite pour les dicts et les listes. Cependant, je suis toujours curieux de savoir si ce que je demande est possible, étant donné les nombreuses options fournies par Python pour étendre la syntaxe. –

+0

Python n'offre pas vraiment la possibilité de remapper complètement la syntaxe. Par exemple, il est facile de configurer une classe de sorte qu'un membre de la classe soit en lecture seule, et toute tentative d'assignation au membre de la classe échouera et déclenchera une exception; mais il est impossible de faire une simple variable se comporter de la même manière.Vous pourriez faire quelque chose comme 'foo = dict (peu importe); foo = do_magic (foo) 'Et puisque' dict's sont mutables, vous pouvez parcourir toutes les entrées et leur faire quelque chose. Mais il n'y a aucun moyen de relier de façon non-explicite une variable, donc vous ne pouvez pas changer le type d'un objet en cachette. – steveha

Questions connexes