Абстрактные классы и 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 к какой-то другой ценности.

Что тут происходит?

2 ответа

Решение

На самом деле это не проблема ABC, это проблема PyMongo. Здесь есть проблема. Кажется, что Pymongo переопределяет __getattr__ вернуть какой-то класс базы данных. Это означает, что host.__isabstractmethod__ возвращает объект базы данных, который является истинным в логическом контексте. Это заставляет ABCMeta полагать, что host это абстрактный метод:

>>> bool(host.__isabstractmethod__)
True

Обходной путь, описанный в отчете о проблеме, должен быть установлен вручную host.__isabstractmethod__ = False на вашем объекте. В последнем комментарии к проблеме предлагается исправление для pymongo 3.0.

Укороченная версия

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
Другие вопросы по тегам