0

我创建了空抽象类并从中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为其他值

这里发生了什么?

4

2 回答 2

4

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

>>> bool(host.__isabstractmethod__)
True

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

于 2015-01-28T21:03:16.097 回答
1

精简版

mongo.MongoClient返回一个看起来是(是?)抽象方法的对象,然后将其分配给dbh. Storage这产生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-01-28T22:04:19.473 回答