猴子修补 Python class
Monkeypatching a Python class
我想了解 Python classes 和对象是如何工作的。在 Perl 中它非常简单,在 package
中定义的每个 sub
都可以调用为静态、class 或对象方法(CLASS::func
、CLASS->func
或 $obj->func
).乍一看,Python class 看起来像带有 bless
哈希的 Perl class(Python 中的 __dict__
属性 class).但是在 Python 中我有点困惑。因此,为了更好地理解,我尝试对一个空 class 进行猴子修补,添加 3 个行为与静态、class 和对象方法完全相同的属性,但我无法得到它。
起初我创建了正常的 class 以获得基本结果:
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)
然后我创建了一个函数(称为 test
),它尝试使用一个参数调用所有方法,如果失败(因为第一个参数可以是 class 或对象本身),则尝试再次调用 none 在输出行前面打印一个 'X',然后打印检测到的类型:
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)
使用默认 A
class 及其对象调用 test
:
test(A)
test(A())
结果是:
* 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>>
因此,使用 class 或对象前缀调用 staticmethod
,它们的行为与普通(命名空间)函数一样(接受 1 个参数)。无论使用哪种方式调用 classmethod
,传递的第一个参数都是 class 对象。从 class 调用 objectmethod
的行为与普通函数一样,如果从对象调用,则第一个参数是对象本身。这后来看起来有点奇怪,但我可以接受。
现在,让我们尝试猴子修补一个空 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())
结果是:
* 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>>
根据此模式,stt_m
的行为类似于普通 class 和 cls_m
的对象方法,而 obj_m
的行为类似于 class 的方法正常 class.
我可以用这种方式猴子修补静态方法吗?
您可以 monkey-patch 方法到 class,但它是这样完成的:
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)
您的 B.cls_m
版本没问题,但是您的 B.stt_m
创建了一个普通方法,并且您的 B.obj_m
将一个实例方法附加到新创建的 B()
,但是然后 B()
被丢弃,你测试一个新的 B()
没有额外的方法。
通常不需要在Python中使用types.MethodType
:
types.MethodType(function, object_)
等同于
function.__get__(object_)
哪个好一点,虽然也很少见。
还有(不相关但太简洁了,更不用说),在 Python 的新版本中,您的
print('*', msg, 'x =', x, 'type(x) =', type(x))
可以写成
print(f"* {msg} {x = } {type(x) = }")
我想了解 Python classes 和对象是如何工作的。在 Perl 中它非常简单,在 package
中定义的每个 sub
都可以调用为静态、class 或对象方法(CLASS::func
、CLASS->func
或 $obj->func
).乍一看,Python class 看起来像带有 bless
哈希的 Perl class(Python 中的 __dict__
属性 class).但是在 Python 中我有点困惑。因此,为了更好地理解,我尝试对一个空 class 进行猴子修补,添加 3 个行为与静态、class 和对象方法完全相同的属性,但我无法得到它。
起初我创建了正常的 class 以获得基本结果:
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)
然后我创建了一个函数(称为 test
),它尝试使用一个参数调用所有方法,如果失败(因为第一个参数可以是 class 或对象本身),则尝试再次调用 none 在输出行前面打印一个 'X',然后打印检测到的类型:
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)
使用默认 A
class 及其对象调用 test
:
test(A)
test(A())
结果是:
* 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>>
因此,使用 class 或对象前缀调用 staticmethod
,它们的行为与普通(命名空间)函数一样(接受 1 个参数)。无论使用哪种方式调用 classmethod
,传递的第一个参数都是 class 对象。从 class 调用 objectmethod
的行为与普通函数一样,如果从对象调用,则第一个参数是对象本身。这后来看起来有点奇怪,但我可以接受。
现在,让我们尝试猴子修补一个空 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())
结果是:
* 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>>
根据此模式,stt_m
的行为类似于普通 class 和 cls_m
的对象方法,而 obj_m
的行为类似于 class 的方法正常 class.
我可以用这种方式猴子修补静态方法吗?
您可以 monkey-patch 方法到 class,但它是这样完成的:
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)
您的 B.cls_m
版本没问题,但是您的 B.stt_m
创建了一个普通方法,并且您的 B.obj_m
将一个实例方法附加到新创建的 B()
,但是然后 B()
被丢弃,你测试一个新的 B()
没有额外的方法。
通常不需要在Python中使用types.MethodType
:
types.MethodType(function, object_)
等同于
function.__get__(object_)
哪个好一点,虽然也很少见。
还有(不相关但太简洁了,更不用说),在 Python 的新版本中,您的
print('*', msg, 'x =', x, 'type(x) =', type(x))
可以写成
print(f"* {msg} {x = } {type(x) = }")