L'objectif est de créer une classe fictive qui se comporte comme un ensemble de résultats de base de données.
Ainsi, par exemple, si une requête de base de données renvoie, à l'aide d'une expression dict, {'ab':100, 'cd':200}
, j'aimerais voir:
>>> dummy.ab
100
Au début, j'ai pensé que je pourrais peut-être 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 __== "__main__":
c = C(ks, vs)
print c.ab
mais c.ab
renvoie un objet de propriété à la place.
Remplacer la ligne setattr
par k = property(lambda x: vs[i])
n’est d'aucune utilité.
Alors, quel est le bon moyen de créer une propriété d'instance au moment de l'exécution?
P.S. Je connais une alternative présentée dans Comment utilise-t-on la méthode __getattribute__
?
Je suppose que je devrais développer cette réponse, maintenant que je suis plus vieux et plus sage et que je sais ce qui se passe. Mieux vaut tard que jamais.
Vous pouvez ajouter une propriété à une classe de manière dynamique. Mais c'est le piège: 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
property
est en réalité une simple implémentation d'une chose appelée descripteur . C'est un objet qui fournit un traitement personnalisé pour un attribut donné, sur une classe donnée. Un peu comme un moyen de factoriser un énorme arbre if
à partir de __getattribute__
.
Quand je demande foo.b
dans l'exemple ci-dessus, Python voit que le b
défini sur la classe implémente le protocole descriptor, ce qui signifie simplement que c'est un objet avec une méthode __get__
, __set__
ou __delete__
. Le descripteur revendique le traitement de cet attribut. Python appelle donc Foo.b.__get__(foo, Foo)
et la valeur de retour vous est renvoyée en tant que valeur de l'attribut. Dans le cas de property
, chacune de ces méthodes appelle simplement le fget
, fset
ou fdel
que vous avez transmis au constructeur property
.
Les descripteurs sont vraiment la manière dont Python expose la plomberie de toute son implémentation OO. En fait, il existe un autre type de descripteur encore plus commun que 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>
La méthode humble n'est qu'un autre type de descripteur. Son __get__
pointe sur l'instance appelante en tant que premier argument; en effet, il fait ceci:
def __get__(self, instance, owner):
return functools.partial(self.function, instance)
Quoi qu’il en soit, je suppose que c’est la raison pour laquelle les descripteurs ne fonctionnent que sur les classes: c’est une formalisation de la matière qui alimente les classes en premier lieu. Ils font même exception à la règle: vous pouvez évidemment affecter des descripteurs à une classe, et les classes sont elles-mêmes des instances de type
! En fait, essayer de lire Foo.b
appelle toujours property.__get__
; il est juste idiomatique que les descripteurs se retournent lorsqu'ils sont accédés en tant qu'attributs de classe.
Je pense que le fait que presque tout le système OO de Python puisse être exprimé en Python est vraiment génial. :)
Oh, et j’ai écrit un commentaire de blog) sur les descripteurs il ya quelque temps si vous êtes intéressé.
L'objectif est de créer une classe fictive qui se comporte comme un ensemble de résultats de base de données.
Vous voulez donc un dictionnaire dans lequel vous pouvez épeler un ['b'] comme un.b?
C'est facile:
class atdict(dict):
__getattr__= dict.__getitem__
__setattr__= dict.__setitem__
__delattr__= dict.__delitem__
Il semble que vous puissiez résoudre ce problème beaucoup plus simplement avec un namedtuple
, puisque vous connaissez la liste complète 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 devez absolument écrire votre propre compositeur, vous devrez faire la métaprogrammation au niveau de la classe. property()
ne fonctionne pas sur les instances.
Vous n'avez pas besoin d'utiliser une propriété pour cela. Remplacez simplement __setattr__
pour les rendre en lecture seule.
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 is 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 is read only!
>>> c.a = 'blah'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
Exception: It is read only!
Comment ajouter des propriétés à une classe python de manière dynamique?
Supposons que vous souhaitiez ajouter une propriété à un objet. En règle générale, je souhaite utiliser des propriétés lorsque je dois commencer à gérer l'accès à un attribut dans un code utilisé en aval, afin de pouvoir conserver une API cohérente. Maintenant, je vais généralement les ajouter au code source où l'objet est défini, mais supposons que vous ne disposez pas de cet accès, ou que vous deviez choisir réellement de manière dynamique vos fonctions par programme.
En utilisant un exemple basé sur la documentation pour property
, créons une classe d'objet avec un attribut "hidden" et créons une instance de celle-ci:
class C(object):
'''basic class'''
_x = None
o = C()
En Python, nous nous attendons à ce qu'il y ait une façon évidente de faire les choses. Cependant, dans ce cas, je vais montrer deux manières: avec la notation de décorateur, et sans. Tout d'abord, sans notation décorative. Cela peut être plus utile pour l'affectation dynamique de getters, setters ou deleters.
Créons-en pour notre classe:
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
Et maintenant, nous les attribuons à la propriété. Notez que nous pourrions choisir nos fonctions par programmation ici, en répondant à la question dynamique:
C.x = property(getx, setx, delx, "I'm the 'x' property.")
Et utilisation:
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
>>> help(C.x)
Help on property:
I'm the 'x' property.
Nous pourrions faire la même chose que ci-dessus avec la notation décorateur, mais dans ce cas, nous devons nommer les méthodes de la même façon (et je recommanderais de le conserver de la même manière que l'attribut). pas aussi trivial que d'utiliser la méthode ci-dessus:
@property
def x(self):
'''I'm the 'x' property.'''
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
Et affectez l'objet de propriété avec ses setters et deleters fournis à la classe:
C.x = x
Et utilisation:
>>> help(C.x)
Help on property:
I'm the 'x' property.
>>> o.x
>>> o.x = 'foo'
>>> o.x
'foo'
>>> del o.x
>>> print(o.x)
None
J'ai posé une question similaire sur cet article de Stack Overflow pour créer une fabrique de classes créant des types simples. Le résultat était cette réponse qui avait une version de travail de la classe factory . 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 pouvez utiliser une variante de ceci pour créer des valeurs par défaut qui sont votre objectif (il y a aussi une réponse dans cette question qui traite de cela).
Vous ne pouvez pas ajouter une nouvelle property()
à une instance au moment de l'exécution, car les propriétés sont des descripteurs de données. Au lieu de cela, vous devez créer dynamiquement une nouvelle classe ou surcharger __getattribute__
afin de traiter les descripteurs de données sur les instances.
Pour ceux qui viennent de moteurs de recherche, voici les deux choses que je cherchais quand je parlais de dynamic properties:
class Foo:
def __init__(self):
# we can dynamically have access to the properties dict using __dict__
self.__dict__['foo'] = 'bar'
assert Foo().foo == 'bar'
# or we can use __getattr__ and __setattr__ to execute code on set/get
class Bar:
def __init__(self):
self._data = {}
def __getattr__(self, key):
return self._data[key]
def __setattr__(self, key, value):
self._data[key] = value
bar = Bar()
bar.foo = 'bar'
assert bar.foo == 'bar'
__dict__
est utile si vous souhaitez définir des propriétés créées dynamiquement. __getattr__
est utile pour ne faire quelque chose que lorsque la valeur est requise, comme interroger une base de données. Le combo set/get est bien pour simplifier l'accès aux données stockées dans la classe (comme dans l'exemple ci-dessus).
Si vous ne voulez qu'une propriété dynamique, jetez un coup d'œil à la fonction intégrée property () .
Je ne suis pas sûr de bien comprendre la question, mais vous pouvez modifier les propriétés de l'instance lors de l'exécution à l'aide du __dict__
intégré de votre classe:
class C(object):
def __init__(self, ks, vs):
self.__dict__ = dict(Zip(ks, vs))
if __== "__main__":
ks = ['ab', 'cd']
vs = [12, 34]
c = C(ks, vs)
print(c.ab) # 12
Le meilleur moyen d’y parvenir est de définir __slots__
. Ainsi, vos instances ne peuvent pas avoir de nouveaux attributs.
ks = ['ab', 'cd']
vs = [12, 34]
class C(dict):
__slots__ = []
def __init__(self, ks, vs): self.update(Zip(ks, vs))
def __getattr__(self, key): return self[key]
if __== "__main__":
c = C(ks, vs)
print c.ab
Cela imprime 12
c.ab = 33
Cela donne: AttributeError: 'C' object has no attribute 'ab'
Juste un autre exemple pour obtenir l'effet désiré
class Foo(object):
_bar = None
@property
def bar(self):
return self._bar
@bar.setter
def bar(self, value):
self._bar = value
def __init__(self, dyn_property_name):
setattr(Foo, dyn_property_name, Foo.bar)
Alors maintenant, nous pouvons faire des choses comme:
>>> foo = Foo('baz')
>>> foo.baz = 5
>>> foo.bar
5
>>> foo.baz
5
C'est un peu différent de ce que voulait OP, mais j'ai secoué mon cerveau jusqu'à ce que je trouve une solution efficace, alors je le mets ici pour le prochain gars/fille
J'avais besoin d'un moyen de spécifier des setters et des getters dynamiques.
class X:
def __init__(self, a=0, b=0, c=0):
self.a = a
self.b = b
self.c = c
@classmethod
def _make_properties(cls, field_name, inc):
_inc = inc
def _get_properties(self):
if not hasattr(self, '_%s_inc' % field_name):
setattr(self, '_%s_inc' % field_name, _inc)
inc = _inc
else:
inc = getattr(self, '_%s_inc' % field_name)
return getattr(self, field_name) + inc
def _set_properties(self, value):
setattr(self, '_%s_inc' % field_name, value)
return property(_get_properties, _set_properties)
Je connais mes champs à l'avance, alors je vais créer mes propriétés. NOTE: vous ne pouvez pas faire cette instance PER, ces propriétés existeront sur la classe !!!
for inc, field in enumerate(['a', 'b', 'c']):
setattr(X, '%s_summed' % field, X._make_properties(field, inc))
Essayons tout cela maintenant ..
x = X()
assert x.a == 0
assert x.b == 0
assert x.c == 0
assert x.a_summed == 0 # enumerate() set inc to 0 + 0 = 0
assert x.b_summed == 1 # enumerate() set inc to 1 + 0 = 1
assert x.c_summed == 2 # enumerate() set inc to 2 + 0 = 2
# we set the variables to something
x.a = 1
x.b = 2
x.c = 3
assert x.a_summed == 1 # enumerate() set inc to 0 + 1 = 1
assert x.b_summed == 3 # enumerate() set inc to 1 + 2 = 3
assert x.c_summed == 5 # enumerate() set inc to 2 + 3 = 5
# we're changing the inc now
x.a_summed = 1
x.b_summed = 3
x.c_summed = 5
assert x.a_summed == 2 # we set inc to 1 + the property was 1 = 2
assert x.b_summed == 5 # we set inc to 3 + the property was 2 = 5
assert x.c_summed == 8 # we set inc to 5 + the property was 3 = 8
Est-ce déroutant? Oui, désolé, je ne pourrais pas vous donner d'exemples significatifs du monde réel. En outre, ce n'est pas pour le cœur léger.
Cela semble fonctionner (mais voir ci-dessous):
class data(dict,object):
def __init__(self,*args,**argd):
dict.__init__(self,*args,**argd)
self.__dict__.update(self)
def __setattr__(self,name,value):
raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
def __delattr__(self,name):
raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
Si vous avez besoin d'un comportement plus complexe, n'hésitez pas à modifier votre réponse.
Les éléments suivants seraient probablement plus efficaces en termes de mémoire pour les grands ensembles de données:
class data(dict,object):
def __init__(self,*args,**argd):
dict.__init__(self,*args,**argd)
def __getattr__(self,name):
return self[name]
def __setattr__(self,name,value):
raise AttributeError,"Attribute '%s' of '%s' object cannot be set"%(name,self.__class__.__name__)
def __delattr__(self,name):
raise AttributeError,"Attribute '%s' of '%s' object cannot be deleted"%(name,self.__class__.__name__)
Pour répondre à l'essentiel de votre question, vous voulez un attribut en lecture seule à partir d'un dict en tant que source de données immuable:
L'objectif est de créer une classe fictive qui se comporte comme un ensemble de résultats de base de données.
Ainsi, par exemple, si une requête de base de données renvoie, à l'aide d'une expression dict,
{'ab':100, 'cd':200}
, alors je verrais>>> dummy.ab 100
Je vais vous montrer comment utiliser une namedtuple
du module collections
pour accomplir ceci:
import collections
data = {'ab':100, 'cd':200}
def maketuple(d):
'''given a dict, return a namedtuple'''
Tup = collections.namedtuple('TupName', d.keys()) # iterkeys in Python2
return Tup(**d)
dummy = maketuple(data)
dummy.ab
renvoie 100
Vous pouvez utiliser le code suivant pour mettre à jour les attributs de classe à l'aide d'un objet dictionnaire:
class ExampleClass():
def __init__(self, argv):
for key, val in argv.items():
self.__dict__[key] = val
if __== '__main__':
argv = {'intro': 'Hello World!'}
instance = ExampleClass(argv)
print instance.intro
Bien que de nombreuses réponses soient données, je ne pouvais pas en trouver une qui me satisfasse. J'ai imaginé ma propre solution qui permet à property
de fonctionner pour le cas dynamique. La source pour répondre à la question initiale:
#!/usr/local/bin/python3
INITS = { 'ab': 100, 'cd': 200 }
class DP(dict):
def __init__(self):
super().__init__()
for k,v in INITS.items():
self[k] = v
def _dict_set(dp, key, value):
dp[key] = value
for item in INITS.keys():
setattr(
DP,
item,
lambda key: property(
lambda self: self[key], lambda self, value: _dict_set(self, key, value)
)(item)
)
a = DP()
print(a) # {'ab': 100, 'cd': 200}
a.ab = 'ab100'
a.cd = False
print(a.ab, a.cd) # ab100 False
Quelque chose qui fonctionne pour moi est la suivante:
class C:
def __init__(self):
self._x=None
def g(self):
return self._x
def s(self, x):
self._x = x
def d(self):
del self._x
def s2(self,x):
self._x=x+x
x=property(g,s,d)
c = C()
c.x="a"
print(c.x)
C.x=property(C.g, C.s2)
C.x=C.x.deleter(C.d)
c2 = C()
c2.x="a"
print(c2.x)
Sortie
a
aa
Extension de l'idée de kjfletch
# This is my humble contribution, extending the idea to serialize
# data from and to tuples, comparison operations and allowing functions
# as default values.
def Struct(*args, **kwargs):
FUNCTIONS = (types.BuiltinFunctionType, types.BuiltinMethodType, \
types.FunctionType, types.MethodType)
def init(self, *iargs, **ikwargs):
"""Asume that unamed args are placed in the same order than
astuple() yields (currently alphabetic order)
"""
kw = list(self.__slots__)
# set the unnamed args
for i in range(len(iargs)):
k = kw.pop(0)
setattr(self, k, iargs[i])
# set the named args
for k, v in ikwargs.items():
setattr(self, k, v)
kw.remove(k)
# set default values
for k in kw:
v = kwargs[k]
if isinstance(v, FUNCTIONS):
v = v()
setattr(self, k, v)
def astuple(self):
return Tuple([getattr(self, k) for k in self.__slots__])
def __str__(self):
data = ['{}={}'.format(k, getattr(self, k)) for k in self.__slots__]
return '<{}: {}>'.format(self.__class__.__name__, ', '.join(data))
def __repr__(self):
return str(self)
def __eq__(self, other):
return self.astuple() == other.astuple()
name = kwargs.pop("__name__", "MyStruct")
slots = list(args)
slots.extend(kwargs.keys())
# set non-specific default values to None
kwargs.update(dict((k, None) for k in args))
return type(name, (object,), {
'__init__': init,
'__slots__': Tuple(slots),
'astuple': astuple,
'__str__': __str__,
'__repr__': __repr__,
'__eq__': __eq__,
})
Event = Struct('user', 'cmd', \
'arg1', 'arg2', \
date=time.time, \
__name__='Event')
aa = Event('pepe', 77)
print(aa)
raw = aa.astuple()
bb = Event(*raw)
print(bb)
if aa == bb:
print('Are equals')
cc = Event(cmd='foo')
print(cc)
Sortie:
<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
<Event: user=pepe, cmd=77, arg1=None, arg2=None, date=1550051398.3651814>
Are equals
<Event: user=None, cmd=foo, arg1=None, arg2=None, date=1550051403.7938335>
class atdict(dict):
def __init__(self, value, **kwargs):
super().__init__(**kwargs)
self.__dict = value
def __getattr__(self, name):
for key in self.__dict:
if type(self.__dict[key]) is list:
for idx, item in enumerate(self.__dict[key]):
if type(item) is dict:
self.__dict[key][idx] = atdict(item)
if type(self.__dict[key]) is dict:
self.__dict[key] = atdict(self.__dict[key])
return self.__dict[name]
d1 = atdict({'a' : {'b': [{'c': 1}, 2]}})
print(d1.a.b[0].c)
Et le résultat est:
>> 1