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