3

I want a function to refer to itself. e.g. to be recursive.

So I do something like that:

def fib(n):
    return n if n <= 1 else fib(n-1)+fib(n-2)

This is fine most of the time, but fib does not, actually, refer to itself; it refers to the the binding of fib in the enclosing block. So if for some reason fib is reassigned, it will break:

>>> foo = fib
>>> fib = foo(10)
>>> x = foo(8)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in fib
TypeError: 'int' object is not callable

How can I prevent this from happening (from inside fib), if at all possible? As far as I know, the name of fib does not exist before the function-definition is fully executed; Are there any workarounds?

I don't have a real use case where it may actually happen; I am asking out of sheer curiosity.

nicael
  • 18,550
  • 13
  • 57
  • 90
Elazar
  • 20,415
  • 4
  • 46
  • 67

4 Answers4

7

I'd make a decorator for this

from functools import wraps

def selfcaller(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(wrapper, *args, **kwargs)
    return wrapper

And use it like

@selfcaller
def fib(self, n):
    return n if n <= 1 else self(n-1)+self(n-2)

This is actually a readable way to define a Fixed Point Combinator (or Y Combinator):

fix = lambda g: (lambda f: g(lambda arg: f(f)(arg))) (lambda f: g(lambda arg: f(f)(arg)))

usage:

fib = fix(lambda self: lambda n: n if n <= 1 else self(n-1)+self(n-2))

or:

@fix
def fib(self):
    return lambda n: n if n <= 1 else self(n-1)+self(n-2)

The binding here happens in the formal parameter, so the problem does not arise.

Elazar
  • 20,415
  • 4
  • 46
  • 67
Abe Karplus
  • 8,350
  • 2
  • 27
  • 24
  • The way I see it, what you are suggesting is a readable way to write a [Fixed-Point Combinator](http://stackoverflow.com/questions/16257858/recursively-decrement-a-list-by-1/16258081#16258081). Interesting. – Elazar May 29 '13 at 19:32
  • @Elazar Basically, yes. I haven't done a lot of pure-functional programming, so I didn't know the term. Python's decorator syntax does help in making the intent of the combinator a lot clearer. – Abe Karplus May 29 '13 at 19:34
  • Can I add a note about it to your answer? I like this connection. – Elazar May 29 '13 at 19:55
  • This isn't really the same; `selfcaller` works on the actual `fib` function, but the Y combinator works on a function that _returns_ the `fib` function. The binding happens in the formal parameter of that function-creating function. Which is just a local, and there's really no reason for it to be a parameter. Also, there's no reason for the inner function to be a `lambda` instead of a `def`—in which case you've already got your local name already. Which means the Y combinator adds nothing; all you need the deco to do is call the function-creating function. Which is… exactly my answer. – abarnert May 29 '13 at 20:23
  • @abarnert This way you don't not need the explicit deletion... I am thinking about the Y combinator. I am not sure if you are right, but if you *are* sure, feel free to edit my addition out - I don't want to be misleading. – Elazar May 29 '13 at 20:29
  • 1
    @abarnert Since lambda calculus considers a multi-argument function `(x,y) -> z` to be equivalent to the higher-order `x -> (y -> z)`, `selfcaller` is equivalent to the Y combinator. – Abe Karplus May 29 '13 at 20:33
  • Theoretically it's not exactly equivalent, but for the purpose of this discussion it is. The name `fib` is incorrect *before* the application of `@fix`, but it is correct after that. – Elazar May 29 '13 at 20:37
  • @AbeKarplus: Yes, but Python calling syntax doesn't work that way, so the Python implementations of `selfcaller` and `fix` are _not_ equivalent. – abarnert May 29 '13 at 20:38
  • Also, there's really no need for the explicit deletion in my answer either, just as there's no reason you can't explicitly stash or delete the original `fib` here. – abarnert May 29 '13 at 20:39
  • @AbeKarplus, do you see a way to define `fix` for a `fib(self, n)`? I can't see it. – Elazar May 29 '13 at 20:54
  • @Elazar I'm not sure quite what you're asking. Do you want `selfcaller` as a lambda function? – Abe Karplus May 29 '13 at 21:29
  • No. I wonder if `@fix` could be defined to receive `fib(self, n)`, just like selfcaller. – Elazar May 29 '13 at 21:31
  • @Elazar: Well, `fib(self, n)` doesn't return the result, it returns a function that returns the result. So, it's a question of lifting or lowering a parameter. It's easy to write a `fib(self, n)` that returns a function of no parameters that has `n` in its closure, but then `fix` has to return a function that puts its parameter into the closure of the wrapped function, instead of just passing it to the wrapped function. (This would be a lot easier to express in Haskell than English, if that's an option.) Which makes things more complicated, and I'm not sure what the benefit is. – abarnert May 29 '13 at 22:20
  • Haskell will be welcomed... I think the question is: what is the *intrinsic* difference between receiving a tuple and currying, for the sake of `fix`/`selfcaller`'s implementation. – Elazar May 30 '13 at 09:01
  • 1
    The intrinsic difference, from the implementation point of view, is that currying and uncurrying in Python aren't a no-op; each has to be done by explicitly, by creating another function (whether with partial, lambda, or def). Of course practically, in the case where you're already creating a function, you can sometimes collapse the extra one and the one you already have, but… I'm actually not sure how to describe that in theoretical terms. – abarnert May 30 '13 at 20:27
3

There's no way to do what you're trying to do. You're right that fib does not exist before the function definition is executed (or, worse, it exists but refers to something completely different…), which means there is no workaround from inside fib that can possibly work.*

However, if you're willing to drop that requirement, there are workarounds that do work. For example:

def _fibmaker():
    def fib(n):
        return n if n <= 1 else fib(n-1)+fib(n-2)
    return fib
fib = _fibmaker()
del _fibmaker

Now fib is referring to the binding in the closure from the local environment of a call to _fibmaker. Of course even that can be replaced if you really want to, but it's not easy (the fib.__closure__ attribute is not writable; it's a tuple, so you can't replace any of its cells; each cell's cell_contents is a readonly attribute, …), and there's no way you're going to do it by accident.

