是否有 PyObject 类的实现允许您正确覆盖魔术方法?

Posted

技术标签:

【中文标题】是否有 PyObject 类的实现允许您正确覆盖魔术方法?【英文标题】:Is there an implementation of the PyObject class that allows you to properly override magic methods? 【发布时间】:2020-09-22 16:32:43 【问题描述】:

所以我在一本书中读到,如果你想从 listdictstr 等内置类型进行扩展,并且想要覆盖魔法方法,你应该使用 UserList、@ 987654329@ 和 UserString 分别来自 collections 模块。显然,这是因为基类是在 CPython 中实现的,这些方法不会相互调用,因此,覆盖它们没有或不需要的影响。

我想知道是否与UserList 类等类似,是否存在一个可用于“正确”扩展object 类的类。我查看了PyObjectdocumentation,发现this,还有this post,但我既不想用C扩展,也不想说。我在 collections 模块或其他任何地方都找不到任何东西。

我问的原因是因为我之前的一个问题'Is it possible to transform default class dunder methods into class methods?',我有一种预感,我的方法不起作用的原因与我之前概述的相同。

【问题讨论】:

你能澄清一下你想做什么吗? UserList 的情况与链接的问题完全不同。内置对象的方法(例如list)不会调用同一对象的other方法,例如list.__iter__ 不会在子类上使用覆盖的list.__getitem__(他们是否应该这样做以及以什么顺序是有争议的)。这确实意味着您不能覆盖特殊方法。一个类'__getitem__ 是一个完全独立于类'class'(又名元类)__getitem__ 的东西,它们需要被单独覆盖。 对于你的最后一个问题,你需要元类...对于这个问题 object 使用 type 。所以你也可以使用 type 感谢您的 cmets @MisterMiyagi @vks!不过,我很难解决这个问题。你是说元类和父类不一样吗? 是的,父/基类和元类是两个不同的东西。父/基类满足issubclass(my_class, base_class)——类“是”其基类的变体。元类是类的类——即isinstance(my_class, meta_class)。一个类“是一个实例”它的元类。 我不太确定您所说的 UserObject 究竟是什么意思,但没有什么可以接近的。 abc.ABC 可能是相关的,尽管它用于不同的目的。请注意,您可以创建自己的UserObject,就像您可以创建自己的UserList 【参考方案1】:

正如 cmets 中所述 - 没有这样的东西,也不需要它 - object 中的所有魔术方法都可以被覆盖并且可以正常工作。

字典、列表和其他集合会发生什么,正如 MisterMiyagi 在 cmets 中解释的那样,例如,dict 的 get 不会使用 __getitem__ 方法,所以如果您正在自定义它的行为,您还得重写get。在collections.abc 模块中定义的类可以正确解决这个问题,允许使用最少的重复代码创建完全工作的映射、序列和集合。

现在,如果您希望其中一种魔法方法作用于一个对象的类而不是该类的实例,您必须在该类的类中实现方法 - 这是“元类”。

这与“超类”有很大的不同——超类定义了子类继承的方法和属性,而这些将在子类中可用。但是类上的魔法方法只影响实例,而不影响类本身(__init_subclass__ 和当然__new__ 除外,它可以更改为创建新实例之外的其他事情)。

元类控制类的构建方式(使用__new____init____call__ 方法) - 并且允许拥有关于它们如何表现的方法通过魔术方法 - 我认为元类中的魔术方法在类中的工作方式与它们在类与普通实例的关系中的工作方式相比没有特殊情况。如果您在元类上实现__add____getitem____len__,所有这些都将适用于使用该元类创建的类。

如果您不想在元类本身中为该类编写魔法方法,可以做的是创建一个元类,该元类将在被调用时自动创建另一个动态元类并复制该类的dunder方法。但在任何严肃的应用程序中都很难将其视为“健康”的设计——拥有适用于类的魔法方法已经有点过头了——尽管在某些情况下这样做很方便。

因此,例如,如果您希望某个类有一个 __geitem__ 以允许您检索该类的所有实例,则可以这样做:

class InstanceRegister(type):
    def __init__(cls, name, bases, namespace, **kw):
        super().__init__(name, bases, namespace, **kw)

        cls._instances = []

        original_new = cls.__new__
        def new_wrapper(cls, *args, **kw):
            if original_new is object.__new__:
                instance = original_new(cls)
            else:
                instance = original_new(cls, *args, **kw)
            cls._instances.append(instance)
            return instance

        cls.__new__ = new_wrapper

    def __len__(cls):
        return len(cls._instances)

    def __getitem__(cls, item):
        return cls._instances[item]

这将起作用,正如在本次互动会议中所看到的那样:

In [26]: class A(metaclass=InstanceRegister): pass                                                                                   

In [27]: a = A()                                                                                                                     

In [28]: len(A)                                                                                                                      
Out[28]: 1

In [29]: A[0] is a                                                                                                                   
Out[29]: True

【讨论】:

以上是关于是否有 PyObject 类的实现允许您正确覆盖魔术方法?的主要内容,如果未能解决你的问题,请参考以下文章

rog夜魔驱动打不开

GetOrgChart:允许的最大primaryColumns

是否有可能有一个基类方法调用其所有派生类的相同(但被覆盖的方法)?

使用 Python C API 实现 PyMyType_Check 方法?

检查 PyObject 是不是为 None

查看一个抽象类的方法是否未被其中一个扩展类覆盖的方法