[英]In Python, how to enforce an abstract method to be static on the child class?
This is the setup I want: A should be an abstract base class with a static & abstract method f(). 这是我想要的设置:A应该是一个带有静态和抽象方法f()的抽象基类。 B should inherit from A. Requirements: 1. You should not be able to instantiate A 2. You should not be able to instantiate B, unless it implements a static f()
B应该从A继承。要求:1。你不应该能够实例化A 2.你不应该实例化B,除非它实现了静态的f()
Taking inspiration from this question, I've tried a couple of approaches. 从这个问题中汲取灵感,我尝试了几种方法。 With these definitions:
有了这些定义:
class abstractstatic(staticmethod):
__slots__ = ()
def __init__(self, function):
super(abstractstatic, self).__init__(function)
function.__isabstractmethod__ = True
__isabstractmethod__ = True
class A:
__metaclass__ = abc.ABCMeta
@abstractstatic
def f():
pass
class B(A):
def f(self):
print 'f'
class A2:
__metaclass__ = abc.ABCMeta
@staticmethod
@abc.abstractmethod
def f():
pass
class B2(A2):
def f(self):
print 'f'
Here A2 and B2 are defined using usual Python conventions and A & B are defined using the way suggested in this answer. 这里使用通常的Python约定定义A2和B2,并使用本答案中建议的方式定义A和B. Following are some operations I tried and the results that were undesired.
以下是我尝试的一些操作以及不希望的结果。
With classes A/B: A / B班:
>>> B().f()
f
#This should have thrown, since B doesn't implement a static f()
With classes A2/B2: A2 / B2类:
>>> A2()
<__main__.A2 object at 0x105beea90>
#This should have thrown since A2 should be an uninstantiable abstract class
>>> B2().f()
f
#This should have thrown, since B2 doesn't implement a static f()
Since neither of these approaches give me the output I want, how do I achieve what I want? 由于这些方法都没有给我我想要的输出,我如何实现我想要的?
You can't do what you want with just ABCMeta
. 只用
ABCMeta
你不能做你想做的ABCMeta
。 ABC enforcement doesn't do any type checking, only the presence of an attribute with the correct name is enforced. ABC强制执行不进行任何类型检查,只强制存在具有正确名称的属性 。
Take for example: 举个例子:
>>> from abc import ABCMeta, abstractmethod, abstractproperty
>>> class Abstract(object):
... __metaclass__ = ABCMeta
... @abstractmethod
... def foo(self): pass
... @abstractproperty
... def bar(self): pass
...
>>> class Concrete(Abstract):
... foo = 'bar'
... bar = 'baz'
...
>>> Concrete()
<__main__.Concrete object at 0x104b4df90>
I was able to construct Concrete()
even though both foo
and bar
are simple attributes. 我能够构造
Concrete()
即使foo
和bar
都是简单的属性。
The ABCMeta
metaclass only tracks how many objects are left with the __isabstractmethod__
attribute being true; ABCMeta
元类仅跟踪__isabstractmethod__
属性为真的剩余对象数量; when creating a class from the metaclass ( ABCMeta.__new__
is called) the cls.__abstractmethods__
attribute is then set to a frozenset
object with all the names that are still abstract. 当从元类创建一个类(
ABCMeta.__new__
被调用)时,然后将cls.__abstractmethods__
属性设置为一个frozenset
对象,其中所有名称仍然是抽象的。
type.__new__
then tests for that frozenset
and throws a TypeError
if you try to create an instance. type.__new__
然后测试该frozenset
并在尝试创建实例时抛出TypeError
。
You'd have to produce your own __new__
method here; 你必须在这里制作自己的
__new__
方法; subclass ABCMeta
and add type checking in a new __new__
method. 子类
ABCMeta
并在新的__new__
方法中添加类型检查。 That method should look for __abstractmethods__
sets on the base classes, find the corresponding objects with the __isabstractmethod__
attribute in the MRO, then does typechecking on the current class attributes. 该方法应该在基类上查找
__abstractmethods__
集,在MRO中找到具有__isabstractmethod__
属性的相应对象,然后对当前类属性进行类型检查。
This'd mean that you'd throw the exception when defining the class , not an instance, however. 这意味着您在定义类时抛出异常,而不是实例。 For that to work you'd add a
__call__
method to your ABCMeta
subclass and have that throw the exception based on information gathered by your own __new__
method about what types were wrong; 为了实现这一点,你需要为你的
ABCMeta
子类添加一个__call__
方法,并根据你自己的__new__
方法收集的关于哪些类型错误的信息抛出异常; a similar two-stage process as what ABCMeta
and type.__new__
do at the moment. 类似于
ABCMeta
和type.__new__
做的两个阶段的过程。 Alternatively, update the __abstractmethods__
set on the class to add any names that were implemented but with the wrong type and leave it to type.__new__
to throw the exception. 或者,更新类上的
__abstractmethods__
设置以添加已实现但具有错误类型的任何名称,并使其type.__new__
以抛出异常。
The following implementation takes that last tack; 以下实现采用最后的方法; add names back to
__abstractmethods__
if the implemented type doesn't match (using a mapping): 如果实现的类型不匹配(使用映射),
__abstractmethods__
名称添加回__abstractmethods__
:
from types import FunctionType
class ABCMetaTypeCheck(ABCMeta):
_typemap = { # map abstract type to expected implementation type
abstractproperty: property,
abstractstatic: staticmethod,
# abstractmethods return function objects
FunctionType: FunctionType,
}
def __new__(mcls, name, bases, namespace):
cls = super(ABCMetaTypeCheck, mcls).__new__(mcls, name, bases, namespace)
wrong_type = set()
seen = set()
abstractmethods = cls.__abstractmethods__
for base in bases:
for name in getattr(base, "__abstractmethods__", set()):
if name in seen or name in abstractmethods:
continue # still abstract or later overridden
value = base.__dict__.get(name) # bypass descriptors
if getattr(value, "__isabstractmethod__", False):
seen.add(name)
expected = mcls._typemap[type(value)]
if not isinstance(namespace[name], expected):
wrong_type.add(name)
if wrong_type:
cls.__abstractmethods__ = abstractmethods | frozenset(wrong_type)
return cls
With this metaclass you get your expected output: 使用此元类,您可以获得预期的输出:
>>> class Abstract(object):
... __metaclass__ = ABCMetaTypeCheck
... @abstractmethod
... def foo(self): pass
... @abstractproperty
... def bar(self): pass
... @abstractstatic
... def baz(): pass
...
>>> class ConcreteWrong(Abstract):
... foo = 'bar'
... bar = 'baz'
... baz = 'spam'
...
>>> ConcreteWrong()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class ConcreteWrong with abstract methods bar, baz, foo
>>>
>>> class ConcreteCorrect(Abstract):
... def foo(self): return 'bar'
... @property
... def bar(self): return 'baz'
... @staticmethod
... def baz(): return 'spam'
...
>>> ConcreteCorrect()
<__main__.ConcreteCorrect object at 0x104ce1d10>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.