There are other ways to do this (e.g., use a special placeholder inside fib, and a decorator that replaces the placeholder with the decorated function), and they're all about equally unobvious and ugly, which may seem to violate TOOWTDI. But in this case, the "it" is something you probably don't want to do, so it doesn't really matter.


Here's one way you can write a general, pure-python decorator for a function that uses self instead of its own name, without needing an extra self parameter to the function:

def selfcaller(func):
    env = {}
    newfunc = types.FunctionType(func.__code__, globals=env)
    env['self'] = newfunc
    return newfunc

@selfcaller
def fib(n):
    return n if n <= 1 else self(n-1)+self(n-2)

Of course this won't work on a function that has any free variables that are bound from globals, but you can fix that with a bit of introspection. And, while we're at it, we can also remove the need to use self inside the function's definition:

def selfcaller(func):
    env = dict(func.__globals__)
    newfunc = types.FunctionType(func.__code__, globals=env)
    env[func.__code__.co_name] = newfunc
    return newfunc

This is Python 3.x-specific; some of the attribute names are different in 2.x, but otherwise it's the same.

This still isn't 100% fully general. For example, if you want to be able to use it on methods so they can still call themselves even if the class or object redefines their name, you need slightly different tricks. And there are some pathological cases that might require building a new CodeType out of func.__code__.co_code. But the basic idea is the same.


* As far as Python is concerned, until the name is bound, it doesn't exist… but obviously, under the covers, the interpreter has to know the name of the function you're defining. And at least some interpreters offer non-portable ways to get at that information.

For example, in CPython 3.x, you can very easily get the name of the function currently being defined—it's just sys._getframe().f_code.co_name.

Of course this won't directly do you any good, because nothing (or the wrong thing) is bound to that name. But notice that f_code in there. That's the current frame's code object. Of course you can't call a code object directly, but you can do so indirectly, either by generating a new function out of it, or by using bytecodehacks.

For example:

def fib2(n):
    f = sys._getframe()
    fib2 = types.FunctionType(f.f_code, globals=globals())
    return n if n<=1 else fib2(n-1)+fib2(n-2)

Again, this won't handle every pathological case… but the only way I can think of to do so is to actually keep a circular reference to the frame, or at least its globals (e.g., by passing globals=f.f_globals), which seems like a very bad idea.

See Frame Hacks for more clever things you can do.


Finally, if you're willing to step out of Python entirely, you can create an import hook that preprocesses or compiles your code from a Python custom-extended with, say, defrec into pure Python and/or bytecode.

