使用 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 都改变。