简体   繁体   English

使用元类来替换类定义?

[英]Using a metaclass to substitute a class definition?

Python 3.6 Python 3.6

I'm trying to modify the behavior of a third party library. 我正在尝试修改第三方库的行为。

I don't want to directly change the source code. 我不想直接更改源代码。

Considering this code below: 考虑以下代码:

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

I'd like to substitute my own class for UselessObject 我想用我自己的类代替UselessObject

I'd like to know if using a metaclass in my module to intercept the creation of UselessObject is a valid idea? 我想知道在我的模块中使用元类来拦截UselessObject的创建UselessObject是一个有效的想法?

EDIT 编辑

This answer posted by Ashwini Chaudhary on the same question, may be of use to others. 这个答案张贴阿什维尼·乔杜里对同一个问题,可能是使用他人的。 As well as the below answer. 以及以下答案。

PS I also discovered that 'module' level __metaclass__ does't work in python 3. So my initial question of it 'being a valid idea' is False PS我还发现'模块'级别__metaclass__在python 3中不起作用。所以我最初的问题'它是一个有效的想法'是假的

FWIW, here's some code that illustrates Rawing's idea. 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())

output 产量

I'm cool

We can even use this technique if CoolObject needs to inherit UselessObject . 如果CoolObject需要继承UselessObject我们甚至可以使用这种技术。 If we change the definition of CoolObject to: 如果我们将CoolObject的定义CoolObject为:

class CoolObject(UselessObject):
    def __repr__(self):
        s = super().__repr__()
        return "I'm cool, but my parent says " + s

we get this output: 我们得到这个输出:

I'm cool, but my parent says I'm useless

This works because the name UselessObject has its old definition when the CoolObject class definition is executed. 这是有效的,因为在执行CoolObject类定义时,名称UselessObject具有其旧定义。

This is not a job for metaclasses. 这不是元类的工作。

Rather, Python allows you to do this through a technique called "Monkeypatching", in which you, at run time, substitute one object for another in run time. 相反,Python允许您通过称为“Monkeypatching”的技术来执行此操作,在运行时,您可以在运行时将一个对象替换为另一个对象。

In this case, you'd be changing the thirdyparty.UselessObject for your.CoolObject before calling thirdyparty.PretendClassDef.do_something 在这种情况下,你会改变thirdyparty.UselessObjectyour.CoolObject之前调用thirdyparty.PretendClassDef.do_something

The way to do that is a simple assignment. 这样做的方法是一个简单的任务。 So, supposing the example snippet you gave on the question is the trirdyparty module, on the library, your code would look like: 因此,假设您在问题上提供的示例代码段是trirdyparty模块,在库中,您的代码将如下所示:

import thirdyparty

class CoolObject:
    # Your class definition here

thirdyparty.UselesObject = Coolobject

Things you have to take care of: that you change the object pointed by UselessObject in the way it is used in your target module. 您需要注意的事项:您在目标模块中使用的方式更改UselessObject指向的对象。

If for example, your PretendedClassDef and UselessObject are defined in different modules, you have to procees in one way if UselessObject is imported with from .useless import UselessObject (in this case the example above is fine), and import .useless and later uses it as useless.UselessObject - in this second case, you have to patch it on the useless module. 例如,你的PretendedClassDef和Usel​​essObject是在不同的模块中定义的,如果使用from .useless import UselessObject ,则必须以一种方式处理from .useless import UselessObject (在这种情况下上面的示例很好),并import .useless以后再使用它as useless.UselessObject - 在第二种情况下,你必须在useless模块上修补它。

Also, Python's unittest.mock has a nice patch callable that can properly perform a monkeypatching and undo it if by some reason you want the modification to be valid in a limited scope, like inside a function of yours, or inside a with block. 此外,Python的unittest.mock有一个很好的patch可调用,可以正确执行monkeypatching并撤消它,如果由于某种原因你希望修改在有限的范围内有效,比如在你的函数内,或在with块内。 That might be the case if you don't want to change the behavior of the thirdyparty module in other sections of your program. 如果您不想在程序的其他部分中更改thirdyparty模块的行为,则可能就是这种情况。

As for metaclasses, they only would be of any use if you would need to change the metaclass of a class you'd be replacing in this way - and them they only could have any use if you'd like to insert behavior in classes that inherit from UselessObject . 至于元类,如果你需要以这种方式改变你要替换的类的元类,它们只会有任何用处 - 如果你想在类中插入行为,它们只能有用。继承自UselessObject In that case it would be used to create the local CoolObject and you'd still perform as above, but taking care that you'd perform the monkeypatching before Python would run the class body of any of the derived classes of UselessObject , taking extreme care when doing any imports from the thirdparty library (that would be tricky if these subclasses were defined on the same file) 在这种情况下,它将用于创建本地CoolObject ,你仍然可以执行上述操作,但是 Python运行任何派生类的UselessObject的类主体之前要注意你执行UselessObject ,要特别小心从第三方库中进行任何导入时(如果在同一文件中定义了这些子类,那将会非常棘手)

This is just building on PM 2Ring's and jsbueno's answers with more contexts: 这只是建立在PM 2Ring和jsbueno的答案上的更多背景:

If you happen to be creating a library for others to use as a third-party library (rather than you using the third-party library), and if you need CoolObject to inherit UselessObject to avoid repetition, the following may be useful to avoid an infinite recursion error that you might get in some circumstances: 如果您正在为其他人创建一个库以用作第三方库(而不是您使用第三方库),并且如果您需要CoolObject继承UselessObject以避免重复,则以下内容可能有助于避免在某些情况下可能会得到的无限递归错误:

module1.py 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 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."

Just be careful that the user knows not to set a different value for parent_class with different subclasses of Actor. 请注意,用户不知道为不同的Actor子类设置parent_class的不同值。 I mean, if you make multiple kinds of Actors, you'll only want to set parent_class once, unless you want it to change for all of them. 我的意思是,如果你制作多种Actors,你只需要设置一次parent_class,除非你希望它为所有这些都改变。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM