【问题标题】:Using metaclass to keep track of instances in python使用元类跟踪python中的实例
【发布时间】:2019-11-17 00:21:44
【问题描述】:

我需要跟踪某些类的实例(并对这些类做其他事情)。我希望不必在相关类中声明任何额外代码,因此理想情况下,所有内容都应在元类中处理。

我不知道如何为这些类的每个新实例添加弱引用。例如:

class Parallelizable(type):
    def __new__(cls, name, bases, attr):
        meta = super().__new__(cls, name, bases, attr)
        # storing the instances in this WeakSet
        meta._instances = weakref.WeakSet()
        return meta

    @property
    def instances(cls):
        return [x for x in cls._instances]

class Foo(metaclass=Parallelizable)
    def __init__(self, name):
        super().__init__()
        self.name = name

        # I would like to avoid having to do that - instead have the metaclass manage it somehow
        self._instances.add(self)

有什么想法吗?我似乎无法在元类方面找到一个钩子来进入 Foo 的 __init__....

【问题讨论】:

    标签: python python-3.x metaclass


    【解决方案1】:

    当其“附属”类的每个新实例为__call__ 时调用的元类上的方法。如果你把记录实例的代码放在那里,这就是你需要做的所有工作:

    
    from weakref import WeakSet
    
    # A convenient class-level descriptor to retrieve the instances:
    
    class Instances:
        def __get__(self, instance, cls):
            return [x for x in cls._instances]
    
    class Parallelizable(type):
        def __init__(cls, name, bases, attrs, **kw):
            super().__init__(name, bases, attrs, **kw)
            cls._instances = WeakSet()
            cls.instances = Instances()
    
        def __call__(cls, *args, **kw):
            instance = super().__call__(*args, **kw)
            cls._instances.add(instance)
            return instance
    
    

    同样的代码在没有描述符的情况下也可以工作——这只是一个很好的方法来拥有一个可以报告实例的类属性。但是如果 WeakSet 就足够了,那么这段代码就足够了:

    
    from weakref import WeakSet
    class Parallelizable(type):
        def __init__(cls, name, bases, attrs, **kw):
            super().__init__(name, bases, attrs, **kw)
            cls.instances = WeakSet()
    
        def __call__(cls, *args, **kw):
            instance = super().__call__(*args, **kw)
            cls.instances.add(instance)
            return instance
    
    

    【讨论】:

    • 为什么在元类中使用类级描述而不是装饰器?有什么优点/缺点?
    • 描述符很好用,与记录无关——保存。与这里的装饰器不同的是,跟踪代码在__call__方法中,它比装饰器方法简单了一个数量级——并且在目标类没有__init__方法时可以工作,例如,没有任何修改。
    【解决方案2】:

    您可以在Parallizable.__new__ 中装饰attrs['__init__'] 方法:

    import weakref
    import functools
    class Parallelizable(type):
        def __new__(meta, name, bases, attrs):
            attrs['__init__'] = Parallelizable.register(attrs['__init__'])
            cls = super().__new__(meta, name, bases, attrs)
            cls._instances = weakref.WeakSet()
            return cls
    
        @classmethod
        def register(cls, method):
            @functools.wraps(method)
            def newmethod(self, *args, **kwargs):
                method(self, *args, **kwargs)
                self._instances.add(self)
            return newmethod
    
        @property
        def instances(cls):
            return [x for x in cls._instances]
    
    class Foo(metaclass=Parallelizable):
        def __init__(self, name):
            "Foo.__init__ doc string"
            super().__init__()
            self.name = name
    
    # Notice that Foo.__init__'s docstring is preserved even though the method has been decorated
    help(Foo.__init__)
    # Help on function __init__ in module __main__:
    #
    # __init__(self, name)
    #     Foo.__init__ doc string
    
    stilton = Foo('Stilton')
    gruyere = Foo('Gruyere')
    print([inst.name for inst in Foo.instances])
    # ['Gruyere', 'Stilton']
    
    del stilton
    print([inst.name for inst in Foo.instances])
    # ['Gruyere']
    

    【讨论】:

    • 自动应用装饰器是一个不错的方法,但也有一些极端情况,例如,当类没有自己的 __init__ 时。
    • 那里有一个错误 - __new__ 中的 cls 指的是元类本身(以及“元”是使用元类创建的新类) - 因此,每个新类都使用代码创建原样将重置寄存器。
    • @jsbueno:是的,感谢您对meta/cls 命名混淆的更正。关于__init__:处理没有__init__ 的类的方法不止一种(例如引发异常,添加一个简单的注册__init__,或者什么都不做。)因为OP 没有说他希望这个被处理,我选择了一个会引发异常的人——提醒任何使用 Parallelizable 和 __init__-less 类的人需要处理该案例。
    • 这是一个有趣的方法。我永远不会想出那个。玩了一段时间后,我明白你在做什么。但是,为什么您认为使用这种方法与 _ call _ 相比的主要好处是?除了丰富我对 Python 更精细的工作原理的理解之外?
    • @Francky_V:我比我自己更喜欢@jsbueno 的解决方案,因为它可读性强、易于理解,并且可以轻松处理__init__-less 类。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-10-28
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    • 1970-01-01
    • 1970-01-01
    • 2023-01-03
    相关资源
    最近更新 更多