Function attributes

M

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

Steven D'Aprano

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

Krister Svanlund

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

This sounds like something you shouldn't be doing. You should probably
use a class instead.

(Sending again to get it on the list >_<)
 
J

John Posner

Not built-in.



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

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

Bruno Desthuilliers

John Posner a écrit :
(snip)


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

Steven D'Aprano

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:

.... if x < 0:
.... print "original"
.... return f(-x)
.... else:
.... return x+1
....
original
3
.... if x > 0:
.... return old_f(-x)
.... else:
.... return x
....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
 
M

MRAB

Stephen said:
On Wed, Feb 10, 2010 at 6:36 AM, Steven D'Aprano
<[email protected]



Not built-in.


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.


I agree its slightly... in-elegant, or sub-optimal, but I'm not sure I
want to call it a *wart*. If one calls it a wart, there might be
inspiration to fix it.

And this sounds like a precursor to making "self" non-explicit, and I
*really* like my explicit self. :)

Then again, I have been slightly bruised by this sub-optimal situation
before.

I tried to do a function using a frame hack to get the information
easily, but failed. My long years of avoiding frame hacks like the
plague have left me deficient. :(
Does this mean that Python needs, say, __function__ (and perhaps also
__module__)?
 
A

Arnaud Delobelle

Steven D'Aprano said:
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:

... if x < 0:
... print "original"
... return f(-x)
... else:
... return x+1
...
original
3
... if x > 0:
... return old_f(-x)
... else:
... return x
...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

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
.... def factorial(this_function, n):
.... if n > 0:
.... return n * this_function(n - 1)
.... else:
.... return 1
.... 1307674368000L
 
S

Steven D'Aprano

Stephen said:
On Wed, Feb 10, 2010 at 6:36 AM, Steven D'Aprano
<[email protected]



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 :(
 
S

Steven D'Aprano

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
 
A

Arnaud Delobelle

Steven D'Aprano said:
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

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:
(False, True)

This is proof of concept stuff - probably very fragile!
 
G

Gabriel Genellina

En Wed, 10 Feb 2010 10:59:41 -0300, Muhammad Alkarouri
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:
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.
 
T

Terry Reedy

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
 
G

Gabriel Genellina

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

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

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

Arnaud Delobelle

Gabriel Genellina said:
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.
 
S

Steve Howell

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

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,780
Messages
2,569,611
Members
45,280
Latest member
BGBBrock56

Latest Threads

Top