1

I'm using Python 2.7 (can't upgrade). I'm trying to store a function in my class, which can be overridden with a different function in the ctor. The function is a normal, static function (not a member or class method) so I don't want to pass the class or object as the first parameter when I invoke it. For some reason, Python always passes the object as the first parameter. How do I avoid this?

Example:

def fn(*args):
    print str(args)

class MyClass(object):
    myfn = fn

    def __init__(self, myfn=None):
        if myfn is not None:
            self.myfn = myfn

    def run(self, arg):
        self.myfn(arg)


o = MyClass()
o.run("foo")

When I run this, I get the object as the first argument to fn:

$ python /tmp/testfn.py
(<__main__.MyClass object at 0x7f7fff8dc090>, 'foo')

If I don't add the class member myfn and instead only assign the value in the ctor then it works as expected:

    def __init__(self, myfn=None):
        self.myfn = fn if myfn is None else myfn

$ python /tmp/testfn.py
('foo',)

But, I don't want to do this because I want to be able to reset the function for the entire class so all future objects automatically get the new function, in addition to being able to reset it for an individual object.

I don't understand why the class member value behaves differently. Is there any way I can convince python that this function is a static function (don't pass the object as the first argument)?

MadScientist
  • 92,819
  • 9
  • 109
  • 136

2 Answers2

4

is there any way I can convince python that this function is a static function (don't pass the object as the first argument)?

Sure, just tell python that it's a staticmethod:

class MyClass(object):
    myfn = staticmethod(fn)

Demo:

>>> def fn(*args):
...     print str(args)
...
>>> class MyClass(object):
...     myfn = staticmethod(fn)
...     def run(self, arg):
...       self.myfn(arg)
... 
>>> o = MyClass()
>>> o.run('foo')
('foo',)
Adam Smith
  • 52,157
  • 12
  • 73
  • 112
mgilson
  • 300,191
  • 65
  • 633
  • 696
2

The reason is this: Before a class definition is executed, a dict is created for the class namespace, and assignments (like your myfn=fn) go into that dict. Function definitions (the member functions) create ordinary function objects, and references to these are also placed in the same dict.

When a method is called on an instance, and the attribute lookup (e.g. obj.method) resolves to a function in the class dict, that is treated as a method call - the attribute lookup actually constructs a bound-method object, which binds the instance to the method, so that calling that object inserts the extra self parameter before calling the actual function.

Functions marked @staticmethod are not processed the same way - there is a wrapper around them when they are initially placed in the class namespace, and when the class is constructed, these wrappers are removed, but some record is made within the class object which prevents these from being turned into bound-methods when they are looked up as the attribute of an instance.

So, the assignment to myfn in the class gives the same result as defining the function in the class - it becomes a member function in the class. It is common to duplicate members in this way:

class xyz:
    def __repr__(self):
          # ....
    __str__ = __repr__

If you put MyClass.myfn = fn after the class definition (not indented), it will still act is if myfn was a method of the class.

So, @staticmethod is the usual and proper way to make a static method. But staticmethod is itself a function: so if the function you have is actually defined outside the class already, you can do this:

class MyClass:
    myfn = staticmethod(fn)

... since that has the same effect as putting @staticmethod ahead of a function - the newly created function is just passed to staticmethod and the result is placed in the namespace instead. In the above, MyClass.myfn and MyClass().myfn will both give the same function object as fn, which is not the same object that was assigned to myfn during the execution of the class declaration; that object was only used to trigger the marking of myfn as static when the class was constructed.

greggo
  • 3,009
  • 2
  • 23
  • 22
  • Ah, excellent explanation. Magic debunked. However I kind of prefer the explicit `staticmethod()` function to the assignment outside of the class method... – MadScientist Dec 09 '15 at 17:41
  • 1
    Ah. For some reason it didn't occur to me that you could add that decorator to a plain function, outside of a class... – MadScientist Dec 09 '15 at 17:43
  • Are you sure about your explanation here? I was feeling skeptical so I tried it and I'm still getting `self` passed. The reason is because python _doesn't_ do any magical processing of the methods during class creation. The special magic happens when the method is accessed. Function binding is implemented via descriptors (the `__get__` method) and the `__get__` method of a `staticmethod` chooses to not pass `self` to the delegate function. (I'm on python2.x and used a new-style class like OP -- Maybe old-style classes behave differently?) – mgilson Dec 09 '15 at 17:45
  • @mgilson oops - I did a test, but I used a builtin (min) rather than a python func, so I guess that was invalid. Unfortunately don't have time to fix this right now, will get to it soon thx – greggo Dec 09 '15 at 17:48