51 votes

Comment une fonction peut-elle accéder à ses propres attributs ?

est-il possible d'accéder aux attributs des objets de la fonction python à partir de la portée de la fonction ?

Par exemple, prenons

def f():
    return SOMETHING

f._x = "foo"
f()           # -> "foo"

maintenant, que doit être QUELQUE CHOSE, si nous voulons que le contenu de l'attribut _x "foo" soit retourné ? si c'est même possible (simplement)

merci

UPDATE :

J'aimerais également les travaux suivants :

g = f
del f
g()          # -> "foo"

UPDATE 2 :

Dire que ce n'est pas possible (si c'est le cas), et pourquoi, est plus satisfaisant que de fournir un moyen de le simuler, par exemple avec un objet différent d'une fonction.

3 votes

Qu'est-ce qui vous empêche d'avoir simplement une fonction avec un seul paramètre ?

3 votes

Space_C0wb0y : les paramètres des fonctions sont hors sujet, c'est une question sur la théorie, pas sur les solutions pragmatiques de la vie réelle.

2 votes

+1 pour m'avoir fait explorer (et apprendre) ce coin des internes de python ;-)

61voto

Mark Lodato Points 6548

Solution

Faire en sorte que l'un des arguments par défaut de la fonction soit une référence à la fonction elle-même.

def f(self):
    return self.x
f.func_defaults = (f,)

Exemple d'utilisation :

>>> f.x = 17
>>> b = f
>>> del f
>>> b()
17

Explication

L'expéditeur initial souhaitait une solution qui ne nécessite pas la recherche d'un nom global. La solution simple

def f():
    return f.x

effectue une recherche de la variable globale f sur chaque appel, ce qui ne répond pas aux exigences. Si f est supprimé, alors la fonction échoue. La méthode la plus compliquée inspect La proposition échoue de la même manière.

Ce que nous voulons, c'est réaliser liaison précoce et stocke la référence liée dans l'objet lui-même. Voici ce que nous faisons de manière conceptuelle :

def f(self=f):
    return self.x

Dans le ci-dessus, self est une variable locale, donc aucune recherche globale n'est effectuée. Cependant, nous ne pouvons pas écrire le code tel quel, parce que f n'est pas encore défini lorsque nous essayons de lier la valeur par défaut de l'élément self à ce sujet. Au lieu de cela, nous définissons la valeur par défaut après f est défini.

Décorateur

Voici un décorateur simple pour faire cela pour vous. Notez que le self l'argument doit venir en dernier, contrairement aux méthodes, où self vient en premier. Cela signifie également que vous devez donner une valeur par défaut si l'un de vos autres arguments prend une valeur par défaut.

def self_reference(f):
    f.func_defaults = f.func_defaults[:-1] + (f,)
    return f

@self_reference
def foo(verb, adverb='swiftly', self=None):
    return '%s %s %s' % (self.subject, verb, adverb)

Exemple :

>>> foo.subject = 'Fred'
>>> bar = foo
>>> del foo
>>> bar('runs')
'Fred runs swiftly'

1 votes

Cela ressemble à un décorateur de fermeture. Je l'aime bien.

0 votes

Je ne suis pas un grand fan de cette approche mais je ne peux pas expliquer pourquoi. Pour d'autres approches, voir cette réponse a "Variable statique en Python ?" .

7 votes

Peut-on ajouter que f.func_defaults a été renommé en f.__defaults__ dans Python 3 par souci de cohérence ? De même, le les doutes sont partagés par d'autres

28voto

gnibbler Points 103484

Vous pourriez simplement utiliser une classe pour faire cela

>>> class F(object):
...     def __call__(self, *args, **kw):
...         return self._x
... 
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'

6 votes

C'est ce que j'utiliserais dans un scénario réel

17voto

Nas Banov Points 7293

Eh bien, regardons ce qu'est la fonction :

>>> def foo():
...     return x
... 
>>> foo.x = 777
>>> foo.x
777
>>> foo()
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 2, in foo
NameError: global name 'x' is not defined
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', 
'__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', 
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 
'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 
'func_globals', 'func_name', 'x']
>>> getattr(foo, 'x')
777

Aha ! Donc, l'attribut a été ajouté à l'objet fonction mais il ne le verra pas parce qu'il recherche l'attribut global. x à la place.

