摘要 classes 和 PyMongo;无法实例化抽象 class
Abstract classes and PyMongo; can't instantiate abstract class
我创建了空摘要 class AbstractStorage
并从中继承了 Storage
class:
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
就变成普通的 class),问题(显然)就会消失 and/or 如果我设置 dbh
到其他一些值。
这是怎么回事?
这实际上不是 ABC 的问题,而是 PyMongo 的问题。有一个问题 here。似乎 pymongo 将 __getattr__
覆盖为 return 某种数据库 class。这意味着 host.__isabstractmethod__
return 是一个数据库对象,在布尔上下文中为真。这导致 ABCMeta 认为 host
是一个抽象方法:
>>> bool(host.__isabstractmethod__)
True
问题报告中描述的解决方法是在您的对象上手动设置 host.__isabstractmethod__ = False
。关于这个问题的最后评论表明已经为 pymongo 3.0 提供了修复。
精简版
mongo.MongoClient
returns 一个看起来是(是?)抽象方法的对象,然后您将其分配给 Storage
中的 dbh
字段。这使得 Storage
成为抽象的 class,因此实例化它会引发 TypeError
.
请注意,我没有 pymongo
,所以除了 ABCMeta
.
对它的处理方式,我无法告诉你更多关于 MongoClient
的信息
长版
ABCMeta.__new__
方法查看它正在创建的新 class 的每个字段。任何本身具有 True
(或 "true-like")__isabstractmethod__
字段的字段都被视为抽象方法。如果 class 有 any 非覆盖的抽象方法,整个 class 被认为是抽象的,所以任何实例化它的尝试都是错误的。
来自标准库的早期版本 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
class 文档中没有提到,但在 @abc.abstractmethod
装饰器下稍微低一点:
In order to correctly interoperate with the abstract base class machinery, the descriptor must identify itself as abstract using __isabstractmethod__
. In general, this attribute should be True
if any of the methods used to compose the descriptor are abstract.
例子
我创建了一个带有 __isabstractmethod__
属性的假 "abstract-looking" class,以及 AbstractStorage
的两个假定的具体子 class。您会看到其中一个产生的错误与您得到的错误完全相同:
#!/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
我创建了空摘要 class AbstractStorage
并从中继承了 Storage
class:
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
就变成普通的 class),问题(显然)就会消失 and/or 如果我设置 dbh
到其他一些值。
这是怎么回事?
这实际上不是 ABC 的问题,而是 PyMongo 的问题。有一个问题 here。似乎 pymongo 将 __getattr__
覆盖为 return 某种数据库 class。这意味着 host.__isabstractmethod__
return 是一个数据库对象,在布尔上下文中为真。这导致 ABCMeta 认为 host
是一个抽象方法:
>>> bool(host.__isabstractmethod__)
True
问题报告中描述的解决方法是在您的对象上手动设置 host.__isabstractmethod__ = False
。关于这个问题的最后评论表明已经为 pymongo 3.0 提供了修复。
精简版
mongo.MongoClient
returns 一个看起来是(是?)抽象方法的对象,然后您将其分配给 Storage
中的 dbh
字段。这使得 Storage
成为抽象的 class,因此实例化它会引发 TypeError
.
请注意,我没有 pymongo
,所以除了 ABCMeta
.
MongoClient
的信息
长版
ABCMeta.__new__
方法查看它正在创建的新 class 的每个字段。任何本身具有 True
(或 "true-like")__isabstractmethod__
字段的字段都被视为抽象方法。如果 class 有 any 非覆盖的抽象方法,整个 class 被认为是抽象的,所以任何实例化它的尝试都是错误的。
来自标准库的早期版本 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
class 文档中没有提到,但在 @abc.abstractmethod
装饰器下稍微低一点:
In order to correctly interoperate with the abstract base class machinery, the descriptor must identify itself as abstract using
__isabstractmethod__
. In general, this attribute should beTrue
if any of the methods used to compose the descriptor are abstract.
例子
我创建了一个带有 __isabstractmethod__
属性的假 "abstract-looking" class,以及 AbstractStorage
的两个假定的具体子 class。您会看到其中一个产生的错误与您得到的错误完全相同:
#!/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