¿Cómo puede una función acceder a sus propios atributos?

¿Es posible acceder a los atributos del objeto de función python desde dentro del scope de la función?

por ejemplo, tengamos

def f(): return SOMETHING f._x = "foo" f() # -> "foo" 

ahora, ¿qué tiene que ser ALGO, si queremos que el contenido del atributo _x “foo” sea devuelto? si es posible (simplemente)

Gracias

ACTUALIZAR:

Me gustaría el siguiente trabajo también:

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

ACTUALIZACIÓN 2:

Afirmar que no es posible (si es el caso), y por qué, es más satisfactorio que proporcionar una manera de falsificarlo, por ejemplo, con un objeto diferente que una función.

Solución

Haga que uno de los argumentos predeterminados de la función sea una referencia a la función en sí.

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

Ejemplo de uso:

 >>> fx = 17 >>> b = f >>> del f >>> b() 17 

Explicación

El póster original quería una solución que no requiera una búsqueda de nombre global. La solucion simple

 def f(): return fx 

realiza una búsqueda de la variable global f en cada llamada, que no cumple con los requisitos. Si se borra f , entonces la función falla. La propuesta de inspect más complicada falla de la misma manera.

Lo que queremos es realizar un enlace temprano y almacenar la referencia enlazada dentro del propio objeto. Lo siguiente es conceptualmente lo que estamos haciendo:

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

En lo anterior, self es una variable local, por lo que no se realiza una búsqueda global. Sin embargo, no podemos escribir el código como está, porque f aún no está definido cuando intentamos vincular el valor predeterminado de self con él. En su lugar, establecemos el valor predeterminado después de que se define f .

Decorador

Aquí hay un simple decorador para hacer esto por ti. Tenga en cuenta que el argumento del self debe ser el último, a diferencia de los métodos, donde el self es lo primero. Esto también significa que debe dar un valor predeterminado si alguno de sus otros argumentos toma un valor predeterminado.

 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) 

Ejemplo:

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

Puedes usar una clase para hacer esto.

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

Bueno, veamos qué función es:

 >>> def foo(): ... return x ... >>> foo.x = 777 >>> foo.x 777 >>> foo() Traceback (most recent call last): File "", line 1, in  File "", 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 

Jajaja Así que el atributo se agregó al objeto de función, pero no lo verá porque en su lugar está buscando una x global.

Podemos intentar capturar el marco de la ejecución de la función e intentar ver lo que hay (esencialmente lo que Anthony Kong sugirió, pero sin el módulo de 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': } >>> fr.f_code ", 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' 

Jajaja Entonces, ¿tal vez podamos obtener el nombre de la función del nombre del bloque de código y luego buscar el atributo? Bastante seguro:

 >>> 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 

¡Eso es genial! Pero, ¿soportaría el cambio de nombre y la eliminación de la función original?

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

Ah, muy dudoso. El objeto de función y su objeto de código todavía insisten en que se llaman foo . Efectivamente, aquí es donde se rompe:

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

¡Dang! Entonces, en general, no se puede hacer a través de la introspección a través de los marcos de ejecución. El problema parece ser que existe una diferencia entre el objeto de función y el objeto de código: los objetos de código son lo que se ejecuta y es solo un atributo func_code del func_code de función y, como tal, no tiene acceso al atributo func_dict , donde nuestro atributo x es :

 >>> g  >>> type(g)  >>> g.func_code ", line 1> >>> type(g.func_code)  >>> g.func_dict {'x': 888} 

Hay, por supuesto, otra chicanería que puedes hacer para que parezca como una función, en particular el truco con la definición de clase ... pero esa no es una función en sí. Todo depende de lo que realmente necesites hacer con eso.

Como solución alternativa, puede usar una función de fábrica para arreglar su scope:

 def factory(): def inner(): print inner.x return inner >>> foo=factory() >>> foo.x=11 >>> foo() 11 >>> bar = foo >>> del foo >>> bar() 11 

Dudo que esta sea la mejor manera de lograr esto, pero puedes acceder a los atributos usando el nombre del método dentro del método:

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

Aquí hay un decorador que inyecta current_fun en las funciones globales antes de ejecutar la función. Es bastante el hack, pero también bastante efectivo.

 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 

Aquí hay un ejemplo de uso

 In [41]: fx = '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 

La respuesta es bastante simple. Simplemente use el nombre del hecho que se busca en el momento de la ejecución, no el tiempo de comstackción:

 def f(): return f._x f._x = "foo" f() # -> "foo" 

Si quieres que sea totalmente independiente del nombre de la función, necesitas algo de magia de marco. Por ejemplo:

 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() 

Esto utiliza un poco de un enfoque de hackers, pero es posiblemente el más correcto hasta ahora dado que funciona con la llamada g() también. Funciona porque se basa en cualquier inspección de bytecode realizada por el módulo dis , como acceso directo.

Parece más intrépido de lo que realmente es, en parte, porque la llamada dis.disassemble() imprime a stdout, así que redirigirlo a un StringIO. Utilizo disassemble() por su función de resaltar la última instrucción (agregar una línea de print text allí para ver cómo se ve) y eso hace que sea más fácil capturar el LOAD_NAME anterior y la variable que usó.

Sería posible usar una biblioteca de inspección de código de bytes más limpia para hacer esto sin usar el módulo dis , pero esto prueba que es posible. Puede que este no sea el enfoque más robusto, pero quizás funcione de nuevo en la mayoría de los casos. No he gastado el tiempo suficiente para introducir internamente en Python o en el bytecode para saber si la mayoría de los bytecodes CALL_FUNCTION están precedidos inmediatamente por instrucciones que el truco de CALL_FUNCTION regulares escogería.

 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() 

Esto genera la siguiente salida:

 call f(): foo call g(): foo 

¿Qué hay de usar una clase en lugar de una función y abusar del método __new__ para hacer que la clase se pueda __new__ como una función? Dado que el método __new__ obtiene el nombre de la clase como primer parámetro, puede acceder a todos los atributos de la clase

como en

 class f(object): def __new__(cls, x): print cls.myattribute return x 

esto funciona como en

 f.myattribute = "foo" f(3) foo 3 

entonces puedes hacer

 g=f f=None g(3) foo 3 

El problema es que incluso si el objeto se comporta como una función, no lo es. Por lo tanto, los IDE no le proporcionan la firma.

Otra forma de lograr esto es definir la función dentro de otra función y hacer que la función externa devuelva la interna. Entonces la función interna puede acceder a sí misma a través de un cierre. Aquí hay un ejemplo simple:

 def makeFunc(): def f(): return f._x return f 

Entonces:

 >>> f = makeFunc() >>> f._x = "foo" >>> f() 'foo' >>> g = f >>> del f >>> g() 'foo' 

Si solo se necesita un método pero desea una clase liviana con un estado de clase compartida más un estado de instancia individual, puede probar el patrón de cierre de esta manera:

 # 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') 

Aquí hay una estrategia que es probablemente peor que la idea de func_defaults , pero no obstante es interesante. Es intrépido, pero no se me ocurre nada que esté prácticamente mal.

Podemos implementar una función que puede referirse a sí misma como una clase con un solo método __new__ (el método que normalmente crea un nuevo objeto de esa clase).

 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 

Quizás este patrón podría ser útil para una función de registro …

 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 __name__ == '__main__': log_once(1) log_once(1) log_once(2) 

Solo define tu función dentro de un cierre:

 def generate_f(): def f(): return fx return f f = generate_f() fx = 314 g = f del f print g() # => 314 

Me gusta esto muchisimo.

 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)