Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Ces trois manières de lier un attribut sont-elles réellement équivalentes ?

11 réponses
Avatar
Francois
Bonjour à tous,

Je me permets de poster ce message car je me perds un peu dans les
différentes manières de lier un attribut d'un objet instance.
Pouvez-vous me dire quelles sont les éventuelles différences entre 1/,
2/ et 3/ ?

1/ obj.x = <valeur>

2/ obj.__dict__['x'] = <valeur>

3/ obj.__setattr__('x', <valeur>)

où <valeur> est une expression quelconque et où l'on suppose que obj est
l'identifiant d'un objet (nouveau style), instance d'une classe C.

Je n'ai pas la réponse bien sûr, même si j'ai l'impression qu'il n'y a
aucune différence entre ces trois moyens. J'ai l'impression (sans
certitude évidemment) que 1/ est *toujours* traduit en interne par 3/
par le compilateur bytecode et que 3/ est par défaut défini comme
strictement équivalent à 2/ sauf si on a pris le soin de redéfinir la
méthode spéciale __setattr__ dans le corps de la classe C . Est-ce
correct ? Pouvez vous m'aider à tirer cela au clair ? Je suis un peu
surpris qu'on puisse faire une même chose de trois manières différentes.

Merci d'avance.


--
François

10 réponses

1 2
Avatar
Pierre Maurette
Francois, le 29/07/2008 a écrit :

[...]

Je suis un peu surpris qu'on puisse faire une
même chose de trois manières différentes.



Vous devriez feuilleter le Kâma Sûtra...

--
Pierre Maurette
Avatar
Kobayashi
Francois a écrit :
Bonjour à tous,

Je me permets de poster ce message car je me perds un peu dans les
différentes manières de lier un attribut d'un objet instance.
Pouvez-vous me dire quelles sont les éventuelles différences entre 1/,
2/ et 3/ ?

1/ obj.x = <valeur>

2/ obj.__dict__['x'] = <valeur>

3/ obj.__setattr__('x', <valeur>)

où <valeur> est une expression quelconque et où l'on suppose que obj est
l'identifiant d'un objet (nouveau style), instance d'une classe C.



Je dirais que seul 2/ permet de "lier un attribut" à une
instance à coup sur. On récupèrera alors la valeur de
l'attribut par "result = obj.__dict__['x']" car
"result = obj.x" est soumis au même conditions que ce
qui vas être dit dans la suite ...

Pour moi, 1/ est une façon équivalente mais
plus concise d'écrire 3/ de même que pour une
fonction f (ou tout autre objet "callable"),
l'écriture f() est équivalente mais plus
concise que f.__call__().

L'appel 1/ ou 3/ implique l'appel à 2/ si
condition1 et condition2 sont vérifiées avec:

condition1 : le comportement par défaut
de __setattr__ n'a pas été changé.

condition2 : la classe C n'a pas d'attribut "x"
ou bien la classe C a un attribut "x" et
cet attribut n'est pas un descripteur

Si la condition 1 n'est pas vraie i.e. si le
comportement de __setattr__ a été modifié, c'est
la nouvelle implémentation qui est appelée

Si la condition 2 n'est pas satisfaite i.e. la
classe C a un descripteur correspondant à x
mais que condition 1 est vraie alors
1/ et 3/ sont équivalents à
getattr(obj.__class__, "x").__set__(obj, <valeur>)

begin{HS}
D'ailleurs, est-ce que quelqu'un connaît une façon
de faire un truc comme isdescriptor(truc) ?
end{HS}

begin{HS2}
Pour l'accès direct à __dict__, il y a surement une
subtilité avec __slots__ mais je n'ai pas encore pris
le temps de regarder ce bidule ...
end{HS2}

PS : Quand je pense que je m'étais dit que j'allais
essayer de répondre simplement ...
Avatar
Francois
Pierre Maurette a écrit :
Je suis un peu surpris qu'on puisse faire une même chose de trois
manières différentes.



