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



所以我在一本书中读到,如果你想从内置类型(如listdictstr(扩展,并且想要覆盖魔术方法,你应该分别使用collections模块中的UserListUserDictUserString。看起来,这是因为基类是在CPython中实现的,其中这些方法不会相互调用,因此,覆盖它们没有或不需要的影响。

我想知道是否类似于UserList类等,存在一个可用于"正确"扩展object类的类。我查看了PyObject文档并找到了这个以及这篇文章,但我既不想用 C 语言扩展,也不想说它。我在collections模块或其他任何地方都找不到任何东西。

我问的原因是因为我之前的一个问题"是否可以将默认的类 dunder 方法转换为类方法?"我有一种预感,我的方法不起作用的原因与我之前概述的原因相同。

正如评论中所述 - 没有这样的事情,也没有必要 -object中的所有魔法方法都可以被覆盖并且工作正常。

字典,列表和其他集合会发生什么,正如MisterMiyagi在评论中解释的那样,例如,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

最新更新