Est-il possible d'accéder aux attributs d'objet de fonction python à partir de la portée de la fonction?
par exemple. ayons
def f():
return SOMETHING
f._x = "foo"
f() # -> "foo"
maintenant, que doit faire quelque chose, si nous voulons avoir le contenu de l'attribut _x "foo" retourné? si c'est même possible (simplement)
merci
METTRE À JOUR:
j'aimerais aussi le travail suivant:
g = f
del f
g() # -> "foo"
MISE À JOUR 2:
L'affirmation qu'il n'est pas possible (si c'est le cas) et pourquoi, est plus satisfaisante que de fournir un moyen de le simuler, par exemple. avec un objet différent d'une fonction
Faites de l’un des arguments par défaut de la fonction 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
L’affiche originale voulait une solution qui ne nécessite pas de recherche de 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é, la fonction échoue. La proposition plus complexe inspect
échoue de la même manière.
Ce que nous voulons, c'est exécuter early binding et stocker la référence liée dans l'objet même. Ce qui suit est conceptuellement ce que nous faisons:
def f(self=f):
return self.x
self
est une variable locale. Par conséquent, aucune recherche globale n'est effectuée. Cependant, nous ne pouvons pas écrire le code tel quel, car f
n'est pas encore défini lorsque nous essayons de lui lier la valeur par défaut de self
. Au lieu de cela, nous définissons la valeur par défaut après que f
soit défini.
Voici un simple décorateur à faire cela pour vous. Notez que l'argument self
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'
Vous pouvez simplement utiliser une classe pour le faire
>>> class F(object):
... def __call__(self, *args, **kw):
... return self._x
...
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
Eh bien, regardons quelle fonction est:
>>> 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 car il recherche globalement x
.
Nous pouvons essayer de saisir le cadre de l'exécution de la fonction et de regarder ce qu'il y a (essentiellement ce que Anthony Kong a suggéré mais sans module 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 que nous pouvons obtenir le nom de la fonction à partir du nom du bloc de code et ensuite rechercher l’attribut de manière détournée? Assez sur:
>>> 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 cela résoudrait-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 de fonction et son objet de code insistent toujours pour qu'ils s'appellent foo
. Effectivement, voici où ç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 existe une différence entre objet fonction et objet code - les objets code sont ce qui est exécuté et n’est qu’un attribut func_code
de la fonction objet et n’a donc pas accès func_dict
attribut, 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 paraisse comme une fonction - en particulier l'astuce avec la définition de classe ... mais ce n'est pas une fonction en soi. Tout dépend de ce que vous devez vraiment faire avec ça.
Pour résoudre ce problème, vous pouvez utiliser une fonction d'usine pour corriger votre étendue:
def factory():
def inner():
print inner.x
return inner
>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
Voici un décorateur qui injecte current_fun dans les fonctions globales avant d'exécuter la fonction. C'est un peu le bidouillage, mais aussi assez efficace.
from functools import wraps
def introspective(f):
@wraps(f)
def wrapper(*args, **kwargs):
exists = 'current_fun' in f.func_globals
old = f.func_globals.get('current_fun',None)
f.func_globals['current_fun'] = wrapper
try:
return f(*args, **kwargs)
finally:
if exists:
f.func_globals['current_fun'] = old
else:
del f.func_globals['current_fun']
return wrapper
@introspective
def f():
print 'func_dict is ',current_fun.func_dict
print '__dict__ is ',current_fun.__dict__
print 'x is ',current_fun.x
Voici un exemple d'utilisation
In [41]: f.x = 'x'
In [42]: f()
func_dict is {'x': 'x'}
__dict__ is {'x': 'x'}
x is x
In [43]: g = f
In [44]: del f
In [45]: g()
func_dict is {'x': 'x'}
__dict__ is {'x': 'x'}
x is x
Je doute que ce soit le meilleur 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
La réponse est plutôt simple. Il suffit d'utiliser le nom de fait recherché au moment de l'exécution, pas lors de la compilation:
def f():
return f._x
f._x = "foo"
f() # -> "foo"
Ceci utilise une approche peu sophistiquée, mais c'est peut-être la plus correcte à ce jour, étant donné que cela fonctionne également avec l'appel g()
. Cela fonctionne parce qu’il s’appuie sur l’inspection du bytecode effectuée par le module dis , en tant que raccourci.
Cela semble plus compliqué que c’est en partie parce que l’appel dis.disassemble()
s’imprime sur stdout; je le redirige donc vers un StringIO. J'utilise disassemble()
pour sa fonctionnalité de mise en surbrillance de la dernière instruction (ajoutez une ligne print text
pour voir à quoi elle ressemble) et cela facilite la saisie du LOAD_NAME
précédent et de la variable utilisée.
Il serait possible d’utiliser une bibliothèque d’inspection de bytecode plus propre sans utiliser le module dis
, mais cela prouve que c’est possible. Cette approche n’est peut-être pas la plus solide, mais peut-être que cela fonctionnera dans la plupart des cas. Je n’ai pas passé assez de temps à fouiller dans les éléments internes ou les bytecodes de Python pour savoir si la plupart des bytecodes CALL_FUNCTION
sont précédés immédiatement d’instructions que l’astuce regex choisirait.
import inspect
import dis
import re
import sys
import StringIO
def f():
caller = inspect.stack()[1][0]
sys.stdout = StringIO.StringIO()
dis.disassemble(caller.f_code, caller.f_lasti)
text = sys.stdout.getvalue()
sys.stdout = sys.__stdout__
match = re.search(r'LOAD_NAME.*\((.*?)\)\s+-->', text)
name = match.group(1)
try:
func = caller.f_locals[name]
except KeyError:
func = caller.f_globals[name]
return func._x
f._x = 'foo'
print 'call f():', f()
g = f
del f
print 'call g():', g()
Cela génère la sortie suivante:
call f(): foo
call g(): foo
Si vous voulez qu'il soit totalement indépendant du nom de la fonction, vous avez besoin d'un peu de magie du cadre. Par exemple:
def f2():
import inspect
frame = inspect.currentframe()
fname = frame.f_code.co_name
fobj = frame.f_globals[fname]
print fobj._x
f2._x = 2
f2()
J'aime beaucoup ça.
from functools import update_wrapper
def dictAsGlobals(f):
nf = type(f)(f.__code__, f.__dict__, f.__name__, f.__defaults__, f.__closure__)
try: nf.__kwdefaults__ = f.__kwdefaults__
except AttributeError: pass
nf.__dict__ = f.__dict__
nf.__builtins__ = f.__globals__["__builtins__"]
return update_wrapper(nf, f)
@dictAsGlobals
def f():
global timesCalled
timesCalled += 1
print(len.__doc__.split("\n")[0])
return factor0 * factor1
vars(f).update(timesCalled = 0, factor0 = 3, factor1 = 2)
print(f())
print(f())
print(f.timesCalled)
Voici une stratégie qui est probablement pire que l'idée func_defaults
, mais qui est néanmoins intéressante. C'est hacky mais je ne peux penser à quoi que ce soit qui cloche.
Nous pouvons implémenter une fonction qui peut se désigner elle-même comme une classe avec une seule méthode __new__
(la méthode qui crée normalement un nouvel objet de cette classe).
class new:
"""Returns True the first time an argument is passed, else False."""
seen = set()
def __new__(cls, x):
old = x in cls.seen
cls.seen.add(x)
return not old
def main():
print(new(1)) # True
print(new(2)) # True
print(new(2)) # false
is_new = new
print(is_new(1)) # False
Peut-être que ce modèle pourrait être utile pour une fonction de journalisation ...
class log_once:
"""Log a message if it has not already been logged.
Args:
msg: message to be logged
printer: function to log the message
id_: the identifier of the msg determines whether the msg
has already been logged. Defaults to the msg itself.
This is useful to log a condition that occurs many times in a single
execution. It may be relevant that the condition was true once, but
you did not need to know that it was true 10000 times, nor do you
desire evidence to that effect to fill your terminal screen.
"""
seen = set()
def __new__(cls, msg, printer=print, id_=None):
id_ = id_ or msg
if id_ not in cls.seen:
cls.seen.add(id_)
printer(id_)
if __== '__main__':
log_once(1)
log_once(1)
log_once(2)
Il suffit de définir votre fonction dans une fermeture:
def generate_f():
def f():
return f.x
return f
f = generate_f()
f.x = 314
g = f
del f
print g()
# => 314
Pourquoi ne pas utiliser une classe au lieu d’une fonction et abuser de la méthode __new__
pour rendre la classe appelable en tant que fonction? Puisque la méthode __new__
obtient le nom de la classe en tant que premier paramètre, elle peut accéder à tous les attributs de la classe.
comme dans
class f(object):
def __new__(cls, x):
print cls.myattribute
return x
cela fonctionne comme dans
f.myattribute = "foo"
f(3)
foo
3
alors vous pouvez faire
g=f
f=None
g(3)
foo
3
Le problème est que même si l'objet se comporte comme une fonction, ce n'est pas le cas. Par conséquent, les IDE ne parviennent pas à vous fournir la signature.
Pour ce faire, vous pouvez également définir la fonction dans une autre fonction et demander à la fonction externe de renvoyer la fonction interne. La fonction interne peut alors accéder à elle-même via une fermeture. Voici un exemple simple:
def makeFunc():
def f():
return f._x
return f
Ensuite:
>>> f = makeFunc()
>>> f._x = "foo"
>>> f()
'foo'
>>> g = f
>>> del f
>>> g()
'foo'
Si une seule méthode est nécessaire mais que vous souhaitez une classe légère avec un état de classe partagé et un état d'instance individuel, vous pouvez essayer le modèle de fermeture comme suit:
# closure example of light weight object having class state,
# local state, and single method
# This is a singleton in the sense that there is a single class
# state (see Borg singleton pattern notebook)
# BUT combined with local state
# As long as only one method is needed, this one way to do it
# If a full class singleton object is needed with multiple
# methods, best look at one of the singleton patterns
def LW_Object_Factory(localState):
# class state - doesn't change
lwof_args = (1, 2, 3)
lwof_kwargs = {'a': 4, 'b': 5}
# local instance - function object - unique per
# instantiation sharing class state
def theObj(doc, x):
print doc, 'instance:'
print '\tinstance class state:\n\t\targs -', \
lwof_args, ' kwargs -', lwof_kwargs
print '\tinstance locals().items():'
for i in locals().items():
print '\t\t', i
print '\tinstance argument x:\n\t\t', '"{}"'.format(x)
print '\tinstance local state theObj.foo:\n\t\t',\
'"{}"'.format(theObj.foo)
print ''
# setting local state from argument
theObj.foo = localState
return(theObj)
lwo1 = LW_Object_Factory('foo in local state for first')
lwo2 = LW_Object_Factory('foo in local state for second')
# prove each instance is unique while sharing class state
print 'lwo1 {} distinct instance from lwo2\n'\
.format(id(lwo1) <> id(lwo2) and "IS" or "IS NOT")
# run them
lwo1('lwo1', 'argument lwo1')
lwo2('lwo2', 'argument lwo2')