【问题标题】:Using a metaclass to substitute a class definition?使用元类代替类定义?
【发布时间】:2017-08-18 07:21:42
【问题描述】:

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 的创建是否有效?

编辑

This Ashwini Chaudhary 就同一问题发布的答案可能对其他人有用。以及下面的答案。

附:我还发现“模块”级别 __metaclass__ 在 python 3 中不起作用。所以我最初的问题是“作为一个有效的想法”是错误的

【问题讨论】:

  • 为什么不只是import that_library; that_library.UselessObject = MyOwnObject
  • this 之类的东西可能会有所帮助。
  • 甜蜜!不知道你能做到! @Ashwini Chaudhary- 是的答案,该帖子更好地描述了我的问题
  • @Rawing,是的,这是个好主意!
  • 给出的答案可以——但我们应该注意这与“元类”无关——这被称为“猴子补丁”。

标签: python metaclass monkeypatching


【解决方案1】:

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 类定义时具有其旧定义。

【讨论】:

  • 您确实需要添加 import otherother.UselessObject = CoolObject 才能在 OP 的情况下工作
  • @MarkusMeskanen 是的,您可以从 Rawing 的评论中看到这一点,但这只是一个可以放入一个文件的简单 MCVE。我不想乱七八糟地创建一个需要读者创建两个单独的文件来测试十几行代码的示例。
  • 我理解你背后的想法,但这是SO,你应该直接回答OP的问题,在他的情况下,这不是他首先定义的类,而是其他人的类包。
  • 如果CoolObject需要继承UselessObject,这个答案够吗?
  • @Shule。当然!我刚刚添加了一些代码来说明这一点。
【解决方案2】:

这不是元类的工作。

相反,Python 允许您通过一种称为“Monkeypatching”的技术来做到这一点,在该技术中,您可以在运行时用一个对象替换另一个对象。

在这种情况下,您将在调用 thirdyparty.PretendClassDef.do_something 之前将 thirdyparty.UselessObject 更改为 your.CoolObject

做到这一点的方法是一个简单的分配。 因此,假设您在问题上给出的示例 sn-p 是 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 块。如果您不想在程序的其他部分更改第三方模块的行为,则可能会出现这种情况。

至于元类,它们只有在您需要以这种方式更改要替换的类的元类时才有用 - 而且它们只有在您想插入行为时才有用在从UselessObject 继承的类中。在这种情况下,它将用于创建本地 CoolObject 并且您仍将按照上述方式执行,但请注意执行猴子补丁before Python 将运行任何UselessObject 的派生类,在从第三方库进行任何导入时要格外小心(如果在同一个文件中定义这些子类,那就很棘手了)

【讨论】:

  • 谢谢!我永远无法完全理解什么是“猴子补丁”。我得去看看unittest.mock
【解决方案3】:

这只是建立在 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 进行更改。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-08-14
    • 2017-06-27
    • 1970-01-01
    • 2017-11-05
    • 2021-06-27
    • 1970-01-01
    • 2011-04-09
    相关资源
    最近更新 更多