Vous devriez feuilleter le Kâma Sûtra...



Merci, ce sera parfait sur les plages cet été. :-))


--
François
Avatar
Francois
Kobayashi a écrit :
1/ obj.x = <valeur>

2/ obj.__dict__['x'] = <valeur>

3/ obj.__setattr__('x', <valeur>)

où <valeur> est une expression quelconque et où l'on suppose que obj
est l'identifiant d'un objet (nouveau style), instance d'une classe C.



Je dirais que seul 2/ permet de "lier un attribut" à une
instance à coup sur. On récupérera alors la valeur de
l'attribut par "result = obj.__dict__['x']" car
"result = obj.x" est soumis au même conditions que ce
qui vas être dit dans la suite ...

Pour moi, 1/ est une façon équivalente mais
plus concise d'écrire 3/ de même que pour une
fonction f (ou tout autre objet "callable"),
l'écriture f() est équivalente mais plus
concise que f.__call__().

L'appel 1/ ou 3/ implique l'appel à 2/ si
condition1 et condition2 sont vérifiées avec:

condition1 : le comportement par défaut
de __setattr__ n'a pas été changé.



Jusque là, cela correspond à ce que je pensais il me semble. Sauf que
moi, je m'étais arrêté à la condition 1. Et d'ailleurs...

condition2 : la classe C n'a pas d'attribut "x"
ou bien la classe C a un attribut "x" et
cet attribut n'est pas un descripteur



... je ne saisis pas trop ce que vient faire cette condition 2 qui a à
voir avec la classe C. Je ne vois pas trop le rapport. Pour moi, si le
comportement de __setattr__ n'a pas été changé dans le corps de la
classe C alors obj.__setattr__('x', <valeur>) devient obj.__dict__['x']
= <valeur> et c'est tout. Mais en réalité je n'ai aucune certitude.

Si la condition 1 n'est pas vraie i.e. si le
comportement de __setattr__ a été modifié, c'est
la nouvelle implémentation qui est appelée



D'accord.

Si la condition 2 n'est pas satisfaite i.e. la
classe C a un descripteur correspondant à x
mais que condition 1 est vraie alors
1/ et 3/ sont équivalents à
getattr(obj.__class__, "x").__set__(obj, <valeur>)



Bon là, je suis perdu, pas sur la logique (là dessus je suis) mais c'est
parce que je ne connais pas vraiment la notion de descripteur.

PS : Quand je pense que je m'étais dit que j'allais
essayer de répondre simplement ...



Mais votre réponse est très logique au contraire. Il me manque des
connaissances pour la comprendre jusqu'au bout. C'est tout. Bien que ne
comprenant pas vraiment la condition 2, j'ai quand même la vague
impression qu'elle n'a pas sa place ici...

Merci beaucoup pour la réponse. :-)


--
François
Avatar
Kobayashi
Francois a écrit :
Kobayashi a écrit :
1/ obj.x = <valeur>

2/ obj.__dict__['x'] = <valeur>

3/ obj.__setattr__('x', <valeur>)

où <valeur> est une expression quelconque et où l'on suppose que obj
est l'identifiant d'un objet (nouveau style), instance d'une classe C.



Je dirais que seul 2/ permet de "lier un attribut" à une
instance à coup sur. On récupérera alors la valeur de
l'attribut par "result = obj.__dict__['x']" car
"result = obj.x" est soumis au même conditions que ce
qui vas être dit dans la suite ...

Pour moi, 1/ est une façon équivalente mais
plus concise d'écrire 3/ de même que pour une
fonction f (ou tout autre objet "callable"),
l'écriture f() est équivalente mais plus
concise que f.__call__().

L'appel 1/ ou 3/ implique l'appel à 2/ si
condition1 et condition2 sont vérifiées avec:

condition1 : le comportement par défaut
de __setattr__ n'a pas été changé.



Jusque là, cela correspond à ce que je pensais il me semble. Sauf que
moi, je m'étais arrêté à la condition 1. Et d'ailleurs...

