Why do class methods always need 'self' as the first parameter?
Chris Torek
nospam at torek.net
Wed Aug 31 22:20:56 EDT 2011
In article <4e5ed670$0$29981$c3e8da3$5496439d at news.astraweb.com>
Steven D'Aprano <steve+comp.lang.python at pearwood.info> wrote:
>Er, yes, just like I suggested in my opening paragraph, and as I answered
>following the bit you marked as snipped :)
Oops, so you did (went back and re-read it). Must have gotten
interrupted and lost track. :-)
>> [A different hack would] requires a bit of implicit
>> sneakiness in the compiler: an instance method magically creates
>> a local variable named "self" that binds to the invisible first
>> parameter, and a class method magically creates a local variable
>> named "cls" that binds to the invisible first parameter, and so
>> on.
>It would need more than "a bit", because methods are just wrappers
>around functions.
Well, depends on how the hack would be done. :-) For instance,
the @decorator might turn on something that "undoes" or "replaces"
the "self" parameter. That is, with ordinary class functions and
methods:
class HackyNotQuitePythonVersion:
def ordinary(arg):
self.arg = arg
would compile to (approximately):
class PythonVersion:
def __mrap(self, *args, **kwargs):
def ordinary(arg):
self.arg = arg
ordinary(*args, **kwargs)
ordinary = __mrap
(add the usual other manipulations to suit here, i.e., all the
stuff for making introspection work right, i.e., @functools.wraps).
@staticmethod would suppress the wrapper entirely, while @classmethod
would change it to one that binds the "cls" argument. (Any function
without some appropriate @whatever gets the Method Wrapper __mrap.
@staticmethod tells the class builder not to add any wrapper, and
@classmethod tells it to add the Class Wrapper __crap. [The name
tells you what I think of the above code. :-) ])
(Note subtle ground for bugs here: if you then actually define a
"self" parameter, it shadows the outer-scope one from the wrapper.
So while I am not even proposing that anyone should do this in the
first place, it has more downsides than mere implementation
complexity.)
>Another way would be for the compiler to perform darkest black magic to
>determine whether the function was being called from inside a method or
>not. That would be complicated and fragile.
Yes, even worse than my outlined implementation above, I think.
>classmethod and staticmethod are functions, not declarations.
They are decorator functions, but to someone *reading the code*
they are also "declarations" of sort. This is all I meant: they
tell the (human) reader/programmer which "secret arguments" to
expect.
>You can't assume that @classmethod is the only way to get a
>class method: the metaclass could do it, or you could inject
>one in from the outside.
Yes, but that would all still work, as in this not-quite-Python
(worsened-Python) language, whoever writes those metaclasses and
other decorators would continue to do whatever icky stuff was
required (e.g., __mrap and __crap above). It would mean yet more
things for people to know about, but then, metaclasses and decorators
*always* mean that:
@hmm
def spam():
return magic
Is "magic" something supplied by the decorator? You have to look
at the decorator to find out, as the rather horrid example I have
attached shows.
(Note: I am doing all this is python 2.x on the laptop. Using
global, in @hmm, is evil, but it works. I did not bother trying
to write a metaclass that inserts __mrap, etc., but I believe it
can be done.)
>Python classes have a lot of dynamism made possible by the fact that methods
>are just wrappers around functions with an explicitly declared "self". That
>dynamism is rarely used, but not *that* rarely, and is very useful when
>used. Implicit self would likely negate all that.
I do not believe it would *negate* it, just *complicate* it. But
that is not a good thing either. :-)
----- horrible example / test code below
import functools
def hmm(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
global magic, rlevel
try:
save = magic, rlevel
restore = True
rlevel += 1
except NameError:
restore = False
rlevel = 1
magic = func.__name__ + " and eggs"
ret = func(*args, **kwargs)
if restore:
magic, rlevel = save
else:
del magic, rlevel
return ret
return wrapper
@hmm
def ham():
if rlevel < 2:
print spam()
return magic
@hmm
def spam():
return magic
print ham()
try:
print magic
except NameError:
print 'name "magic" is not available here, as desired'
try:
print rlevel
except NameError:
print 'name "rlevel" is not available here, as desired'
class X(object):
def __mrap(self, *args, **kwargs):
def xset(arg):
self.arg = arg
xset(*args, **kwargs)
xset = __mrap
def __mrap(self, *args, **kwargs):
def show():
print self.arg
show(*args, **kwargs)
show = __mrap
x = X()
x.xset('value')
x.show()
--
In-Real-Life: Chris Torek, Wind River Systems
Intel require I note that my opinions are not those of WRS or Intel
Salt Lake City, UT, USA (40°39.22'N, 111°50.29'W) +1 801 277 2603
email: gmail (figure it out) http://web.torek.net/torek/index.html
More information about the Python-list
mailing list