3

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?

brian d foy
133k31 gold badges214 silver badges611 bronze badges
asked Feb 20, 2022 at 19:24
1
  • Class members in Python are super weird because they are resolved through the descriptor protocol – the piece of magic that lets you implement class methods vs static methods vs instance methods and also properties. It is up to the descriptor (the object you assign to a class member) to determine how a later access behaves – there is no special syntax for this as in Perl. Normal functions/lambdas are a descriptor that turns them into instance methods when accessed through a class/object. Commented Feb 20, 2022 at 23:09

1 Answer 1

3

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) = }")
answered Feb 20, 2022 at 20:33
Sign up to request clarification or add additional context in comments.

5 Comments

I would also point out that there is almost never a need to monkey patch in Python.
@LeopardShark Thanks to opening my eyes! I did not realize that I can use the decorators as simple functions (as they are, really)! Great help for a newbie! I used the ModelType to try to convert the lambda to a class method, which succeeded for some reason!
@Keith Thanks for the comment. This was just a training, how a class is created. For educational reason only! :)
@LeopardShark Do you know what staticmethod and classmethod really does under the hood? Or they are just do some internal flagging showing what they are...
@TrueY They implement what are called descriptors.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.