condition2 : la classe C n'a pas d'attribut "x"
ou bien la classe C a un attribut "x" et
cet attribut n'est pas un descripteur



... je ne saisis pas trop ce que vient faire cette condition 2 qui a à
voir avec la classe C. Je ne vois pas trop le rapport. Pour moi, si le
comportement de __setattr__ n'a pas été changé dans le corps de la
classe C alors obj.__setattr__('x', <valeur>) devient obj.__dict__['x']
= <valeur> et c'est tout. Mais en réalité je n'ai aucune certitude.



Pour un peu de lecture sur les descripteurs, j'ai beaucoup appris
avec http://www.cafepy.com/article/python_attributes_and_methods
et http://www.python.org/doc/newstyle onglet "How-To Guide for
Descriptors" semble très bien aussi.

Revenons à notre "obj.x = <valeur>". Cette écriture est
équivalente à "obj.__setattr__('x', <valeur>)" qui est
elle même équivalente à "obj.__setattr__.__call__('x', <valeur>)".
Jusqu'ici, tout va bien.

On peut ré-écrire
# --
obj.__setattr__.__call__('x', <valeur>)
# --
en
# --
accessor = obj.__setattr__
accessor.__call__('x', <valeur>)
# --

Sachant qu'une fonction est un descripteur :),
accessor = obj.__setattr__ est équivalent à
accessor = obj.__dict__["__setattr__"].__get__(obj, C)

où l'on constate que accessor est un object
"<bound method C.__setattr__ of obj>" qui a une méthode
__call__ (c'est par ce mécanisme que je comprend
comment on passe d'une fonction à une méthode "bound" ou
"unbound")

Or, l'implémentation du __call__ de la "bound method" est
"fonction.__call__(obj, ...)" après avoir vérifié que
obj est une instance de C.

Ce qui implique que "obj.x = <valeur>" est équivalent à
C.__dict__["__setattr__"].__call__(obj, "x", <valeur>)

Maintenant, il faut savoir que l'implémentation par
défaut de setattr est un peu spéciale. Si je devais
l'émuler, je dirais qu'elle s'écrit

def __setattr__(self, name, value):
cls = type(self)
if hasattr(cls, name):
attr = getattr(cls, name)
if isdescriptor(attr):
attr.__set__(self, value)
return
pass
self.__dict__[name] = value
return

D'où le HS1 de mon message précédent, je ne sais
pas comment en interne, python fait "isdescriptor"
car le fait d'avoir "__get__" et "__set__" ne suffit
pas, il faut aussi être une instance d'une new style
classe ...

On voit également que dans l'appel
attr.__set__(self, value), on a perdu la
référence à name ce qui implique que
l'attribut name doit être stocké dans
le descripteur si on veut pouvoir parfaitement
reproduire le comportement par défaut.

Bon voila, c'est comme cela que je comprend,
personnellement, les descripteurs ... en espérant
n'avoir pas dit trop de bêtises !


Si la condition 1 n'est pas vraie i.e. si le
comportement de __setattr__ a été modifié, c'est
la nouvelle implémentation qui est appelée



D'accord.

Si la condition 2 n'est pas satisfaite i.e. la
classe C a un descripteur correspondant à x
mais que condition 1 est vraie alors
1/ et 3/ sont équivalents à
getattr(obj.__class__, "x").__set__(obj, <valeur>)



Bon là, je suis perdu, pas sur la logique (là dessus je suis) mais c'est
parce que je ne connais pas vraiment la notion de descripteur.

PS : Quand je pense que je m'étais dit que j'allais
essayer de répondre simplement ...



Mais votre réponse est très logique au contraire. Il me manque des
connaissances pour la comprendre jusqu'au bout. C'est tout. Bien que ne
comprenant pas vraiment la condition 2, j'ai quand même la vague
impression qu'elle n'a pas sa place ici...

