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.ABCMetaAbstractStorage通常のクラスになる)、および/またはdbh他の値に設定すると、問題は(明らかに)解消されます。

何が起きてる?

4

2 に答える 2

4

これは ABC の問題ではなく、PyMongo の問題です。ここに問題があります。pymongo が__getattr__何らかのデータベース クラスを返すようにオーバーライドしているようです。これはhost.__isabstractmethod__、ブール値のコンテキストで true である Database オブジェクトを返すことを意味します。これにより、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については何も言えません。MongoClientABCMeta

ロングバージョン

この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__属性を持つ偽の「抽象的に見える」クラスと、 2 つのおそらく具象的な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 に答える