How to peek inside a decorated function

S

Steven D'Aprano

Suppose I have a function f() which I know has been decorated, but I don't
have access to the original undecorated function any longer:

def reverse(func):
def f(*args):
args = list(args)
args.reverse()
return func(*args)
return f

def say(*args):
print args

rsay = reverse(say)
del say


Is there any way to peek inside the decorated function rsay() to get access
to the undecorated function say()?

If I look at the code object I can see a reference to the original:
('list', 'args', 'reverse', 'func')

and if I disassemble the code object I can see it being dereferenced:
[snip for brevity]
5 22 LOAD_DEREF 0 (func)
25 LOAD_FAST 0 (args)
28 CALL_FUNCTION_VAR 0
31 RETURN_VALUE

but if I look at the closure object, nothing seems useful:
['__class__', '__cmp__', '__delattr__', '__doc__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__str__']


and I can't find any other attributes which refers back to the undecorated
original function.
 
T

Terry Reedy

Steven said:
Suppose I have a function f() which I know has been decorated, but I don't
have access to the original undecorated function any longer:

def reverse(func):
def f(*args):
args = list(args)
args.reverse()
return func(*args)
return f

def say(*args):
print args

rsay = reverse(say)
del say


Is there any way to peek inside the decorated function rsay() to get access
to the undecorated function say()?

If I look at the code object I can see a reference to the original:
('list', 'args', 'reverse', 'func')

and if I disassemble the code object I can see it being dereferenced:
[snip for brevity]
5 22 LOAD_DEREF 0 (func)
25 LOAD_FAST 0 (args)
28 CALL_FUNCTION_VAR 0
31 RETURN_VALUE

but if I look at the closure object, nothing seems useful:
dir(rsay.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__str__']


and I can't find any other attributes which refers back to the undecorated
original function.

In 3.0, rsay.__closure__[0].cell_contents would be the original function
object.
 
P

Peter Otten

Steven said:
Suppose I have a function f() which I know has been decorated, but I don't
have access to the original undecorated function any longer:

def reverse(func):
def f(*args):
args = list(args)
args.reverse()
return func(*args)
return f

def say(*args):
print args

rsay = reverse(say)
del say


Is there any way to peek inside the decorated function rsay() to get
access to the undecorated function say()?

If I look at the code object I can see a reference to the original:
('list', 'args', 'reverse', 'func')

and if I disassemble the code object I can see it being dereferenced:
[snip for brevity]
5 22 LOAD_DEREF 0 (func)
25 LOAD_FAST 0 (args)
28 CALL_FUNCTION_VAR 0
31 RETURN_VALUE

but if I look at the closure object, nothing seems useful:
dir(rsay.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__str__']


and I can't find any other attributes which refers back to the undecorated
original function.

Following Terry's lead it turns out that cell contents become easily
accessible in python 2.5:

$ cat cellcontents.py
""".... def f(*args):
.... args = list(args)
.... args.reverse()
.... return func(*args)
.... return f
........ print args
....
rsay = reverse(say)
del say
c = rsay.func_closure[0]
"cell_contents" in dir(c) True
c.cell_contents(1,2,3) (1, 2, 3)
"""

import doctest
doctest.testmod()
$ python2.6 cellcontents.py
$ python2.5 cellcontents.py
$ python2.4 cellcontents.py
**********************************************************************
File "cellcontents.py", line 15, in __main__
Failed example:
"cell_contents" in dir(c)
Expected:
True
Got:
False
**********************************************************************
File "cellcontents.py", line 17, in __main__
Failed example:
c.cell_contents(1,2,3)
Exception raised:
Traceback (most recent call last):
File "doctest.py", line 1248, in __run
compileflags, 1) in test.globs
File "<doctest __main__[6]>", line 1, in ?
c.cell_contents(1,2,3)
AttributeError: 'cell' object has no attribute 'cell_contents'
**********************************************************************
1 items had failures:
2 of 7 in __main__
***Test Failed*** 2 failures.

Peter
 
H

Hrvoje Niksic

Steven D'Aprano said:
Suppose I have a function f() which I know has been decorated, but I don't
have access to the original undecorated function any longer:

def reverse(func):
def f(*args):
args = list(args)
args.reverse()
return func(*args)
return f

def say(*args):
print args

rsay = reverse(say)
del say

Is there any way to peek inside the decorated function rsay() to get access
to the undecorated function say()?

This works in Python 2.5.2:
rsay.func_closure[0].cell_contents
<function say at 0xb7e67224>

Of course, this applies only if you know there's only one free
variable, and you know that the decorator is in fact implemented with
a closure, and so on.
dir(rsay.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__str__']

I got 'cell_contents' as well when I did the 'dir', at least under
Python 2.5.2.
 
A

Aaron Brady

Steven D'Aprano said:
Suppose I have a function f() which I know has been decorated, but I don't
have access to the original undecorated function any longer:
def reverse(func):
    def f(*args):
        args = list(args)
        args.reverse()
        return func(*args)
    return f
def say(*args):
    print args
rsay = reverse(say)
del say
Is there any way to peek inside the decorated function rsay() to get access
to the undecorated function say()?

This works in Python 2.5.2:
rsay.func_closure[0].cell_contents

<function say at 0xb7e67224>

Of course, this applies only if you know there's only one free
variable, and you know that the decorator is in fact implemented with
a closure, and so on.
dir(rsay.func_closure[0])
['__class__', '__cmp__', '__delattr__', '__doc__',
'__getattribute__', '__hash__', '__init__', '__new__', '__reduce__',
'__reduce_ex__', '__repr__', '__setattr__', '__str__']

I got 'cell_contents' as well when I did the 'dir', at least under
Python 2.5.2.

Assume the decorator preserves the function at all; that is, it
doesn't discard it. Then you can scan func_closure. In poor cases,
you might have to search through a nested decoration as well. In
others, such as when the decorator is an object, not a function, you
might have to search an instance dictionary, also possibly nested.
The leading assumption also requires that the function is stored in a
Python object; that is, not in a C structure.

In Python 3.0.1:
.... def g( *ar, **kw ):
.... return fun( *ar, **kw )
.... return g
........ print( x )
....
i= f( h )
i.__closure__[0].cell_contents
<function h at 0x00B59810>
 
S

Steven D'Aprano

Hrvoje said:
Is there any way to peek inside the decorated function rsay() to get
access to the undecorated function say()?

This works in Python 2.5.2:
rsay.func_closure[0].cell_contents
<function say at 0xb7e67224>

Thanks to everyone who responded.


The reason I ask is because I've just spent the weekend battling with
doctests of decorated functions, and discovering that without
functools.wraps() all my doctests weren't being called. I'm wondering
whether it would be a good idea for doctest to auto-detect tests in
closures.

This is not needed if you call wraps() appropriately, but you might not
always do that.
 
P

Peter Otten

Steven said:
Suppose I have a function f() which I know has been decorated, but I don't
have access to the original undecorated function any longer:

def reverse(func):
def f(*args):
args = list(args)
args.reverse()
return func(*args)
return f

def say(*args):
print args

rsay = reverse(say)
del say


Is there any way to peek inside the decorated function rsay() to get
access to the undecorated function say()?

Here's a hack for Python 2.4:

def make_extractor(x=None):
def extractor(): return x
return extractor

extractor = make_extractor()
function = type(extractor)

def get_cell_contents(cell):
return function(extractor.func_code, {}, "yadda", None, (cell,))()

get_cell_contents(rsay.func_closure[0])(1,2,3)

Peter
 

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,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top