Merci beaucoup pour la réponse. :-)




Avatar
Boris Borcic
Francois wrote:
Bonjour à tous,

Je me permets de poster ce message car je me perds un peu dans les
différentes manières de lier un attribut d'un objet instance.
Pouvez-vous me dire quelles sont les éventuelles différences entre 1/,
2/ et 3/ ?

1/ obj.x = <valeur>

2/ obj.__dict__['x'] = <valeur>

3/ obj.__setattr__('x', <valeur>)



Noter que 2/ seul autorise

>>> class Bar(object) : pass
>>> bar = Bar()
>>> bar.__dict__[666] = "Diable !"
>>> dir(bar)
[666, '__class__', '__delattr__', '__dict__', '__doc__', '__getattribute_ _',
'__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ ex__',
'__repr__', '__setattr__', '__str__', '__weakref__']
Avatar
Francois
Désolé pour ma réponse tardive, j'étais parti loin d'internet...

Kobayashi a écrit :

Pour un peu de lecture sur les descripteurs, j'ai beaucoup appris
avec http://www.cafepy.com/article/python_attributes_and_methods
et http://www.python.org/doc/newstyle onglet "How-To Guide for
Descriptors" semble très bien aussi.



Merci beaucoup pour les liens. Le premier notamment m'a l'air très
instructif.

Pour la suite de la réponse, j'avoue avoir un peu de mal. Je crois qu'il
faut d'abord que je me documente encore.

Merci pour ton aide.


--
François
Avatar
Méta-MCI \(MVP\)
Avatar
Méta-MCI \(MVP\)
Bonjour !

Noter que 2/ seul autorise



Exact.

Et aussi, le 1/ ne sait pas traiter les caractères non ASCII (accents,
par exemple) :
bar.__dict__['noël']= '25 décembre'
print dir(bar)
print bar.noël


@-salutations
--
Michel Claveau
Avatar
Francois
Méta-MCI (MVP) a écrit :
Bonjour !

Noter que 2/ seul autorise



Exact.

Et aussi, le 1/ ne sait pas traiter les caractères non ASCII (accents,
par exemple) :
bar.__dict__['noël']= '25 décembre'
print dir(bar)
print bar.noël




Effectivement, mais quand je faisais référence à obj.x, pour moi obj et
x représentent des noms de variables valides, c'est déjà assez
suffisant. :-)

J'ai essayé de me documenter un peu, et il me semble que c'est vraiment
les descripteurs qui complexifient les choses. J'avoue avoir du mal à
comprendre l'intérêt des descripteurs, je pense que ça doit être réservé
à de la programmation un peu exotique.

Finalement, si on laisse de côté les descripteurs, je pense que les
choses se passent ainsi (c'est plutôt simple) :

Si obj fait référence à un objet quelconque et x est un nom de variable
valide, alors obj.x = expr (où expr est une expression quelconque) n'est
possible que si type(obj) possède la méthode __setattr__.

Dans ce cas, si x ne fait pas référence à un descripteur, obj.x = expr
est toujours intercepté par la méthode __setattr__ et donc

#--------------
obj.x = expr
#--------------

devient

#--------------
obj.__setattr__(x,expr)
(ou type(obj).__setattr__(obj,x,expr) qui lui est équivalent).
#--------------

1) Si le programmeur, n'a pas défini la méthode __setattr__ , par défaut
l'implémentation de __setattr__ fait que

#--------------
obj.__setattr__(x,expr)
#--------------

sera complètement équivalent à

#--------------
obj.__dict__('x') = expr
#--------------

Donc, dans ce cas, obj.x = expr équivaut à obj.__dict__('x') = expr
(sauf pour x égal à __name__, __dict__, __bases__ [si obj est une
classe] ou __class__ [si obj est une instance], mais c'est un détail)

2) Si le programmeur a pris le soin de définir la méthode __setattr__,
alors bien bien sûr c'est cette définition qui s'applique.

Voila.


--
François
1 2