I would like to understand how Python classes and objects work. In Perl it is pretty simple, each sub
definied in a package
can be called as static, class or object method (CLASS::func
, CLASS->func
or $obj->func
). For the first glance, a Python class looks like a Perl class with a bless
-ed HASH (The __dict__
attribute in Python class). But in Python I'm a little bit confused. So, to understand better, I have tried to monkey-patch an empty class, adding 3 attributes which behave exactly like a static, class and object method, but I could not get it.
At first I have created the normal class to get the base result:
def say(msg, x):
print('*', msg, 'x =', x, 'type(x) =', type(x))
class A():
@staticmethod
def stt_m(x):
say('stt_m', x)
@classmethod
def cls_m(x):
say('cls_m', x)
def obj_m(x):
say('obj_m', x)
Then I have created a function (called test
) which tries to call all methods with one parameter and if fails (as the first parameter can be the class or object itself), tries to call again with none printing an 'X' in front of the output line, and then prints the detected types:
def test(obj):
# Detect if obj is a class or an instantiated object
what = 'Class' if type(obj) == type else 'Object'
print()
# Try to call static, class and object method getting attributes
for a in ('stt_m', 'cls_m', 'obj_m'):
meth = getattr(obj, a)
try:
meth(111)
except:
print('X', end='')
meth()
print(' ', what, a, meth)
Calling test
with the default A
class and its object:
test(A)
test(A())
The result is:
* stt_m x = 111 type(x) = <class 'int'>
Class stt_m <function A.stt_m at 0x7fb37e63c8c8>
X* cls_m x = <class '__main__.A'> type(x) = <class 'type'>
Class cls_m <bound method A.cls_m of <class '__main__.A'>>
* obj_m x = 111 type(x) = <class 'int'>
Class obj_m <function A.obj_m at 0x7fb37e63c9d8>
* stt_m x = 111 type(x) = <class 'int'>
Object stt_m <function A.stt_m at 0x7fb37e63c8c8>
X* cls_m x = <class '__main__.A'> type(x) = <class 'type'>
Object cls_m <bound method A.cls_m of <class '__main__.A'>>
X* obj_m x = <__main__.A object at 0x7fb37e871748> type(x) = <class '__main__.A'>
Object obj_m <bound method A.obj_m of <__main__.A object at 0x7fb37e871748>>
So, calling a staticmethod
with either class or object prefix, they behaves as normal (namespace) functions (accepting 1 argument). Calling a classmethod
with either way, the first argument passed is the class object. Calling objectmethod
from a class behaves as a normal function and if called from an object, then the first argument is the object itself. This later looks a bit strange, but I can live with it.
Now, let's try to monkey-patch an empty class:
class B():
pass
B.stt_m = lambda x: say('stt_m', x)
B.cls_m = types.MethodType(lambda x: say('cls_m', x), B)
B.obj_m = types.MethodType(lambda x: say('obj_m', x), B())
test(B)
test(B())
Result is:
* stt_m x = 111 type(x) = <class 'int'>
Class stt_m <function <lambda> at 0x7fbf05ec7840>
X* cls_m x = <class '__main__.B'> type(x) = <class 'type'>
Class cls_m <bound method <lambda> of <class '__main__.B'>>
X* obj_m x = <__main__.B object at 0x7fbf0d7dd978> type(x) = <class '__main__.B'>
Class obj_m <bound method <lambda> of <__main__.B object at 0x7fbf0d7dd978>>
X* stt_m x = <__main__.B object at 0x7fbf06375e80> type(x) = <class '__main__.B'>
Object stt_m <bound method <lambda> of <__main__.B object at 0x7fbf06375e80>>
X* cls_m x = <class '__main__.B'> type(x) = <class 'type'>
Object cls_m <bound method <lambda> of <class '__main__.B'>>
X* obj_m x = <__main__.B object at 0x7fbf0d7dd978> type(x) = <class '__main__.B'>
Object obj_m <bound method <lambda> of <__main__.B object at 0x7fbf0d7dd978>>
According to this pattern, stt_m
behaves, like an object method of the normal class and cls_m
and obj_m
behaves like class method of the normal class.
Can I monkey-patch a static method this way?
CodePudding user response:
You can monkey-patch methods onto a class, but it’s done like this:
B.stt_m = staticmethod(lambda x: say('stt_m', x))
B.cls_m = classmethod(lambda x: say('cls_m', x))
B.obj_m = lambda x: say('obj_m', x)
Your version for B.cls_m
is OK, but your B.stt_m
creates a normal method, and your B.obj_m
attaches an instance method to a newly created B()
, but then that B()
is thrown away, and you test a new B()
without the extra method.
There’s usually no need to use types.MethodType
in Python:
types.MethodType(function, object_)
is equivalent to
function.__get__(object_)
which is a bit better, although also very rare.
Also (irrelevant but too neat not to mention), in newish versions of Python, your
print('*', msg, 'x =', x, 'type(x) =', type(x))
can just be written as
print(f"* {msg} {x = } {type(x) = }")