【问题标题】:How to run code when a class is subclassed? [duplicate]当一个类被子类化时如何运行代码? [复制]
【发布时间】:2013-08-10 04:43:43
【问题描述】:

当我的类被子类化时,有没有办法触发代码?

class SuperClass:
    def triggered_routine(subclass):
        print("was subclassed by " + subclass.__name__)

magically_register_triggered_routine()

print("foo")

class SubClass0(SuperClass):
    pass

print("bar")

class SubClass1(SuperClass):
    print("test")

应该输出

foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1

【问题讨论】:

  • 使用元类;创建类时调用元类,就像创建实例时调用类一样。
  • 无法添加答案,但今天 python3.6 有 __init_subclass__ - 看看吧!
  • @OrDuan:谢谢,听起来很有用。甚至可能有足够的理由取消将此问题标记为重复项,因为现在有一个专门的解决方案可以解决我的问题,而不是“使用元类”解决方案。

标签: python inheritance


【解决方案1】:

类(默认)是type 的实例。 正如Foo 类的实例是由foo = Foo(...) 创建的一样, type 的一个实例(即一个类)由myclass = type(name, bases, clsdict) 创建。

如果你想在创建类的时候发生一些特别的事情,那么你必须修改创建类的东西——即type。这样做的方法是定义type 的子类——即元类。

元类对它的类就像一个类对它的实例一样。

在 Python2 中,您可以使用

定义一个类的元类
class SuperClass:
    __metaclass__ = Watcher

其中Watchertype 的子类。

在 Python3 中语法已更改为

class SuperClass(metaclass=Watcher)

两者都等价于

Superclass = Watcher(name, bases, clsdict)

在这种情况下,name 等于字符串'Superclass'bases 是元组(object, )clsdict 是类定义主体中定义的类属性的字典。

注意与myclass = type(name, bases, clsdict) 的相似之处。

因此,就像您在创建实例时使用类的 __init__ 来控制事件一样,您可以使用元类的 __init__ 来控制创建类时的事件:


class Watcher(type):
    def __init__(cls, name, bases, clsdict):
        if len(cls.mro()) > 2:
            print("was subclassed by " + name)
        super(Watcher, cls).__init__(name, bases, clsdict)

class SuperClass:
    __metaclass__ = Watcher


print("foo")

class SubClass0(SuperClass):
  pass

print("bar")

class SubClass1(SuperClass):
  print("test")

打印

foo
was subclassed by SubClass0
bar
test
was subclassed by SubClass1

【讨论】:

  • 谢谢。但是,您的代码仅适用于 python2。在 python3 中,SuperClass 需要是 class SuperClass(metaclass = Watcher): pass
  • 谢谢。我编辑了这篇文章以解决 Python2 和 Python3 语法的差异。
  • 我自己尝试时错过了两个关键的事情:1 - Watcher 子类 type,而不是 object。 2 - SuperClass 根本没有超类,而不是 object。我只是习惯于将object 写为我定义的每个类的超类,以至于我不知何故错过了这段代码正在使用其他东西。虽然让SuperClass 继承自object 似乎没有任何害处……但Watcher 绝对有必要继承自type,而不是object
  • 在 python3 中更新解决方案并添加抽象类的使用(当我们需要这样的注册表时,我认为这是正确的): ``` import abc class Watcher(abc.ABCMeta): def __init__(cls , name, bases, clsdict): if len(cls.mro()) > 2: print("was subclassed by " + name) super().__init__( name, bases, clsdict) class SuperClass( metaclass=Watcher): @abc.abstractmethod def method(): pass class SubClass0(SuperClass): def method(): print("S0 ok") class SubClass1(SuperClass): pass s1 = SubClass0() s1 = SubClass1() ```跨度>
【解决方案2】:

编辑:我的旧帖子实际上没有用。 classmethod 的子类化无法按预期工作。

首先,我们想有一些方法告诉元类这个特定的方法应该有特殊的子类调用行为,我们只需在我们想要调用的函数上设置一个属性。为方便起见,我们甚至将函数转换为classmethod,以便也可以发现它所在的真正基类。我们将返回 classmethod 以便它可以用作装饰器,这是最方便的。

import types
import inspect

def subclass_hook(func):
    func.is_subclass_hook = True
    return classmethod(func)

我们还需要一种方便的方式来查看使用了 subclass_hook 装饰器。我们知道classmethod 已被使用,因此我们将检查它,然后才查找is_subclass_hook 属性。

def test_subclass_hook(thing):
    x = (isinstance(thing, types.MethodType) and
         getattr(thing.im_func, 'is_subclass_hook', False))
    return x

最后,我们需要一个作用于信息的元类:在大多数情况下,这里最有趣的事情就是检查每个提供的基类是否有钩子。这样一来,super 就以最不令人惊讶的方式发挥作用。

class MyMetaclass(type):
    def __init__(cls, name, bases, attrs):
        super(MyMetaclass, cls).__init__(name, bases, attrs)

        for base in bases:
            if base is object:
                continue
            for name, hook in inspect.getmembers(base, test_subclass_hook):
                hook(cls)

应该这样做。

>>> class SuperClass:
...     __metaclass__ = MyMetaclass
...     @subclass_hook
...     def triggered_routine(cls, subclass):
...         print(cls.__name__ + " was subclassed by " + subclass.__name__)

>>> class SubClass0(SuperClass):
...     pass
SuperClass was subclassed by SubClass0

>>> class SubClass1(SuperClass):
...     print("test")
test
SuperClass was subclassed by SubClass1

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-03
    • 2014-05-01
    • 2019-05-20
    • 1970-01-01
    • 2014-12-18
    • 1970-01-01
    相关资源
    最近更新 更多