为什么abc.ABCMeta抽象实例化检查不能用于list和dict的派生类? [英] Why doesn't the abc.ABCMeta abstract instantiation check work on derivatives of `list` and `dict`?

查看:87
本文介绍了为什么abc.ABCMeta抽象实例化检查不能用于list和dict的派生类?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在尝试使用python中的abc模块.啦

I have been experimenting a little with the abc module in python. A la

>>> import abc

在正常情况下,如果ABC类包含未实现的abstractmethod,则希望不会实例化该类.您知道如下:

In the normal case you expect your ABC class to not be instantiated if it contains an unimplemented abstractmethod. You know like as follows:

>>> class MyClass(metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def mymethod(self):
...         return -1
...
>>> MyClass()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class MyClass with abstract methods mymethod

对于任何派生类,为

OR.直到您继承某项内容为止,一切似乎都可以正常工作,如dictlist所示,如下所示:

OR for any derived Class. It all seems to work fine until you inherit from something ... say dict or list as in the following:

>>> class YourClass(list, metaclass=abc.ABCMeta):
...     @abc.abstractmethod
...     def yourmethod(self):
...         return -1
...
>>> YourClass()
[]

这是令人惊讶的,因为type可能还是主要工厂或metaclass -ish之类的东西,所以我假定以下内容.

This is surprising because type is probably the primary factory or metaclass -ish thing anyway or so I assume from the following.

>>> type(abc.ABCMeta)
<class 'type'>
>>> type(list)
<class 'type'>

从一些调查中,我发现它可以归结为简单的事情,就像将__abstractmethod__属性添加到类的object一样,其余的事情会自己发生:

From some investigation I found out that it boils down to something as simple as adding an __abstractmethod__ attribute to the class' object and rest happens by itself:

>>> class AbstractClass:
...     pass
...
>>> AbstractClass.__abstractmethods__ = {'abstractmethod'}
>>> AbstractClass()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class AbstractClass with abstract methods abstractmethod

因此,可以通过故意重写__new__方法并清除__abstractmethods__来简单地避免检查,如下所示:

So one can simply avoid the check by intentionally overriding the __new__ method and clearing out __abstractmethods__ as in below:

>>> class SupposedlyAbstractClass(metaclass=abc.ABCMeta):
...     def __new__(cls):
...         cls.__abstractmethods__ = {}
...         return super(AbstractClass, cls).__new__(cls)
...     @abc.abstractmethod
...     def abstractmethod(self):
...         return -1
...
>>> SupposedlyAbstractClass()
<__main__.SupposedlyAbstractClass object at 0x000001FA6BF05828>

此行为在Python 2.7和Python 3.7中与我亲自检查的相同.我不知道其他所有python实现是否都一样.

This behaviour is the same in Python 2.7 and in Python 3.7 as I have personally checked. I am not aware if this is the same for all other python implementations.

最后,请问问题……为什么要使其表现得如此?我们永远不应该从listtupledict制作抽象类吗?还是应该继续进行操作,并在实例化之前添加__new__类方法来检查__abstractmethods__?

Finally, down to the question ... Why has this been made to behave like so? Is it wise we should never make abstract classes out of list, tuple or dict? or should I just go ahead and add a __new__ class method checking for __abstractmethods__ before instantiation?

推荐答案

问题

如果您有下一堂课,

The problem

If you have the next class:

from abc import ABC, abstractmethod
class Foo(list, ABC):
    @abstractmethod
    def yourmethod(self):
        pass

问题在于,Foo 的对象可以被创建且没有任何错误,因为Foo.__new__(Foo)将调用直接委派给list.__new__(Foo)而不是ABC.__new__(Foo)(负责检查是否所有抽象方法都在要实例化的类中实现)

the problem is that and object of Foo can be created without any error because Foo.__new__(Foo) delegates the call directly to list.__new__(Foo) instead of ABC.__new__(Foo) (which is responsible of checking that all abstract methods are implemented in the class that is going to be instantiated)

我们可以在Foo上实现__new__并尝试调用ABC.__new__:

We could implement __new__ on Foo and try to call ABC.__new__:

class Foo(list, ABC):
    def __new__(cls, *args, **kwargs):
        return ABC.__new__(cls)

    @abstractmethod
    def yourmethod(self):
        pass
Foo()

但是他提出了下一个错误:

But he next error is raised:

TypeError: object.__new__(Foo) is not safe, use list.__new__()

这是由于ABC.__new__(Foo)调用object.__new__(Foo)导致的,当Foo继承自list

This is due to ABC.__new__(Foo) invokes object.__new__(Foo) which seems that is not allowed when Foo inherits from list

您可以在Foo.__new__上添加其他代码,以检查是否实现了要实例化的类中的所有抽象方法(基本上完成了ABC.__new__的工作).

You can add additional code on Foo.__new__ in order to check that all abstract methods in the class to be instantiated are implemented (basically do the job of ABC.__new__).

类似这样的东西:

class Foo(list, ABC):
    def __new__(cls, *args, **kwargs):
        if hasattr(cls, '__abstractmethods__') and len(cls.__abstractmethods__) > 0:
            raise TypeError(f"Can't instantiate abstract class {cls.__name__} with abstract methods {', '.join(cls.__abstractmethods__)}")
        return super(Foo, cls).__new__(cls)


    @abstractmethod
    def yourmethod(self):
        return -1

现在Foo()引发错误.但是下一个代码运行没有任何问题:

Now Foo() raises an error. But the next code runs without any issue:

class Bar(Foo):
     def yourmethod(self):
         pass
Bar()

这篇关于为什么abc.ABCMeta抽象实例化检查不能用于list和dict的派生类?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