Why do class methods always need 'self' as the first parameter?

Chris Torek nospam at torek.net
Thu Sep 1 04:20:56 CEST 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