Bound Methods vs Unbound Functions in Python 3
In Python 3, the distinction between bound methods and unbound
functions comes down to how a function is accessed and whether an
instance is automatically supplied as its first argument. When a
function defined inside a class is accessed through an instance, it
becomes a bound method that automatically binds the instance to
self. When accessed directly through the class, it remains
a standard, plain function—eliminating the Python 2 concept of "unbound
methods" entirely. This article breaks down the mechanics, behavioral
differences, and underlying descriptor protocol that govern these two
callables.
What Is a Bound Method?
A bound method is a method that is dependent on an instance of a class. When you call a method through an object instance, Python automatically packages the instance and the function together.
class Greeter:
def greet(self):
return "Hello!"
obj = Greeter()
bound_greet = obj.greet
print(type(bound_greet)) # <class 'method'>Because the method is bound to obj, calling
bound_greet() automatically passes obj as the
first argument (self).
Bound method objects have special read-only attributes:
__self__: References the class instance to which the method is bound.__func__: References the original underlying function object.
What Is an Unbound Function?
In Python 2, accessing a method via the class (e.g.,
Greeter.greet) returned an "unbound method" type that
enforced type-checking on the first argument. Python 3 eliminated this
type.
In Python 3, retrieving a method directly from a class returns a standard function:
unbound_greet = Greeter.greet
print(type(unbound_greet)) # <class 'function'>Because it is simply a function, it is not tied to any instance. To invoke it, you must explicitly pass an instance as the argument:
# Calling the function directly requires passing the instance
print(Greeter.greet(obj)) # Output: Hello!If you call Greeter.greet() without an argument, Python
raises a TypeError indicating that the required positional
argument self is missing.
The Underlying Mechanism: The Descriptor Protocol
Functions in Python act as descriptors by implementing the
__get__ method. The transformation from a function to a
bound method occurs dynamically at runtime during attribute access:
Access via an instance (
obj.greet): Python callsGreeter.__dict__['greet'].__get__(obj, Greeter). Because an instance (obj) is provided,__get__returns a bound method object wrapping both the function and the instance.Access via the class (
Greeter.greet): Python callsGreeter.__dict__['greet'].__get__(None, Greeter). Because the instance argument isNone, the__get__implementation returns the function itself without wrapping it.
Key Differences Summary
- Type: A bound method has the type
method, whereas an unbound reference on a class is of typefunction. - Argument Binding: A bound method automatically
passes the instance as the first parameter. A function accessed through
the class requires all arguments, including
self, to be passed manually. - Identity and Lifecycle: A bound method is created
temporarily when the attribute is accessed on an instance, whereas the
function object exists continuously in the class namespace dictionary
(
__dict__). - Introspection: Bound methods expose
__self__pointing to the bound object. Functions on the class do not have a__self__attribute because they are not attached to an instance.