is it possible to access the python function object attributes from within the function scope?
e.g. let's have
def f():
return SOMETHING
f._x = "foo"
f() # -> "foo"
now, what SOMETHING has to be, if we want to have the _x attribute content "foo" returned? if it's even possible (simply)
thanks
UPDATE:
i'd like the following work also:
g = f
del f
g() # ->开发者_如何学编程; "foo"
UPDATE 2:
Statement that it is not possible (if it is the case), and why, is more satisfying than providing a way how to fake it e.g. with a different object than a function
Solution
Make one of the function's default arguments be a reference to the function itself.
def f(self):
return self.x
f.func_defaults = (f,)
Example usage:
>>> f.x = 17
>>> b = f
>>> del f
>>> b()
17
Explanation
The original poster wanted a solution that does not require a global name lookup. The simple solution
def f():
return f.x
performs a lookup of the global variable f
on each call, which does not meet the requirements. If f
is deleted, then the function fails. The more complicated inspect
proposal fails in the same way.
What we want is to perform early binding and store the bound reference within the object itself. The following is conceptually what we are doing:
def f(self=f):
return self.x
In the above, self
is a local variable, so no global lookup is performed. However, we can't write the code as-is, because f
is not yet defined when we try to bind the default value of self
to it. Instead, we set the default value after f
is defined.
Decorator
Here's a simple decorator to do this for you. Note that the self
argument must come last, unlike methods, where self
comes first. This also means that you must give a default value if any of your other arguments take a default value.
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)
Example:
>>> foo.subject = 'Fred'
>>> bar = foo
>>> del foo
>>> bar('runs')
'Fred runs swiftly'
You could just use a class to do this
>>> class F(object):
... def __call__(self, *args, **kw):
... return self._x
...
>>> f=F()
>>> f._x = "foo"
>>> f()
'foo'
>>> g=f
>>> del f
>>> g()
'foo'
Well, let's look at what function is:
>>> 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! So the attribute was added to the function object but it won't see it because it is looking for global x
instead.
We can try to grab the frame of the function execution and try to look what's there (essentially what Anthony Kong suggested but w/o inspect
module):
>>> 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! So maybe we can get the name of the function from the name of the code block and then look in round-about way for the attribute? Sure enough:
>>> 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
That's great! But would it stand the renaming and deletion of original function?
>>> g = foo
>>> g.func_name
'foo'
>>> g.func_code.co_name
'foo'
Ah, very doubtful. The function object and its code object still insist they are called foo
. Sure enough, here is where it breaks:
>>> 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! So in general it can't be done through introspection via the execution frames. The problems seems to be that there is a difference between function object and code object - code objects are what is executed and is just one attribute func_code
of the function-object and as such has no access to the func_dict
attribute, where our attribute x
is:
>>> 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}
There is of course other chicanery you can do so that it seems as function - in particular the trick with class definition... but that is not a function per se. It all depends on what do you really need to do with that.
As a workaround you could use a factory function to fix your scope:
def factory():
def inner():
print inner.x
return inner
>>> foo=factory()
>>> foo.x=11
>>> foo()
11
>>> bar = foo
>>> del foo
>>> bar()
11
I doubt this is the best way to accomplish this, but you can access the attributes by using the method's name within the method:
>>> 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
Here's a decorator that injects current_fun into the functions globals before executing the function. It's quite the hack, but also quite effective.
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
Here's a usage example
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
The answer is rather simple. Just use the fact name is looked for at execution time, not compile time:
def f():
return f._x
f._x = "foo"
f() # -> "foo"
If you want it to be totally independent of the function name, you need some frame magic. For example:
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()
This uses a bit of a hackish approach, but it's possibly the most correct so far given that it works with the g()
call as well. It works because it's relying on whatever bytecode inspection is performed by the dis module, as a shortcut.
It looks more hackish than it really is partly because the dis.disassemble()
call prints to stdout, so I redirect that into a StringIO. I use disassemble()
for its feature of highlighting the last instruction (add a print text
line in there to see how it looks) and that makes it easier to grab the previous LOAD_NAME
and the variable it used.
It would be possible to use a cleaner bytecode inspection library to do this without using the dis
module at all, but this proves that it's possible. This might not be the most robust approach, but then again maybe it will work in most cases. I haven't spent enough time poking into Python internals or bytecode to know whether most CALL_FUNCTION
bytecodes are preceded immediately by instructions that the regex trick would pick out.
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()
This generates the following output:
call f(): foo
call g(): foo
How about using a class instead of a function and abusing the __new__
method to make the class callable as a function? Since the __new__
method gets the class name as the first parameter, it can access all the class attributes
like in
class f(object):
def __new__(cls, x):
print cls.myattribute
return x
this works as in
f.myattribute = "foo"
f(3)
foo
3
then you can do
g=f
f=None
g(3)
foo
3
The issue is that even if the object behaves like a function, it is not. Hence IDEs fail to provide you with the signature.
Another way to accomplish this is to define the function inside another function, and have the outer function return the inner one. Then the inner function can access itself via a closure. Here's a simple example:
def makeFunc():
def f():
return f._x
return f
Then:
>>> f = makeFunc()
>>> f._x = "foo"
>>> f()
'foo'
>>> g = f
>>> del f
>>> g()
'foo'
If there is only one method needed but you want a light-weight class with shared class state plus individual instance state, you might try the closure pattern like this:
# 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')
Here is a strategy that is probably worse than the func_defaults
idea, but is interesting nonetheless. It's hacky but I can't think of anything practically wrong with it.
We can implement a function that can refer to itself as a class with a single __new__
method (the method that normally creates a new object of that class).
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
Perhaps this pattern could be useful for a logging function...
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)
Just define your function inside a closure:
def generate_f():
def f():
return f.x
return f
f = generate_f()
f.x = 314
g = f
del f
print g()
# => 314
I like this alot.
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)
Sorry for the late reply but I just stumbled upon this. I would have to argue that the way that “g” is asked to work is non-Pythonic. Inside function, the name “f“ refers to the value of a global variable at the time the function is called. Given that, consider the following:
def f():
print(f)
f, g = 42, f
g() # prints 42
del f
g() # raises an exception
Hopefully, no one argues that this is incorrect behavior. Given that fact, I cam only vote for any answer that requires the use of a different variable name (e.g. “self”) inside the function.
精彩评论