12

[The code in the original version was badly messed up. Even after I fixed the code, several highly confusing typos remained in the post. I believe I finally fixed all of them too. Profuse apologies.]

The two calls to alias below produce different outputs, because the object associated with the variable my_own_id changes between the two calls:

>>> def my_own_id():
...     me = my_own_id
...     return id(me)
... 
>>> alias = my_own_id
>>> alias()
4301701560
>>> my_own_id = None
>>> alias()
4296513024

What can I assign to me in the definition of my_own_id so that its output remains invariant wrt subsequent re-definitions of the my_own_id variable? (IOW, so that the internal me variable always refers to the same function object?)

(I can get the current frame (with inspect.currentframe()), but it contains only a reference to the current code object, not to the current function.)

P.S. The motivation for this question is only to know Python better.

kjo
  • 33,683
  • 52
  • 148
  • 265
  • 2
    Where is `spam` defined? – Martijn Pieters Aug 10 '12 at 19:27
  • 1
    The example is somewhat confusing - are `ham` and `spam` references to `my_own_id()`? – voithos Aug 10 '12 at 19:30
  • Nice find :-) [you should edit the question renaming my_own_id to spam] I have no idea why this happens though. If you assign again, it remains stable: `foo = ham; ham(); del ham; foo()` (which I suppose is the answer to your questions, although without any insight :-) – thebjorn Aug 10 '12 at 19:40
  • This is interesting: `>>> id(my_own_id) 45375408 >>> alias = my_own_id >>> id(alias) 45375408 >>> my_own_id = None >>> id(my_own_id) 505354444 >>> id(alias) 45375408 >>> alias() 505354444` – jamylak Aug 10 '12 at 19:55
  • @MartijnPieters: my code was messed up; I have fixed it now. Sorry for the confusion. – kjo Aug 10 '12 at 19:56
  • @voithos: see my comment to MartijnPieters. Apologies to you too, and everyone else, for the badly mangled original post. – kjo Aug 10 '12 at 19:58
  • The question is very interesting. How to get the function object from within the function itself ? – Stefano Borini Aug 10 '12 at 20:14
  • Note that the return value from the final call to `alias()` is the id of `None` -- i.e., "what's the ID of the object that the name 'my_own_id' is bound to"? – Russell Borogove Aug 10 '12 at 21:05

7 Answers7

3

It seems that referring to my_own_id will look for 'my_own_id' in the global namespace dictionary, so it will always be the name used on function definition. Since that name can be assigned to different values, the value retrieved can also change. If you make me a default argument, you can assign it to the function itself at function definition to keep a reference to the actual function.

You could use this decorator which implicitly passes the original function itself as the first argument.

>>> from functools import wraps
>>> def save_id(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            return func(func, *args, **kwargs)
        return wrapper


>>> @save_id
def my_own_id(me): # me is passed implicitly by save_id
    return id(me)

>>> alias = my_own_id
>>> alias()
40775152
>>> my_own_id = 'foo'
>>> alias()
40775152
jamylak
  • 128,818
  • 30
  • 231
  • 230
  • Did you actually try that? I get a NameError in python 2.6.1; at the point where the arguments are parsed, the name `my_own_id` doesn't exist yet. – Russell Borogove Aug 10 '12 at 21:03
  • @RussellBorogove just tried it on 2.6.5 and 2.7 and it seems to work. http://ideone.com/EtXTY & http://ideone.com/bbBSC – jamylak Aug 10 '12 at 21:05
  • Ah, I left out the `my_own_id = None` line. You're getting the id of `None`, not the id of the function. (I'm talking about your first stanza here, not the decorator version.) – Russell Borogove Aug 10 '12 at 21:09
  • @RussellBorogove Oh I'll just get rid of that solution. I could have actually got the id of an `object()` I guess... – jamylak Aug 10 '12 at 21:10
2

