What is the simplest way to access the attributes of a function from
inside it, other than using its explicit name?
In a function like f below:
def f(*args):
f.args = args
print args
is there any other way?
I am guessing the next question will be: should I really care? It just
feels like there should be a way, but I am not able to verbalise a
valid one at the moment, sorry.
Regards,
Muhammad Alkarouri
> Hi everyone,
>
> What is the simplest way to access the attributes of a function from
> inside it, other than using its explicit name? In a function like f
> below:
>
> def f(*args):
> f.args = args
> print args
>
> is there any other way?
Not built-in.
> I am guessing the next question will be: should I really care? It just
> feels like there should be a way, but I am not able to verbalise a valid
> one at the moment, sorry.
I completely agree with you. It is a wart that functions are only able to
refer to themselves by name, because if the name changes, things break.
Consider:
old_f = f # save the old version of the function
def f(x):
return old_f(x+1) # make a new function and call it f
This won't work correctly, because old_f still tries to refer to itself
under the name "f", and things break very quickly.
--
Steven
This sounds like something you shouldn't be doing. You should probably
use a class instead.
(Sending again to get it on the list >_<)
They didn't break immediately for me -- what am I missing?:
#-------------------
def f(): return "old func"
g = f
print "name attr of f:", f.__name__
print "name attr of g:", g.__name__
def f(): return "new func"
print "name attr of f:", f.__name__
print "name attr of g:", g.__name__
print "*** calling function currently named f:"
print f()
print "*** calling function currently named g:"
print g()
#-------------------
program output (Python 2.6.4):
name attr of f: f
name attr of g: f
name attr of f: f
name attr of g: f
*** calling function currently named f:
new func
*** calling function currently named g:
old func
-John
>>> def f(*args):
>>> f.args = args
>>> print args
>>>
(snip)
>> I completely agree with you. It is a wart that functions are only able to
>> refer to themselves by name, because if the name changes, things break.
>> Consider:
>>
>> old_f = f # save the old version of the function
>>
>> def f(x):
>> return old_f(x+1) # make a new function and call it f
>>
>> This won't work correctly, because old_f still tries to refer to itself
>> under the name "f", and things break very quickly.
>
> They didn't break immediately for me -- what am I missing?:
The fact that in the OP's snippet, code inside f's body refers to f by
its name.
>> This won't work correctly, because old_f still tries to refer to itself
>> under the name "f", and things break very quickly.
>
> They didn't break immediately for me -- what am I missing?:
The original function f doesn't try to refer to itself in any way. With
no recursive call or access, it doesn't matter what f is named.
See this example instead:
>>> def f(x):
... if x < 0:
... print "original"
... return f(-x)
... else:
... return x+1
...
>>> f(2)
3
>>> f(-2)
original
3
>>>
>>> old_f = f
>>> def f(x):
... if x > 0:
... return old_f(-x)
... else:
... return x
...
>>> f(-1)
-1
>>> f(1)
original
original
original
original
original
original
[...]
File "<stdin>", line 3, in f
File "<stdin>", line 4, in f
File "<stdin>", line 3, in f
RuntimeError: maximum recursion depth exceeded
--
Steven
Of course! Tx. -John
It's not ideal, but you can use a decorator like this to solve this
problem:
def bindfunction(f):
def bound_f(*args, **kwargs):
return f(bound_f, *args, **kwargs)
bound_f.__name__ = f.__name__
return bound_f
>>> @bindfunction
... def factorial(this_function, n):
... if n > 0:
... return n * this_function(n - 1)
... else:
... return 1
...
>>> factorial(15)
1307674368000L
>>> fac = factorial
>>> fac(15)
1307674368000L
>>> factorial = 'foobar'
>>> fac(15)
1307674368000L
--
Arnaud
> Stephen Hansen wrote:
>> On Wed, Feb 10, 2010 at 6:36 AM, Steven D'Aprano
>> <st...@remove-this-cybersource.com.au
>> <mailto:st...@remove-this-cybersource.com.au>> wrote:
>>
>> On Wed, 10 Feb 2010 05:59:41 -0800, Muhammad Alkarouri wrote:
>> > What is the simplest way to access the attributes of a function
>> > from inside it, other than using its explicit name? In a
>> > function like f
>>
>> Not built-in.
[...]
> Does this mean that Python needs, say, __function__ (and perhaps also
> __module__)?
Python already has __module__, except that it is spelled:
import __main__
As for __function__, perhaps, but a solution I would be satisfied with
would be to make it optional. Most functions are not recursive, and do
not attach attributes to themselves. For the few that do need to refer to
themselves, it would be sufficient to call a decorator to get the
behaviour needed. I thought I had come up with such a thing, once before,
but I can't find it now :(
--
Steven
> It's not ideal, but you can use a decorator like this to solve this
> problem:
>
> def bindfunction(f):
> def bound_f(*args, **kwargs):
> return f(bound_f, *args, **kwargs)
> bound_f.__name__ = f.__name__
> return bound_f
Ah, very nice. Perhaps it's better to use functools.wraps?
import functools
def bindfunction(f):
@functools.wraps(f)
def bound_f(*args, **kwargs):
return f(bound_f, *args, **kwargs)
return bound_f
--
Steven
I think I wrote this before functools :). Anyway it still doesn't work
with mutually recursive functions. I also tried another approach (can't
find the file anymore, so I've redone it, sorry no time to make it very
readable) as below. It doesn't require an extra first argument for the
function and it takes care of mutually recursive functions if used as in
the example.
def bindglobals(*args):
"""
Binds all the globals in all the arguments which must be functions.
return the new bound functions. When called with a single argument,
return the bound function so it can be used as a decorator. It is
assumed that all argument are functions and have the same global
namespace
"""
function_names = [f.__name__ for f in args]
def get_global(f, n):
d = f.func_globals
if n not in d:
d = d['__builtins__'].__dict__
return d[n]
bound_globals = dict(
(n, get_global(f, n))
for f in args for n in f.func_code.co_names
if n not in function_names
)
bound_functions = [
type(f)(f.func_code, bound_globals, f.func_name,
f.func_defaults, f.func_closure)
for f in args
]
bound_globals.update(zip(function_names, bound_functions))
if len(args) == 1:
return bound_functions[0]
else:
return bound_functions
# Example
@bindglobals
def fac(n):
return 1 if n <= 1 else n*fac(n - 1)
# Decorator syntax cannot be used with mutually recursive functions:
def even(n):
return True if not n else odd(n - 1)
def odd(n):
return False if not n else even(n - 1)
even, odd = bindglobals(even, odd)
# Example in action:
>>> fac(10)
3628800
>>> f = fac
>>> fac = 'foo'
>>> f(10)
3628800
>>> even(5), odd(5)
(False, True)
>>> e, o = even, odd
>>> even, odd = 'spam', 'eggs'
>>> e(5), o(5)
(False, True)
This is proof of concept stuff - probably very fragile!
--
Arnaud
> Does this mean that Python needs, say, __function__ (and perhaps also
> __module__)?
See PEP 3130 "Access to Current Module/Class/Function" (rejected)
(http://www.python.org/dev/peps/pep-3130/)
--
Arnaud
> What is the simplest way to access the attributes of a function from
> inside it, other than using its explicit name?
> In a function like f below:
>
> def f(*args):
> f.args = args
> print args
>
> is there any other way?
See this:
>>> def foo(): pass
...
>>> import sys
>>> sys.getrefcount(foo)
2
The 2 means that there is a *single* reference to the function - the foo
name in the global namespace. No other reference exists, there is no
hidden attribute somewhere that you may use. If you want another way to
reach the function, you'll have to add it yourself.
I've written a decorator for "injecting" a __function__ name into the
function namespace, but I can't find it anywhere. I think I implemented
it by adding a fake additional argument and replacing LOAD_GLOBAL with
LOAD_NAME in the bytecode.
> I am guessing the next question will be: should I really care? It just
> feels like there should be a way, but I am not able to verbalise a
> valid one at the moment, sorry.
One reason would be to write truly recursive functions (currently, a
recursive call involves a name lookup, which could potentially return a
different function). Another one, to implement some kind of tail call
optimization.
--
Gabriel Genellina
> I've written a decorator for "injecting" a __function__ name into the
> function namespace, but I can't find it anywhere. I think I implemented
> it by adding a fake additional argument and replacing LOAD_GLOBAL with
> LOAD_NAME in the bytecode.
The decorator only needs to replace the defaults args tuple.
It does not even need to know the parameter name,
just that it is the only (or last) with a default .
def f(n, me=None):
if n > 0: return n*me(n-1)
elif n==0: return 1
f.__defaults__ = (f,) # 3.1
print(f(5))
# 120
To generalize:
def g(a,b=1,me=None):
if a: return me(0)
else: return 41+b
g.__defaults__ = g.__defaults__[:len(g.__defaults__)-1] + (g,)
print(g(3))
#42
Of course, user could still screw up recursion by providing another
value for 'me'. This strikes me as about a likely (low) as a user
screwing up recursion in a library module function by rebinding the name
in the imported module.
Terry Jan Reedy
This is simple to implement, but requires changing the function
definition. My goal was to keep the original code unchanged, that is,
leave it as:
def f(n):
if n > 0: return n*f(n-1)
elif n==0: return 1
(like a normal, recursive function), and make the 'f' inside the function
body "magically" refer to the function itself.
> Of course, user could still screw up recursion by providing another
> value for 'me'. This strikes me as about a likely (low) as a user
> screwing up recursion in a library module function by rebinding the name
> in the imported module.
Well, if people really want to shoot themselves in the foot, there's
nothing we can do to avoid that...
--
Gabriel Genellina
> This is simple to implement, but requires changing the function
> definition. My goal was to keep the original code unchanged, that is,
> leave it as:
>
> def f(n):
> if n > 0: return n*f(n-1)
> elif n==0: return 1
>
> (like a normal, recursive function), and make the 'f' inside the function
> body "magically" refer to the function itself.
Someone did this several years ago with a bytecode hack. I believe it
was in the cookbook. It was rejected as part of standard CPython.
> En Thu, 11 Feb 2010 00:25:00 -0300, Terry Reedy <tjr...@udel.edu>
> escribió:
>> On 2/10/2010 4:49 PM, Gabriel Genellina wrote:
>>
>>> I've written a decorator for "injecting" a __function__ name into the
>>> function namespace, but I can't find it anywhere. I think I implemented
>>> it by adding a fake additional argument and replacing LOAD_GLOBAL with
>>> LOAD_NAME in the bytecode.
>>
>> The decorator only needs to replace the defaults args tuple.
>> It does not even need to know the parameter name,
>> just that it is the only (or last) with a default .
>>
>> def f(n, me=None):
>> if n > 0: return n*me(n-1)
>> elif n==0: return 1
>>
>> f.__defaults__ = (f,) # 3.1
>> print(f(5))
>
> This is simple to implement, but requires changing the function
> definition. My goal was to keep the original code unchanged, that is,
> leave it as:
>
> def f(n):
> if n > 0: return n*f(n-1)
> elif n==0: return 1
>
> (like a normal, recursive function), and make the 'f' inside the function
> body "magically" refer to the function itself.
I posted an example of a decorator that does just this in this thread a
couple of days ago:
http://mail.python.org/pipermail/python-list/2010-February/1235742.html
It doesn't require any bytecode hacking, although it requires breaking
apart the function object and making a new one from the bits.
--
Arnaud
> I posted an example of a decorator that does just this in this thread a
> couple of days ago:
>
> http://mail.python.org/pipermail/python-list/2010-February/1235742.html
Ouch! I didn't see your post, nor several other earlier posts in this
thread. In fact, I thought mine was the *first* reply when I wrote it!
Yes, of course, your code does exactly that without any bytecode hacks.
--
Gabriel Genellina
You should care. :)
You can avoid referring to f twice by using an inner method with a
name that is less subject to change or influence from outside forces:
def countdown_function_that_might_get_renamed(
n,
preamble,
postamble):
def recurse(n):
print preamble, n, postamble
if n:
recurse(n-1)
recurse(n)
countdown_function_that_might_get_renamed(10, "before", "after")
Note that the example makes the recursive call more concise, by
avoiding the need to resend the parameters that don't change during
the recursion ("preamble" and "postamble"). Of course, you do trade
off some terseness with the inner method, but it's only two lines of
code and one level of indentation.
For a somewhat related discussion see this short thread:
http://groups.google.com/group/comp.lang.python/browse_thread/thread/197221f82f7c247b
In particular see Gabriel's response to me. The fact that you are
saving off f.args makes me think that you are solving a problem
similar to mine, but I could be wrong.
It's not just me then. I'm using Thunderbird on Windows Vista to view
the group via gmane, and notice the earlier parts of a thread can show
up hours or even days after the later ones. Would something who
understands these things be kind enough to explain why this happens, or
at least provide the keywords needed for google, as I haven't got a clue
where to start.
The first feed is the general interchange of articles between NNTP
servers: while this is usually a reliable interchange mechanism, an
individual server can "glitch" occasionally (by losing network
connectivity, for example, or when some part of its news support
mechanism breaks temporarily).
The second is the python-list mailing list, maintained as a part of the
python.org infrastructure (by a hard-working team of volunteers, I
should add, who get little enough credit for the sterling work they do
day in day out to keep the real information flowing and the spam out).
More recently the waters have been muddied by Google Groups, which sadly
seems at time to be so badly managed (or simply not managed?) as to make
it a principal source of spam. This surprises me, as Gmail appears to
have very effective spam filtering. Many people appear to use Google
Groups as their primary interface to newsgroups and/or mailing lists to
the extent that they believe the Groups interface to be what everyone
uses to access the list.
The task of maintaining a two-way bridge between the comp.lang.python
NNTP community and the python-list email community while filtering out
the spam and ensuring that posts aren't duplicated too often is a
difficult one. For example, I hit "reply all" to your message, and had I
not trimmed the headers my NNTP client (Thunderbird) would have sent
copies to you, to the mailing list AND to the newsgroup. Sometimes such
messages do appear twice, particularly (in my experience) to recipients
who use the email channel.
In short, these things happen because life is complicated and the
technical solutions available to disambiguate the feeds aren't perfect.
So you might consider filing the glitches under "shit happens". Speaking
personally I wouldn't have a clue how to keep this stuff going on a
daily basis at the volume level we see on this group, and I am very
grateful to everyone who does.
regards
Steve
--
Steve Holden +1 571 484 6266 +1 800 494 3119
PyCon is coming! Atlanta, Feb 2010 http://us.pycon.org/
Holden Web LLC http://www.holdenweb.com/
UPCOMING EVENTS: http://holdenweb.eventbrite.com/
Many thanks for the explanation.
Kindest regards.
Mark Lawrence
> Gmane is primarily a news (NNTP-based) service, though for reasons best
> know to the organizers they choose to rename the standard newsgroups to
> fit their own idea of how the universe should be organized, so there the
> group becomes gmane.comp.python.general.
As I understand the site, the last I read a year or two ago, gmane is a
technical mailinglist-to-news gateway. It does not mirror newsgroups,
standard or not. It only mirrors lists such as python-list, not
newsgroups, such comp.lang.python. In doing so, it organizes the lists
into categories like g.comp.python, which has about 200 subgroups
mirroring 200 python lists.