使用元类来替换类定义?

时间:2017-08-18 07:21:42

标签: python metaclass monkeypatching

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

我想用我自己的班级代替UselessObject

我想知道在我的模块中使用元类来拦截UselessObject的创建是否是一个有效的想法?

修改

Ashwini Chaudhary在同一个问题上发表的回复

This可能对其他人有用。以及以下答案。

P.S。我还发现了'模块'级别__metaclass__在python 3中不起作用。所以我最初的问题是它是一个有效的想法'是假的

3 个答案:

答案 0 :(得分:3)

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具有旧的定义。

答案 1 :(得分:2)

这不是元类的工作。

相反,Python允许您通过一种名为&#34; Monkeypatching&#34;的技术来实现这一点,在运行时,您可以在运行时将一个对象替换为另一个对象。

在这种情况下,您在调用thirdyparty.UselessObject

之前更改了your.CoolObject的{​​{1}}

这样做的方法是一个简单的任务。 因此,假设您在问题上提供的示例代码段是trirdyparty模块,在库中,您的代码将如下所示:

thirdyparty.PretendClassDef.do_something

您需要注意的事项:您在目标模块中使用的方式更改import thirdyparty class CoolObject: # Your class definition here thirdyparty.UselesObject = Coolobject 指向的对象。

例如,如果您的PretendedClassDef和Usel​​essObject在不同的模块中定义,如果使用UselessObject导入UselessObject(在这种情况下上面的示例很好),则必须以一种方式处理,{{1}然后将其用作from .useless import UselessObject - 在第二种情况下,您必须在import .useless模块上对其进行修补。

此外,Python的useless.UselessObject有一个很好的patch可调用,可以正确执行monkeypatching并撤消它,如果由于某种原因你希望修改在有限的范围内有效,比如在你的一个函数内,或在useless块内。如果您不想在程序的其他部分更改第三方模块的行为,可能就是这种情况。

至于元类,如果你需要改变你用这种方式替换的类的元类,它们只会有用 - 如果你这样做它们只能用它们喜欢在继承自unittest.mock的类中插入行为。在这种情况下,它将用于创建本地with并且您仍然按上述方式执行,但要注意您在 Python运行之前执行monkeypatching UselessObject的任何派生类的类体,在从第三方库中进行任何导入时要格外小心(如果在同一文件中定义了这些子类,那将非常棘手)

答案 2 :(得分:0)

这只是建立在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的不同值。我的意思是,如果你制作了多种Actors,你只需要设置一次parent_class,除非你希望它为所有这些都改变。