And if you're thinking "But that sounds like it would be a lot nicer as a macro than as a preprocessor hack, if only Python had macros"… then you'll probably prefer to use a preprocessor hack that gives Python macros, like MacroPy, and then write your extensions as macros.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • (If it "exists but refers to something completely different" it will not make any difference - this name will be assigned shortly.) – Elazar May 29 '13 at 19:26
  • @Elazar: Well, yes, but the point is that either (a) there's no existing thing you _could_ bind to—or (b) there _is_, but it's something you don't _want_ to bind to. Either way, there is no way around the problem from inside `fib`. – abarnert May 29 '13 at 19:48
  • I see. I thought maybe there's some sort of reflection thing I am not familiar with... Your answer does seem like the straight-forward way. – Elazar May 29 '13 at 19:53
  • 1
    @Elazar: Well, from inside `fib` there's nothing to reflect on. But yes, from _outside_—e.g., in a decorator—you can get all kinds of information, such as a list of the unbound names in the function. (The [`inspect` module docs](http://docs.python.org/3.3/library/inspect.html) show all of the easily-reflectable stuff in a nice chart at the top.) And you could use that to make a decorator that doesn't require adding an explicit `self` parameter like Abe Karplus's, if you wanted. – abarnert May 29 '13 at 20:16
  • Isn't there anything like an access to the state of the interpreter? (Not that I would use anything like it in real life of course). – Elazar May 29 '13 at 20:20
  • Can you give an example for your last suggestion - not using an explicit self? – Elazar May 29 '13 at 20:21
  • 2
    @Elazar: If you want to write code that only works in CPython, as opposed to pure Python, then yes, you can use `sys._getframe(1)`. See [Frame Hacks](http://farmdev.com/src/secrets/framehack/) for some examples of what you can do. – abarnert May 29 '13 at 20:27
  • @Elazar: For not using an explicit self… the idea is to use `types.FunctionTypes` to build a new function with the original's code, but a special `globals` environment. Give me a sec and I'll hammer out the details. – abarnert May 29 '13 at 20:45
  • so it would only fit a global function, I guess. or maybe you can pass nonlocals too? – Elazar May 29 '13 at 20:56
  • @Elazar: Well, you can pass `closure`. From you interpreter, type `help(types.FunctionType)` and `help(types.CodeType)` for full details on what you can substitute. – abarnert May 29 '13 at 21:15
  • great read that was. That's why I like asking the most obvious of questions. That way I get a chance to learn a lot more and get ideas into right perspective. – Bleeding Fingers May 30 '13 at 20:12
2

Like abamert said "..there is no way around the problem from inside ..".

Here's my approach:

def fib(n):
    def fib(n):
        return n if n <= 1 else fib(n-1)+fib(n-2)
    return fib(n)
Community
  • 1
  • 1
Bleeding Fingers
  • 6,993
  • 7
  • 46
  • 74
  • @elazar it's surprising that the SO community actually allowed the code edit. Check [this](http://meta.stackexchange.com/questions/179655/clarification-of-edit-rejection#comment544033_179656). – Bleeding Fingers May 30 '13 at 06:37
  • If you are saying you are not fine with my edit, you can roll it back; I thought it will need your approval anyway. – Elazar May 30 '13 at 08:05
  • @Elazar nope. I am perfectly fine with it, it was a good edit. Just wondering how did they approve it. Lack of consistency. – Bleeding Fingers May 30 '13 at 08:42
  • I think this answer cries for macros (mentioned in the last part of @abarnert answer). – Elazar May 30 '13 at 08:56
  • @Elazar I have no clue of it(in python) nor [MacroPy](https://github.com/lihaoyi/macropy) that abamert mentioned. In fact I am currently in the middle of reading [this](http://www.gigamonkeys.com/book/macros-standard-control-constructs.html), so I am *assuming* reading python macro's along side would be great. – Bleeding Fingers May 30 '13 at 20:19
  • If you're interested in doing this with macros, and there isn't one already in the MacroPy examples, and you can't figure out how to do it yourself, contact co-author Haoyi Li and I'm willing to bet it'll be in the examples by tomorrow. – abarnert May 30 '13 at 20:20
  • 1
    Challenge accepted! I don't think it's general-purpose enough to go in the examples, but I'll post the snippet somewhere for people to see. – Li Haoyi May 31 '13 at 19:31
1

Someone asked me for a macro based solution for this, so here it is:

# macropy/my_macro.py
from macropy.core.macros import *

macros = Macros()

@macros.decorator()
def recursive(tree, **kw):
    tree.decorator_list = []

    wrapper = FunctionDef(
        name=tree.name,
        args=tree.args,
        body=[],
        decorator_list=tree.decorator_list
    )

    return_call = Return(
        Call(
            func = Name(id=tree.name),
            args = tree.args.args,
            keywords = [],
            starargs = tree.args.vararg,
            kwargs = tree.args.kwarg
        )
    )

    return_call = parse_stmt(unparse_ast(return_call))[0]

    wrapper.body = [tree, return_call]

    return wrapper

This can be used as follows:

>>> import macropy.core.console
0=[]=====> MacroPy Enabled <=====[]=0
>>> from macropy.my_macro import macros, recursive
>>> @recursive
... def fib(n):
...     return n if n <= 1 else fib(n-1)+fib(n-2)
...
>>> foo = fib
>>> fib = foo(10)
>>> x = foo(8)
>>> x
21

It basically does exactly the wrapping that hus787 gave:

  • Create a new statement which does return fib(...), which uses the argument list of the original function as the ...
  • Create a new def, with the same name, same args, same decorator_list as the old one
  • Place the old function, together followed by the return statement, in the body of the new functiondef
  • Strip the original function of its decorators (I assume you'd want to decorate the wrapper instead)

The parse_stmt(unparse_ast(return_call))[0] rubbish is a quick hack to get stuff to work (you actually can't just copy the argument AST from the param list of the function and use them in a Call AST) but that's just detail.

To show that it's actually doing that, you can add a print unparse_ast statement to see what the transformed function looks like:

@macros.decorator()
def recursive(tree, **kw):
    ...
    print unparse_ast(wrapper)
    return wrapper

which, when run as above, prints

def fib(n):

    def fib(n):
        return (n if (n <= 1) else (fib((n - 1)) + fib((n - 2))))
    return fib(n)

Looks like exactly what you want! It should work for any function, with multiple args, kwargs, defaults, etc., but I'm too lazy to test. Working with the AST is a bit verbose, and MacroPy is still super-experimental, but i think it's pretty neat.

Li Haoyi
  • 15,330
  • 17
  • 80
  • 137