291 votes

Comment faire pour ajouter une propriété à un python classe dynamiquement?

L'objectif est de créer une maquette de la classe qui se comporte comme une base de jeu de résultats.

Ainsi, par exemple, si une requête de base de données renvoie, à l'aide d'un dict expression, {"ab": 100, "cd": 200}, alors je voudrais voir

>>> dummy.ab
100

Donc, au début je pensais peut-être en mesure de le faire de cette façon

ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
    def __init__(self, ks, vs):
        for i, k in enumerate(ks):
                self[k] = vs[i]
                setattr(self, k, property(lambda x: vs[i], self.fn_readyonly))

    def fn_readonly(self, v)
        raise "It is ready only"

if __name__ == "__main__":
    c = C(ks, vs)
    print c.ab

mais "c.ab" renvoie un objet de propriété plutôt.

Remplacer le setattr ligne avec

k = property(lambda x: vs[i])

Il n'est d'aucune utilité.

Alors, quelle est la bonne façon de créer une instance de la propriété lors de l'exécution?

P. S. je suis conscient de l'alternative ici

432voto

Eevee Points 18333

Je suppose que je dois développer cette réponse, maintenant que je suis plus vieux et plus sage et de savoir ce qu'il se passe. Mieux vaut tard que jamais.

Vous pouvez ajouter une propriété à une classe dynamiquement. Mais voilà le hic: vous devez l'ajouter à la classe.

>>> class Foo(object):
...     pass
... 
>>> foo = Foo()
>>> foo.a = 3
>>> Foo.b = property(lambda self: self.a + 1)
>>> foo.b
4

Un property est en fait une simple mise en œuvre d'une chose qui s'appelle un descripteur. C'est un objet qui fournit une gestion personnalisée pour un attribut donné, sur une classe donnée. Un peu comme une sorte de facteur énorme if arbre de __getattribute__.

Quand je demande, foo.b dans l'exemple ci-dessus, Python voit que l' b définie sur la classe implémente le protocole descripteur-ce qui signifie simplement qu'il est un objet avec un __get__, __set__ou __delete__ méthode. Le descripteur revendique la responsabilité pour la manipulation de cet attribut, donc Python appels Foo.b.__get__(foo, Foo), et la valeur de retour est de nouveau transmis à vous en tant que valeur de l'attribut. Dans le cas d' property, chacune de ces méthodes se contente d'appeler l' fget, fsetou fdel vous avez passé à l' property constructeur.

Les descripteurs sont vraiment Python voie d'exposition de la plomberie, de l'ensemble de OO mise en œuvre. En fait, il y a un autre type de descripteur bien plus fréquente qu' property.

>>> class Foo(object):
...     def bar(self):
...         pass
... 
>>> Foo().bar
<bound method Foo.bar of <__main__.Foo object at 0x7f2a439d5dd0>>
>>> Foo().bar.__get__
<method-wrapper '__get__' of instancemethod object at 0x7f2a43a8a5a0>

L'humble méthode est juste un autre type de descripteur. Ses __get__ de punaises sur la convocation de l'instance en tant que premier argument; en effet, il fait ceci:

def __get__(self, instance, owner):
    return functools.partial(self.function, instance)

De toute façon, je suppose que c'est pourquoi les descripteurs de travailler uniquement sur les classes: ils sont une formalisation des choses que les pouvoirs de classes dans la première place. Ils sont encore l'exception à la règle: vous pouvez évidemment attribuer des descripteurs à une classe, et les classes sont elles-mêmes des instances de type! En fait, en essayant de lire en Foo.b toujours property.__get__; c'est juste idiomatiques pour les descripteurs de retourner eux-mêmes lors de l'accès comme les attributs de classe.

Je pense que c'est assez cool que la presque totalité de Python OO système peut être exprimée en Python. :)

Oh, et j'ai écrit un long billet de blog sur les descripteurs d'un moment de retour si vous êtes intéressé.

67voto

bobince Points 270740

L'objectif est de créer une maquette de la classe qui se comporte comme une base de jeu de résultats.

Donc, ce que vous voulez, c'est un dictionnaire où vous pouvez épeler un['b'].b?

C'est simple:

class atdict(dict):
    __getattr__= dict.__getitem__
    __setattr__= dict.__setitem__
    __delattr__= dict.__delitem__

45voto

Ryan Points 7423

Vous n'avez pas besoin d'utiliser une propriété pour. Substituer __setattr__ à lire seulement.

class C(object):
    def __init__(self, keys, values):
        for (key, value) in zip(keys, values):
            self.__dict__[key] = value

    def __setattr__(self, name, value):
        raise Exception("It's read only!")

Tada.

>>> c = C('abc', [1,2,3])
>>> c.a
1
>>> c.b
2
>>> c.c
3
>>> c.d
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'C' object has no attribute 'd'
>>> c.d = 42
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It's read only!
>>> c.a = 'blah'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in __setattr__
Exception: It's read only!

40voto

Eevee Points 18333

Il semble que vous pourriez résoudre ce problème beaucoup plus simplement avec un namedtuple, puisque vous savez que l'ensemble de la liste des champs à l'avance.

from collections import namedtuple

Foo = namedtuple('Foo', ['bar', 'quux'])

foo = Foo(bar=13, quux=74)
print foo.bar, foo.quux

foo2 = Foo()  # error

Si vous avez absolument besoin d'écrire votre propre setter, vous aurez à faire de la métaprogrammation au niveau de la classe; property() ne fonctionne pas sur les instances.

5voto

kjfletch Points 2913

J'ai demandé à un de même la question sur ce Débordement de Pile post pour créer une fabrique de classe qui a créé les types simples. Le résultat est cette réponse qui avait une version de travail de la fabrique de classe. Voici un extrait de la réponse:

def Struct(*args, **kwargs):
    def init(self, *iargs, **ikwargs):
        for k,v in kwargs.items():
            setattr(self, k, v)
        for i in range(len(iargs)):
            setattr(self, args[i], iargs[i])
        for k,v in ikwargs.items():
            setattr(self, k, v)

    name = kwargs.pop("name", "MyStruct")
    kwargs.update(dict((k, None) for k in args))
    return type(name, (object,), {'__init__': init, '__slots__': kwargs.keys()})

>>> Person = Struct('fname', 'age')
>>> person1 = Person('Kevin', 25)
>>> person2 = Person(age=42, fname='Terry')
>>> person1.age += 10
>>> person2.age -= 10
>>> person1.fname, person1.age, person2.fname, person2.age
('Kevin', 35, 'Terry', 32)
>>>

Vous pourriez utiliser une variante de cette de créer des valeurs par défaut qui est de votre objectif (il est aussi une réponse à la question qui lui est consacré).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X