python - python 抽象类和 PyMongo ;不能实例化抽象类

  显示原文与译文双语对照的内容

我创建了空抽象类 AbstractStorage 并从它继承了 Storage 类:


import abc


import pymongo as mongo



host = mongo.MongoClient()



print(host.alive()) # True



class AbstractStorage(metaclass=abc.ABCMeta):


 pass



class Storage(AbstractStorage):


 dbh = host


 def __init__(self):


 print('__init__')



Storage()



我期望输出是


True


__init__



但是我得到的是


True


Traceback (most recent call last):


 File"/home/vaultah/run.py", line 16, in <module>


 Storage()


TypeError: Can't instantiate abstract class Storage with abstract methods dbh



如果我删除 metaclass=abc.ABCMeta ( 所以 AbstractStorage 成为一个普通的类) 和/或者将 dbh 设置为其他值,那么( 明显明显) 会消失。

.这里正在做什么?

时间:

这并不是abc的问题,它是PyMongo的问题。 这里有一个关于的问题。 似乎pymongo重写 __getattr__ 以返回某种数据库类。 这意味着 host.__isabstractmethod__ 返回一个数据库对象,它在布尔上下文中是 true 。 这导致ABCMeta认为 host 是一种抽象方法:


>>> bool(host.__isabstractmethod__)


True



问题报告中描述的解决方法是在你的对象上手动设置 host.__isabstractmethod__ = False 。 关于这个问题的最后评论建议 pymongo 3.0已经修复了。

mongo.MongoClient 返回一个象( 是) 一样的抽象方法,然后将它的分配给 Storage 中的dbh 字段。 这使得 Storage 成为一个抽象类,因此实例化它就会引发 TypeError

注意,我没有 pymongo,所以我不能告诉你更多关于 MongoClient的信息,比如 ABCMeta的处理方式。

长版本

ABCMeta.__new__ 方法在创建的新类的每个字段中查找。 它本身拥有 True ( 或者或或者"真像") __isabstractmethod__ 字段的任何字段都被认为是抽象。 如果某个类具有非重写的抽象方法,而非重写的抽象方法,那么整个类被认为是抽象的,因此任何。

从早期版本的库标准 abc.py 中:


def __new__(mcls, name, bases, namespace):


 cls = super().__new__(mcls, name, bases, namespace)


 # Compute set of abstract method names


 abstracts = {name


 for name, value in namespace.items()


 if getattr(value,"__isabstractmethod__", False)}


 #.. .


 cls.__abstractmethods__ = frozenset(abstracts)


 #.. .



abc.ABCMeta 类文档中没有提到这一点,但在 @abc.abstractmethod decorator下,这一点略低一些:

为了与抽象基类机器正确地交互,描述符必须使用 __isabstractmethod__ 将它的标识为抽象。 通常,如果用于构成描述符的任何方法都是抽象的,则该属性应该是 True

示例

我用 __isabstractmethod__ 属性创建了一个伪"抽象外观"类,并创建了 AbstractStorage的两个具体子类。 你将看到,其中一个产生了你正在获取的准确错误:


#!/usr/bin/env python3



import abc


# I don't have pymongo, so I have to fake it. See CounterfeitAbstractMethod.


#import pymongo as mongo



class CounterfeitAbstractMethod():


"""


 This class appears to be an abstract method to the abc.ABCMeta.__new__


 method.



 Normally, finding an abstract method in a class's namespace means


 that class is also abstract, so instantiating that class is an


 error.



 If a class derived from abc.ABCMeta has an instance of


 CounterfeitAbstractMethod as a value anywhere in its namespace


 dictionary, any attempt to instantiate that class will raise a


 TypeError: Can't instantiate abstract class <classname> with


 abstract method <fieldname>.


"""


 __isabstractmethod__ = True



class AbstractStorage(metaclass=abc.ABCMeta):



 def __init__(self):


"""


 Do-nothing initializer that prints the name of the (sub)class


 being initialized.


"""


 print(self.__class__.__name__ +".__init__ executing.")


 return



class ConcreteStorage(AbstractStorage):


"""


 A concrete class that also _appears_ concrete to abc.ABCMeta. This


 class can be instantiated normally.


"""


 whatever ="Anything that doesn't appear to be an abstract method will do."



class BogusStorage(AbstractStorage):


"""


 This is (supposedly) a concrete class, but its whatever field appears


 to be an abstract method, making this whole class abstract ---


 abc.ABCMeta will refuse to construct any this class.


"""


 #whatever = mongo.MongoClient('localhost', 27017)


 whatever = CounterfeitAbstractMethod()



def main():


"""


 Print details of the ConcreteStorage and BogusStorage classes.


"""


 for cls in ConcreteStorage, BogusStorage:


 print(cls.__name__ +":")


 print(" whatever field:" + str(cls.whatever))


 print(" abstract methods:" + str(cls.__abstractmethods__))


 print(" Instantiating...")


 print("", end="")


 # KABOOM! Instantiating BogusStorage will raise a TypeError,


 # because it appears to be an _abstract_ class.


 instance = cls()


 print(" instance:" + str(instance))


 print()


 return



if"__main__" == __name__:


 main()



运行这里命令产生:


$./storage.py


ConcreteStorage:


 whatever field: Anything that doesn't appear to be an abstract method will do.


 abstract methods: frozenset()


 Instantiating...


 ConcreteStorage.__init__ executing.


 instance: <__main__.ConcreteStorage object at 0x253afd0>



BogusStorage:


 whatever field: <__main__.CounterfeitAbstractMethod object at 0x253ad50>


 abstract methods: frozenset({'whatever'})


 Instantiating...


 Traceback (most recent call last):


 File"./storage.py", line 75, in <module>


 main()


 File"./storage.py", line 68, in main


 instance = cls()


TypeError: Can't instantiate abstract class BogusStorage with abstract methods whatever



...