【问题标题】:Abstract classes and PyMongo; can't instantiate abstract class抽象类和 PyMongo;无法实例化抽象类
【发布时间】:2017-02-05 15:54:32
【问题描述】:

我创建了空抽象类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 设置为其他值,问题(显然)就会消失。

这是怎么回事?

【问题讨论】:

    标签: python python-3.x abstract-class pymongo pymongo-2.x


    【解决方案1】:

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

    >>> bool(host.__isabstractmethod__)
    True
    

    问题报告中描述的解决方法是在您的对象上手动设置host.__isabstractmethod__ = False。关于该问题的最后一条评论表明 pymongo 3.0 已修复。

    【讨论】:

    • 哇。谢谢 :) 也许我应该在类定义中写 if mongo.version_tuple &lt; (3, 0): dbh.__isabstractmethod__ = False 之类的东西。
    • @vaultah:你可以这样做。不过,我认为即使在更高版本中设置该属性也不会造成伤害;它看起来有点奇怪。
    • 恭喜您找到了实际的错误报告。 (在其他新闻中:我输入 way 的速度比我想象的要慢。)
    【解决方案2】:

    短版

    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 装饰器下面有点低:

    为了与抽象基类机制正确互操作,描述符必须使用__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
    

    【讨论】:

      猜你喜欢
      • 2015-10-06
      • 2015-08-04
      • 1970-01-01
      • 2013-03-07
      • 2012-09-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多