I suspect that this cannot be done in Python 2.7, at least not directly. Here's the pretty printed content of your f
function, minus some explicitly unrelated entries:
>>> pprint({key: getattr(f, key) for key in dir(f)}) {'__call__': <method-wrapper '__call__' of function object at 0x7f9d22aefb18>, '__class__': <type 'function'>, '__closure__': (<cell at 0x7f9d22af1fd8: int object at 0x1311128>,), '__code__': <code object func at 0x7f9d22d3b230, file "<stdin>", line 2>, '__defaults__': None, '__dict__': {}, '__doc__': None, '__module__': '__main__', '__name__': 'func', 'func_closure': (<cell at 0x7f9d22af1fd8: int object at 0x1311128>,), 'func_code': <code object func at 0x7f9d22d3b230, file "<stdin>", line 2>, 'func_defaults': None, 'func_dict': {}, 'func_doc': None, 'func_globals': {'__builtins__': <module '__builtin__' (built-in)>, '__doc__': None, '__name__': '__main__', '__package__': None, 'creator': <function creator at 0x7f9d22aefaa0>, 'f': <function func at 0x7f9d22aefb18>, 'pprint': <function pprint at 0x7f9d22aefc08>}, 'func_name': 'func'}
The only interesting keys are func_closure
(which is __closure__
) and func_code
(which is __code__
), but none of them help.
Closing is a collection of cell
objects, each of which contains the value of a key-value pair in a closed environment. There is only one value in f.func_closure
, and the value of the variable par
:
>>> repr(f.func_closure[0]) <cell at 0x7f9d22af1fd8: int object at 0x1311128> >>> f.func_closure[0].cell_contents 7
A cell
does not contain a reference to the creator of the closure, functions using the closure, or even the closed environment itself. (Elements of a closed environment are apparently extracted based on their position in the tuple of cell
objects.)
The function code object is approaching, but also does not name its creator. Minus obviously non-essential entries, it contains:
>>> pprint({k: getattr(f.func_code, k) for k in dir(f.func_code)}) {'__class__': <type 'code'>, '__doc__': 'code(argcount, nlocals, stacksize, flags, codestring, constants, names,\n varnames, filename, name, firstlineno, lnotab[, freevars[, cellvars]])\n\nCreate a code object. Not for the faint of heart.', 'co_argcount': 1, 'co_cellvars': (), 'co_code': '\x88\x00\x00|\x00\x00\x17S', 'co_consts': (None,), 'co_filename': '<stdin>', 'co_firstlineno': 2, 'co_flags': 19, 'co_freevars': ('par',), 'co_lnotab': '\x00\x01', 'co_name': 'func', 'co_names': (), 'co_nlocals': 1, 'co_stacksize': 2, 'co_varnames': ('arg',)}
It contains the name of the private variable ( 'co_freevars': ('par',),
) and the name of the function inside creator
( 'co_name': 'func',
), but not the name or any link to an external function.
Partial solution
There is a way to identify the external function of a private function if you have a link to both. The object code object of a function function will contain a link to a code object with a private function:
>>> pprint({k: getattr(creator.func_code, k) for k in dir(creator.func_code)}) {'__class__': <type 'code'>, '__doc__': 'code(argcount, nlocals, stacksize, flags, codestring, constants, names,\n varnames, filename, name, firstlineno, lnotab[, freevars[, cellvars]])\n\nCreate a code object. Not for the faint of heart.', 'co_argcount': 1, 'co_cellvars': ('par',), 'co_code': '\x87\x00\x00f\x01\x00d\x01\x00\x86\x00\x00}\x01\x00|\x01\x00S', 'co_consts': (None, <code object func at 0x7f9d22d3b230, file "<stdin>", line 2>), 'co_filename': '<stdin>', 'co_firstlineno': 1, 'co_flags': 3, 'co_freevars': (), 'co_lnotab': '\x00\x01\x0f\x02', 'co_name': 'creator', 'co_names': (), 'co_nlocals': 2, 'co_stacksize': 2, 'co_varnames': ('par', 'func')}
You can determine that creator
is the source of f
because the tuple creator.func_code.co_consts
contains a link to f.func_code
:
>>> f.func_code in creator.func_code.co_consts True >>> f.func_code is creator.func_code.co_consts[1] True
The same code object is used by every function returned by creator
(their differences are stored in cell
objects, not in code objects):
>>> g = creator(10) >>> g.func_code is f.func_code is creator.func_code.co_consts[1] True
So, if you can narrow down potential sources down to say values โโin globals()
or in dir(some_class)
, you can check each of them to find out if this is the โparentโ of f
:
def is_creator(f, contender): target = f.func_code try: constants = contender.func_code.co_consts except AttributeError: return False for constant in constants: if constant is target: return True return False def find_creators(f, contenders): for contender in contenders: if is_creator(f, contender): yield contender return >>> is_creator(f, creator) True >>> is_creator(g, creator) True >>> is_creator(f, max) False >>> is_creator(f, g) False >>> is_creator(f, 'Seriously?') False >>> is_creator(f, None) False >>> list(find_creators(f, globals().values())) [<function creator at 0x7f9d22aefaa0>] >>> builtins = [getattr(__builtins__, s) for s in dir(__builtins__)] >>> list(find_creators(f, builtins)) []
This kind of sucks, in fact, because it does not point you to the creator, it simply identifies the creator if you have already found it. Alternatively, it can be fooled if someone uses creator.__code__
to create an impostor:
def impostor(bogus): def need_a_free_variable_in_impostors_func_code(unused): return bogus - unused return need_a_free_variable_in_impostors_func_code >>> creator(3)(7) 10 >>> impostor(3)(7) -4 >>> is_creator(f, impostor) False >>> impostor.__code__ = creator.__code__ >>> impostor(3)(7) 10 >>> is_creator(f, impostor) True >>> list(find_creators(f, globals().values())) [<function creator at 0x7f9d22aefaa0>, <function impostor at 0x7f9d1bf7f8c0>]
Doubtful Improvements
There are other clues as soon as a potential creator is found, but they are not really evidence. Examples include:
The name of the private variable 'par'
appears both in f
as a free variable, and in creator
as a variable "cell":
>>> f.func_code.co_freevars[0] in creator.func_code.co_cellvars True
The name f
(which is 'func'
, not 'f'
) appears in the creator function code object. (Function code objects are immutable, so f.func_code.co_name
must be the original name assigned to f
when it was created. f.__name__
could be reassigned since then. So f.func_code
--- the whole code object --- but this is nowhere near common.)
>>> f.func_code.co_name in creator.func_code.co_varnames True
Since function definitions can be deeply nested, that is, various free variables in the innermost function can be defined (written in co_cellvars
) in different external functions --- I donโt think adding checks for them will make is_creator
any smarter.