Self function

Steven D'Aprano steven at REMOVE.THIS.cybersource.com.au
Tue May 5 02:08:24 EDT 2009


On Mon, 04 May 2009 17:54:50 -0400, Terry Reedy wrote:

> bearophileHUGS at lycos.com wrote:
> 
>> Another possible syntax:
>> 
>> def fact(n):
>>     return 1 if n <= 1 else n * return(n - 1)
>> 
>> But I guess most people don't see this problem as important&common
>> enough to justify changing the language.
> 
> Actually, I would like a way to refer to the current function from
> inside a function.  but I would like support in the language, so that
> the compiler patched the code after the function object is created to
> directly refer to the function object (or can the code object call the
> code object?) without any name lookup at all.

I don't know about avoiding name lookup, that smacks of deepest black 
magic, and Python doesn't usually do that. It does however do parlour 
tricks like `__name__` and `self`, suggests a solution.

I propose a small piece of sugar. When a function is entered, Python 
creates an ordinary local name in the function's local namespace, and 
binds the function itself to that name. Two possibilities for the name 
are `this` or `__this__`, analogous to `self` in methods and `__name__` 
in modules.

If there is any support for this, I propose to send the following (long) 
post to python-ideas. Feedback, corrections and suggestions welcome.


* * * 


Summary:

Most objects in Python don't need to know what name, or names (if any) 
they are bound to. Functions are a conspicuous exception to that rule: 
there are good reasons for a function to refer to itself, and the only 
straightforward way to do so is by name. I propose that on calling a 
function, Python should create a local name which refers to the function 
itself, giving the programmer the ability to have a function refer to 
itself without using its name.


There are (at least) four reasons for a function to refer to itself. In 
no particular order:

(1) User-friendly messages (for logging, errors or other):

def parrot():
    print "Error feeding cracker to function %r" % parrot


(2) Recursion:

def spam(n):
    if n < 0: return spam(-n).upper()
    return "spam "*n


(3) Introspection:

def spanish_inquisition():
    print inspect.getmembers(spanish_inquisition)


(4) Storing data between function calls:

def cheeseshop():
    if hasattr(cheeseshop, 'cheeses'):
        print "We have many fine cheeses"
    else:
        print "I'm sorry, I have been wasting your time"


I call these self-reflective functions.

In some cases there are alternatives e.g. cheeseshop could be written as 
a functor object, or some recursive functions can be re-written as 
iteration. Nevertheless such self-reflective functions are acceptable to 
many people, and consequently in moderately common use. How to have a 
function refer to itself is a common question, e.g. for newbies:

http://www.mail-archive.com/tutor%40python.org/msg35114.html

and even more experienced programmers:

http://article.gmane.org/gmane.comp.python.general/622020

(An earlier draft of this proposal was sent to that thread.)


However, none of these functions are robust in the face of the function 
being renamed, either at runtime or in the source code. In general, this 
will fail for any of the above functions:

func = spam
del spam
func()

Self-reflective functions like these are (almost?) unique in Python in 
that they require a known name to work correctly. You can rename a class, 
instance or module and expect it to continue to work, but not so for such 
functions. When editing source code, it is very easy to forget to change 
all the references to the function name within the body of itself 
function, even for small functions, and refactoring tools are overkill.

My proposal is for Python to automatically generate a local variable 
named either `this` or `__this__` when entering a function. This will 
allow any of the above functions to be re-written using the special name, 
and become robust against name changes.

def spanish_inquisition():
    print inspect.getmembers(__this__)

fang = spanish_inquisition
del spanish_inquisition
fang()


It will also allow lambda to use recursion:

lambda n: 0 if n <= 1 else __this__(n-1)

(This may count as a point against it, for those who dislike lambda.)


It is often useful to create two similar functions, or a variant of a 
function, without duplicating the source code. E.g. you might want a 
function that uses a cache, while still keeping around the version 
without a cache:

cached_function = memoize(function)
    
Currently, this does not give the expected results for recursive 
functions, nor does it give an error. It simply fails to behave as 
expected. Re-writing function() to use __this__ for the recursive call 
will solve that problem.


Q: Will `__this__` or `this` clash with existing variables?

I prefer `this` over `__this__`, for analogy with `self` inside methods. 
However it is more likely that existing code already uses the name 
`this`, since double-leading-and-trailing-underscore names are reserved 
for use by Python. Because `this` is an ordinary local variable, if a 
function assigns to it the function will work as expected. The only 
meaningful clash I can think of will occur when a function refers to a 
non-local name `this` without declaring it first. Another obvious clash 
may be:

def function():
    import this
    ...

Both of these are likely to be very rare.

Q: Will there be a performance penalty if every function defines the name 
`__this__` on entry?

I don't expect so, but if it is found to be a problem, a slightly more 
sophisticated strategy would be to only define `__this__` inside the 
function if the body of the function refers to that variable. That will 
allow the majority of functions which are not self-reflective to avoid 
paying that (probably minuscule) cost.


Q: Is this really necessary?

This is sugar, a convenience for the programmer. None of the problems it 
solves cannot be solved, or worked around, by other methods. 
Nevertheless, that the question "how do I have my function refer to 
itself?" keeps being asked over and over again suggests that the current 
solutions are (1) not obvious to newbies, and (2) not entirely 
satisfactory to more experienced programmers.


Here is a proof-of-concept pure Python implementation, using a decorator, 
and abusing a global variable. This is NOT to imply that `__this__` 
should be a global if this proposal goes ahead.


from functools import wraps
def make_this(func):
    global __this__
    __this__ = func
    @wraps(func)
    def f(*args, **kwargs):
        return func(*args, **kwargs)
    return f


>>> @make_this
... def spam(n):
...     if n < 0: return __this__(-n).upper()
...     return ' '.join([__this__.__name__] * n)
...
>>> tasty_meat_like_product = spam
>>> del spam
>>> tasty_meat_like_product(-3)
'SPAM SPAM SPAM'



-- 
Steven



More information about the Python-list mailing list