Nous pouvons essayer de saisir le cadre de l'exécution de la fonction et essayer de voir ce qu'il y a là (essentiellement ce qu'Anthony Kong a suggéré mais sans inspect ) :

>>> def foo():
...     import sys
...     return sys._getframe()
... 
>>> fr = foo()
>>> dir(fr)
['__class__', '__delattr__', '__doc__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'f_back', 'f_builtins', 'f_code', 'f_exc_traceback', 'f_exc_type', 'f_exc_value', 'f_globals', 'f_lasti', 'f_lineno', 'f_locals', 'f_restricted', 'f_trace']
>>> fr.f_locals
{'sys': <module 'sys' (built-in)>}
>>> fr.f_code
<code object foo at 01753020, file "<interactive input>", line 1>
>>> fr.f_code.co_code
'd\x01\x00d\x00\x00k\x00\x00}\x00\x00|\x00\x00i\x01\x00\x83\x00\x00S'
>>> fr.f_code.co_name
'foo'

Aha ! Alors peut-être pouvons-nous obtenir le nom de la fonction à partir du nom du bloc de code et ensuite chercher l'attribut de manière détournée ? Tout à fait :

>>> getattr(fr.f_globals[fr.f_code.co_name], 'x')
777
>>> fr.f_globals[fr.f_code.co_name].x
777
>>> def foo():
...     import sys
...     frm = sys._getframe()
...     return frm.f_globals[frm.f_code.co_name].x
... 
>>> foo.x=777
>>> foo()
777

C'est génial ! Mais supporterait-il le renommage et la suppression de la fonction originale ?

>>> g = foo
>>> g.func_name
'foo'
>>> g.func_code.co_name
'foo'

Ah, très douteux. L'objet fonction et son objet code insistent toujours sur le fait qu'ils sont appelés foo . Bien sûr, c'est là que ça casse :

>>> g.x
777
>>> g.x=888
>>> foo.x
888
>>> g()
888
>>> del foo
>>> g()
Traceback (most recent call last):
  File "<interactive input>", line 1, in <module>
  File "<interactive input>", line 4, in foo
KeyError: 'foo'

Dang ! Donc, en général, cela ne peut pas être fait par introspection via les cadres d'exécution. Le problème semble être qu'il y a une différence entre objet de la fonction y objet de code - les objets de code sont ce qui est exécuté et c'est juste un attribut func_code de l'objet-fonction et, en tant que tel, n'a pas accès à l'élément func_dict où notre attribut x est :

>>> g
<function foo at 0x0173AE30>
>>> type(g)
<type 'function'>
>>> g.func_code
<code object foo at 017532F0, file "<interactive input>", line 1>
>>> type(g.func_code)
<type 'code'>
>>> g.func_dict
{'x': 888}

Il y a bien sûr d'autres astuces que vous pouvez faire pour que cela semble être une fonction - en particulier l'astuce avec la définition des classes... mais ce n'est pas une fonction en soi. Tout dépend de ce que vous avez vraiment besoin de faire avec ça.

1 votes

Inutile comme réponse immédiate, mais incroyablement éclairant à tous les autres égards. J'ai vraiment appris beaucoup de choses sur le pourquoi de la portée. +1

11voto

AntiEgo Points 316

Comme solution de rechange, vous pouvez utiliser une fonction d'usine pour corriger votre champ d'application :

def factory():
    def inner():
        print inner.x
    return inner

>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11

4voto

Mark Rushakoff Points 97350

Je doute que ce soit le meilleur Il n'y a pas de moyen d'y parvenir, mais vous pouvez accéder aux attributs en utilisant le nom de la méthode dans la méthode :

>>> def foo():
...   print foo.x
... 
>>> foo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
AttributeError: 'function' object has no attribute 'x'
>>> foo.x = 5
>>> foo()
5

0 votes

Hmm. mais si je pouvais le faire fonctionner aussi pour la fonction "renommée", comme : g = f; del f; print(g()) ? :)

1 votes

@mykhal il va préserver x bien sûr, puisque g est juste une autre référence à quelque chose initialement référencé uniquement par f

3 votes

Nailxx : err, oui, mais puisque nous référençons cette fonction avec un nom explicite, qui est maintenant supprimé, NameError est soulevé.

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