使用 metaclass 代替 class 定义?
Using a metaclass to substitute a class definition?
Python 3.6
我正在尝试修改第三方库的行为。
不想直接改源码
考虑以下代码:
class UselessObject(object):
pass
class PretendClassDef(object):
"""
A class to highlight my problem
"""
def do_something(self):
# Allot of code here
result = UselessObject()
return result
我想用我自己的 class 代替 UselessObject
我想知道在我的模块中使用 metaclass 来拦截 UselessObject
的创建是否是一个有效的想法?
编辑
Ashwini Chaudhary 在同一问题上发表的回答,可能对其他人有用。以及下面的答案。
P.S。我还发现 'module' 级别 __metaclass__
在 python 3 中不起作用。所以我最初的问题 'being a valid idea' 是 False
FWIW,这里有一些代码可以说明 Rawing 的想法。
class UselessObject(object):
def __repr__(self):
return "I'm useless"
class PretendClassDef(object):
def do_something(self):
return UselessObject()
# -------
class CoolObject(object):
def __repr__(self):
return "I'm cool"
UselessObject = CoolObject
p = PretendClassDef()
print(p.do_something())
输出
I'm cool
如果CoolObject
需要继承UselessObject
,我们甚至可以使用这个技巧。如果我们将 CoolObject
的定义更改为:
class CoolObject(UselessObject):
def __repr__(self):
s = super().__repr__()
return "I'm cool, but my parent says " + s
我们得到这个输出:
I'm cool, but my parent says I'm useless
这是有效的,因为名称 UselessObject
在执行 CoolObject
class 定义时具有其旧定义。
这不是 metaclasses 的工作。
相反,Python 允许您通过一种称为 "Monkeypatching" 的技术来做到这一点,您可以在 运行 时间用一个对象替换另一个对象 运行时间。
在这种情况下,您需要在调用 thirdyparty.PretendClassDef.do_something
之前将 thirdyparty.UselessObject
更改为 your.CoolObject
这样做的方法很简单。
因此,假设您在问题中给出的示例片段是 trirdyparty 模块,在库中,您的代码将如下所示:
import thirdyparty
class CoolObject:
# Your class definition here
thirdyparty.UselesObject = Coolobject
您必须注意的事情:您更改 UselessObject
指向的对象,使其在您的目标模块中使用。
例如,如果您的 PretendedClassDef 和 UselessObject 是在不同的模块中定义的,如果 UselessObject 是使用 from .useless import UselessObject
导入的(在这种情况下,上面的示例很好),并且 import .useless
然后将其用作 useless.UselessObject
- 在第二种情况下,您必须在 useless
模块上对其进行修补。
此外,Python 的 unittest.mock
有一个很好的 patch
可调用函数,它可以正确地执行猴子修补,如果由于某种原因你希望修改在范围有限,例如在您的函数内部或 with
块内。如果您不想在程序的其他部分更改第三方模块的行为,可能就是这种情况。
至于 metaclasses,它们只有在您需要更改要以这种方式替换的 class 的 metaclass 时才有用- 只有当您想在继承自 UselessObject
的 class 中插入行为时,它们才有用。在那种情况下,它将用于创建本地 CoolObject
并且你仍然会像上面那样执行,但要注意你会执行 monkeypatching before Python 将 运行 class UselessObject
的任何派生 class 的主体,在从第三方库进行任何导入时要格外小心(如果这些subclasses 在同一个文件中定义)
这只是建立在 PM 2Ring 和 jsbueno 的答案之上,具有更多上下文:
如果你刚好是创建一个库供别人使用作为第三方库(而不是你自己使用第三方库),并且你需要CoolObject继承UselessObject避免重复,下面可能有助于避免在某些情况下可能遇到的无限递归错误:
module1.py
class Parent:
def __init__(self):
print("I'm the parent.")
class Actor:
def __init__(self, parent_class=None):
if parent_class!=None: #This is in case you don't want it to actually literally be useless 100% of the time.
global Parent
Parent=parent_class
Parent()
module2.py
from module1 import *
class Child(Parent):
def __init__(self):
print("I'm the child.")
class LeadActor(Actor): #There's not necessarily a need to subclass Actor, but in the situation I'm thinking, it seems it would be a common thing.
def __init__(self):
Actor.__init__(self, parent_class=Child)
a=Actor(parent_class=Child) #prints "I'm the child." instead of "I'm the parent."
l=LeadActor() #prints "I'm the child." instead of "I'm the parent."
请注意,用户知道不要为 Actor 的不同子类设置不同的 parent_class 值。我的意思是,如果您制作多种类型的 Actor,您将只想设置 parent_class 一次,除非您希望它为所有的 Actor 都改变。
Python 3.6
我正在尝试修改第三方库的行为。
不想直接改源码
考虑以下代码:
class UselessObject(object):
pass
class PretendClassDef(object):
"""
A class to highlight my problem
"""
def do_something(self):
# Allot of code here
result = UselessObject()
return result
我想用我自己的 class 代替 UselessObject
我想知道在我的模块中使用 metaclass 来拦截 UselessObject
的创建是否是一个有效的想法?
编辑
P.S。我还发现 'module' 级别 __metaclass__
在 python 3 中不起作用。所以我最初的问题 'being a valid idea' 是 False
FWIW,这里有一些代码可以说明 Rawing 的想法。
class UselessObject(object):
def __repr__(self):
return "I'm useless"
class PretendClassDef(object):
def do_something(self):
return UselessObject()
# -------
class CoolObject(object):
def __repr__(self):
return "I'm cool"
UselessObject = CoolObject
p = PretendClassDef()
print(p.do_something())
输出
I'm cool
如果CoolObject
需要继承UselessObject
,我们甚至可以使用这个技巧。如果我们将 CoolObject
的定义更改为:
class CoolObject(UselessObject):
def __repr__(self):
s = super().__repr__()
return "I'm cool, but my parent says " + s
我们得到这个输出:
I'm cool, but my parent says I'm useless
这是有效的,因为名称 UselessObject
在执行 CoolObject
class 定义时具有其旧定义。
这不是 metaclasses 的工作。
相反,Python 允许您通过一种称为 "Monkeypatching" 的技术来做到这一点,您可以在 运行 时间用一个对象替换另一个对象 运行时间。
在这种情况下,您需要在调用 thirdyparty.PretendClassDef.do_something
thirdyparty.UselessObject
更改为 your.CoolObject
这样做的方法很简单。 因此,假设您在问题中给出的示例片段是 trirdyparty 模块,在库中,您的代码将如下所示:
import thirdyparty
class CoolObject:
# Your class definition here
thirdyparty.UselesObject = Coolobject
您必须注意的事情:您更改 UselessObject
指向的对象,使其在您的目标模块中使用。
例如,如果您的 PretendedClassDef 和 UselessObject 是在不同的模块中定义的,如果 UselessObject 是使用 from .useless import UselessObject
导入的(在这种情况下,上面的示例很好),并且 import .useless
然后将其用作 useless.UselessObject
- 在第二种情况下,您必须在 useless
模块上对其进行修补。
此外,Python 的 unittest.mock
有一个很好的 patch
可调用函数,它可以正确地执行猴子修补,如果由于某种原因你希望修改在范围有限,例如在您的函数内部或 with
块内。如果您不想在程序的其他部分更改第三方模块的行为,可能就是这种情况。
至于 metaclasses,它们只有在您需要更改要以这种方式替换的 class 的 metaclass 时才有用- 只有当您想在继承自 UselessObject
的 class 中插入行为时,它们才有用。在那种情况下,它将用于创建本地 CoolObject
并且你仍然会像上面那样执行,但要注意你会执行 monkeypatching before Python 将 运行 class UselessObject
的任何派生 class 的主体,在从第三方库进行任何导入时要格外小心(如果这些subclasses 在同一个文件中定义)
这只是建立在 PM 2Ring 和 jsbueno 的答案之上,具有更多上下文:
如果你刚好是创建一个库供别人使用作为第三方库(而不是你自己使用第三方库),并且你需要CoolObject继承UselessObject避免重复,下面可能有助于避免在某些情况下可能遇到的无限递归错误:
module1.py
class Parent:
def __init__(self):
print("I'm the parent.")
class Actor:
def __init__(self, parent_class=None):
if parent_class!=None: #This is in case you don't want it to actually literally be useless 100% of the time.
global Parent
Parent=parent_class
Parent()
module2.py
from module1 import *
class Child(Parent):
def __init__(self):
print("I'm the child.")
class LeadActor(Actor): #There's not necessarily a need to subclass Actor, but in the situation I'm thinking, it seems it would be a common thing.
def __init__(self):
Actor.__init__(self, parent_class=Child)
a=Actor(parent_class=Child) #prints "I'm the child." instead of "I'm the parent."
l=LeadActor() #prints "I'm the child." instead of "I'm the parent."
请注意,用户知道不要为 Actor 的不同子类设置不同的 parent_class 值。我的意思是,如果您制作多种类型的 Actor,您将只想设置 parent_class 一次,除非您希望它为所有的 Actor 都改变。