Indeed, if you rely only on the function name, if that name is overitten in the global variable space (in the module the function was defined), a reference using the name of the function itslef will fail

The easier, more maintanable way is to write a decorator for that, that would provide a nonlocalvariable containing a reference to the function itself.

from functools import wraps

def know_thyself(func):
   @wraps(func):
   def new_func(*args, **kwargs):
        my_own_id = func
        return func(*args, **kwargs)
   return new_func

And can be used as:

>>> @know_thyself
... def my_own_id():
...     me = my_own_id
...     return id(me)
... 

There is another possible approach, far from being this clean, using frame introspection and rebuilding a new function re-using the same object code. I had used this on this post about a self-referential lambda expression in Python: http://metapython.blogspot.com.br/2010/11/recursive-lambda-functions.html

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 1
    Probably want to change the names, since as currently written this doesn't quite work: you define the `own_id` variable but then don't use it. – Danica Aug 10 '12 at 20:00
  • 1
    Er, I don't think this works either... nonlocal scope only works on an enclosing scope, not through callers. since the definition of `my_own_id` is not inside of either `know_thyself` nor `new_func`; none of the names are visible (short of some frame inspecting shenanegans) – SingleNegationElimination Aug 10 '12 at 20:06
1

Well, if you don't mind calling a function (to get the desired function into the global scope), you can wrap the function to protect its definition:

>>> def be_known():
...     global my_own_id
...     def _my_own_id():
...         return id(_my_own_id)
...     my_own_id = _my_own_id
... 
>>> be_known()
>>> my_own_id()
140685505972568
>>> alias, my_own_id = my_own_id, None
>>> alias()
140685505972568

Note that the protected function must call itself with the nonlocal name, not the global name.

SingleNegationElimination
  • 151,563
  • 33
  • 264
  • 304
1

The decorator approach is probably the best one. Here are some more for fun:

  1. Hijack one of the function arguments to provide a static variable.

    def fn(fnid=None):
        print "My id:", fnid
    fn.func_defaults = (id(fn),)
    
  2. There are a few ways to get the current function here: Python code to get current function into a variable?; most of these involve searching for currentframe.f_code in a variety of places. These work without any modification to the original function.

Community
  • 1
  • 1
Luke
  • 11,374
  • 2
  • 48
  • 61
1
import inspect
def _this_fn():
    try:
        frame = inspect.currentframe().f_back
        code = frame.f_code
        return frame.f_globals[code.co_name]
    finally:
        del code
        del frame

def myfunc(*parms):
    print _this_fn()

>>> myfunc(1)
<function myfunc at 0x036265F0>
>>> myfunc
<function myfunc at 0x036265F0>
BoarGules
  • 11
  • 1
  • Interesting, but this won't work, as you'll see if you replace the line `me = my_own_id` with `me = _this_fn()` in the test case I posted. Out of curiosity, why bother with `del code` and `del frame` (and with them the need for the `try...finally` structure) in `_this_fn`? – kjo Feb 18 '13 at 16:05
0

It's due to scope

>>> def foo():
...     x = foo
...     print x
... 
>>> foo()
<function foo at 0x10836e938>
>>> alias = foo
>>> alias()
<function foo at 0x10836e938>
>>> foo = None
>>> alias()
None
>>> foo = []
>>> alias()
[]
>>> del foo
>>> alias()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in foo
NameError: global name 'foo' is not defined
>>> 
Stefano Borini
  • 138,652
  • 96
  • 297
  • 431
0

Luke had an idea but didn't appear to develop it: use a mutable default parameter to hold the value in the function object. Default parameter values are evaluated only once, when the function is defined, and retain their previous value after that.

>>> def my_own_id(me=[None]):
    if not me[0]:
        me[0] = my_own_id
    return id(me[0])

>>> alias = my_own_id
>>> alias()
40330928
>>> my_own_id = None
>>> alias()
40330928

This requires care on your part to never call the function with a parameter.

Community
  • 1
  • 1
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622