Class method differences in Python: bound, unbound and static
In Python, there is a distinction between bound and unbound methods.
Basically, a call to a member function (like method_one
), a bound function
a_test.method_one()
is translated to
Test.method_one(a_test)
i.e. a call to an unbound method. Because of that, a call to your version of method_two
will fail with a TypeError
>>> a_test = Test()
>>> a_test.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
You can change the behavior of a method using a decorator
class Test(object):
def method_one(self):
print "Called method_one"
@staticmethod
def method_two():
print "Called method two"
The decorator tells the built-in default metaclass type
(the class of a class, cf. this question) to not create bound methods for method_two
.
Now, you can invoke static method both on an instance or on the class directly:
>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
What is the difference between a function, an unbound method and a bound method?
A function is created by the def
statement, or by lambda
. Under Python 2, when a function appears within the body of a class
statement (or is passed to a type
class construction call), it is transformed into an unbound method. (Python 3 doesn't have unbound methods; see below.) When a function is accessed on a class instance, it is transformed into a bound method, that automatically supplies the instance to the method as the first self
parameter.
def f1(self):
pass
Here f1
is a function.
class C(object):
f1 = f1
Now C.f1
is an unbound method.
>>> C.f1
<unbound method C.f1>
>>> C.f1.im_func is f1
True
We can also use the type
class constructor:
>>> C2 = type('C2', (object,), {'f1': f1})
>>> C2.f1
<unbound method C2.f1>
We can convert f1
to an unbound method manually:
>>> import types
>>> types.MethodType(f1, None, C)
<unbound method C.f1>
Unbound methods are bound by access on a class instance:
>>> C().f1
<bound method C.f1 of <__main__.C object at 0x2abeecf87250>>
Access is translated into calling through the descriptor protocol:
>>> C.f1.__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
Combining these:
>>> types.MethodType(f1, None, C).__get__(C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf87310>>
Or directly:
>>> types.MethodType(f1, C(), C)
<bound method C.f1 of <__main__.C object at 0x2abeecf871d0>>
The main difference between a function and an unbound method is that the latter knows which class it is bound to; calling or binding an unbound method requires an instance of its class type:
>>> f1(None)
>>> C.f1(None)
TypeError: unbound method f1() must be called with C instance as first argument (got NoneType instance instead)
>>> class D(object): pass
>>> f1.__get__(D(), D)
<bound method D.f1 of <__main__.D object at 0x7f6c98cfe290>>
>>> C.f1.__get__(D(), D)
<unbound method C.f1>
Since the difference between a function and an unbound method is pretty minimal, Python 3 gets rid of the distinction; under Python 3 accessing a function on a class instance just gives you the function itself:
>>> C.f1
<function f1 at 0x7fdd06c4cd40>
>>> C.f1 is f1
True
In both Python 2 and Python 3, then, these three are equivalent:
f1(C())
C.f1(C())
C().f1()
Binding a function to an instance has the effect of fixing its first parameter (conventionally called self
) to the instance. Thus the bound method C().f1
is equivalent to either of:
(lamdba *args, **kwargs: f1(C(), *args, **kwargs))
functools.partial(f1, C())
Differences between methods of a class, which are function and which are bound method ?
This answer will be really technical, I hope it's still understandable though. The problem is that it requires knowledge of the descriptor protocol to understand how methods in Python work.
All functions in Python 3 are descriptors, to be precise they are non-data descriptors. That means they implements a __get__
method - but no __set__
method.
That's interesting because descriptors can do (almost) anything if they are looked up on a class or an instance.
By the definition of the __get__
method in Pythons data model:
object.__get__(self, instance, owner)
Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access).
owner
is always the owner class, whileinstance
is the instance that the attribute was accessed through, orNone
when the attribute is accessed through theowner
. This method should return the (computed) attribute value or raise anAttributeError
exception.
So what does this have to do with the difference between function
and bound_method
?
It's easy, a function accessed through __get__
with an instance=None
will return itself:
>>> def func(x): return x
>>> func.__get__(None, object)
<function __main__.func>
>>> func.__get__(None, object) is func
True
While it will be a bound_method
if accessed with an not-None instance:
>>> func.__get__(object())
<bound method func of <object object at 0x00000155614A0610>>
It's basically just a wrapper around func
with the instance stored:
>>> m = func.__get__(object())
>>> m.__self__ # stored instance
<object at 0x155614a0650>
>>> m.__func__ # stored function
<function __main__.func>
However, when called, it will pass the instance as first argument to the wrapped function:
>>> m()
<object at 0x155614a0650>
So, bound method
s will pass the instance as first argument, while function
s do not (they requires all attributes).
So when you look at a class all normal methods will display as functions while all normal methods on an instance will be bound methods
.
Why did I mention normal methods? Because you can define arbitrary descriptors. For example the Python built-ins already contain several exceptions:
classmethod
staticmethod
property
(this is in fact a data-descriptor so I'll neglect it in the following discussion)
classmethod
s will display as bound method
even when looked up on the class. That's because they are meant to be callable on the class and pass the class to the function, no matter if they are called on the class or the instance:
class Test(object):
@classmethod
def func(x):
return x
>>> Test.func
<bound method Test.func of <class '__main__.Test'>>
>>> Test().func
<bound method Test.func of <class '__main__.Test'>>
And staticmethod
s always display as functions because they never pass anything additional to the function:
class Test(object):
@staticmethod
def func(x):
return x
>>> Test().func
<function __main__.Test.func>
>>> Test.func
<function __main__.Test.func>
So it's easily possible to see also bound method
s on the class (e.g. classmethod
s) and likewise one could also find normal function
s on instances (e.g. staticmethod
s).
Difference between @staticmethod and @classmethod
Maybe a bit of example code will help: Notice the difference in the call signatures of foo
, class_foo
and static_foo
:
class A(object):
def foo(self, x):
print(f"executing foo({self}, {x})")
@classmethod
def class_foo(cls, x):
print(f"executing class_foo({cls}, {x})")
@staticmethod
def static_foo(x):
print(f"executing static_foo({x})")
a = A()
Below is the usual way an object instance calls a method. The object instance, a
, is implicitly passed as the first argument.
a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>, 1)
With classmethods, the class of the object instance is implicitly passed as the first argument instead of self
.
a.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)
You can also call class_foo
using the class. In fact, if you define something to be
a classmethod, it is probably because you intend to call it from the class rather than from a class instance. A.foo(1)
would have raised a TypeError, but A.class_foo(1)
works just fine:
A.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)
One use people have found for class methods is to create inheritable alternative constructors.
With staticmethods, neither self
(the object instance) nor cls
(the class) is implicitly passed as the first argument. They behave like plain functions except that you can call them from an instance or the class:
a.static_foo(1)
# executing static_foo(1)
A.static_foo('hi')
# executing static_foo(hi)
Staticmethods are used to group functions which have some logical connection with a class to the class.
foo
is just a function, but when you call a.foo
you don't just get the function,
you get a "partially applied" version of the function with the object instance a
bound as the first argument to the function. foo
expects 2 arguments, while a.foo
only expects 1 argument.
a
is bound to foo
. That is what is meant by the term "bound" below:
print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>
With a.class_foo
, a
is not bound to class_foo
, rather the class A
is bound to class_foo
.
print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>
Here, with a staticmethod, even though it is a method, a.static_foo
just returns
a good 'ole function with no arguments bound. static_foo
expects 1 argument, anda.static_foo
expects 1 argument too.
print(a.static_foo)
# <function static_foo at 0xb7d479cc>
And of course the same thing happens when you call static_foo
with the class A
instead.
print(A.static_foo)
# <function static_foo at 0xb7d479cc>
Class method differences in Python: bound, unbound and static
In Python, there is a distinction between bound and unbound methods.
Basically, a call to a member function (like method_one
), a bound function
a_test.method_one()
is translated to
Test.method_one(a_test)
i.e. a call to an unbound method. Because of that, a call to your version of method_two
will fail with a TypeError
>>> a_test = Test()
>>> a_test.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)
You can change the behavior of a method using a decorator
class Test(object):
def method_one(self):
print "Called method_one"
@staticmethod
def method_two():
print "Called method two"
The decorator tells the built-in default metaclass type
(the class of a class, cf. this question) to not create bound methods for method_two
.
Now, you can invoke static method both on an instance or on the class directly:
>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two
What's the difference between a 'function', 'method' and 'bound method' in Python 3?
Is 'method' a type equivalent to 'unbound method' in Python 2?
Kind-a-sort-a. But not really. It is a method_descriptor
object defined in C code. It is an unbound method, but not the kind you found in Python 2.
For Python types written C, all 'methods' are really C functions. The <method 'name' of 'type' objects>
object you found is a special object you can use to call that function given an instance and further arguments, just like the function
object does for custom Python classes. The object is defined in C in the PyMethodDescr_Type
structure. It implements the descriptor protocol, just like functions do.
Python defines several other such descriptor types; if you use __slots__
, each attribute is a dsescriptor of type member_descriptor
(see the PyMemberDescr_Type
structure), while classmethod
, property
and staticmethod
are perhaps better known descriptor objects.
In Python 2, bound and unbound methods are really just one type, instancemethod
(defined by the PyMethod_Type
structure); it'll report as bound if the __self__
(im_self
) attribute is set. In Python 3 using a function as a descriptor simply doesn't produce method objects without __self__
set; instead calling function.__get__()
with no instance just returns the function again.
The only reason Python 2 returns unbound methods is to enforce a type check; the first argument must be an instance of the class (or a subclass thereof). This didn't make all that much sense for Python code that supports duck-typing, so in Python 3 the restriction was removed. However, with C code you can't use duck-typing, you still have to restrict the type, and that's why C-types still return a method_descriptor
object that enforces this restriction.
Related Topics
How to Tail a Log File in Python
How to Preserve Timezone When Parsing Date/Time Strings with Strptime()
How to Use String.Replace() in Python 3.X
Imploding a List for Use in a Python MySQLdb in Clause
How to Merge Multiple Dicts with Same Key or Different Key
Python: Changes to My Copy Variable Affect the Original Variable
Indexing One Array by Another in Numpy
Checking Whole String with a Regex
Why Does Python Code Run Faster in a Function
How Does Asyncio Actually Work
Label Encoding Across Multiple Columns in Scikit-Learn
Python: List VS Dict for Look Up Table
How to Find the Mime Type of a File in Python
Python Subprocess/Popen with a Modified Environment
Handling Very Large Numbers in Python