簡體   English   中英

在Python中,如何強制抽象方法在子類上是靜態的?

[英]In Python, how to enforce an abstract method to be static on the child class?

這是我想要的設置:A應該是一個帶有靜態和抽象方法f()的抽象基類。 B應該從A繼承。要求:1。你不應該能夠實例化A 2.你不應該實例化B,除非它實現了靜態的f()

這個問題中汲取靈感,我嘗試了幾種方法。 有了這些定義:

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'

這里使用通常的Python約定定義A2和B2,並使用答案中建議的方式定義A和B. 以下是我嘗試的一些操作以及不希望的結果。

A / B班:

>>> B().f()
f
#This should have thrown, since B doesn't implement a static f()

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()

由於這些方法都沒有給我我想要的輸出,我如何實現我想要的?

只用ABCMeta你不能做你想做的ABCMeta ABC強制執行不進行任何類型檢查,只強制存在具有正確名稱的屬性

舉個例子:

>>> 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>

我能夠構造Concrete()即使foobar都是簡單的屬性。

ABCMeta元類僅跟蹤__isabstractmethod__屬性為真的剩余對象數量; 當從元類創建一個類( ABCMeta.__new__被調用)時,然后將cls.__abstractmethods__屬性設置為一個frozenset對象,其中所有名稱仍然是抽象的。

type.__new__然后測試該frozenset並在嘗試創建實例時拋出TypeError

你必須在這里制作自己的 __new__方法; 子類ABCMeta並在新的__new__方法中添加類型檢查。 該方法應該在基類上查找__abstractmethods__集,在MRO中找到具有__isabstractmethod__屬性的相應對象,然后對當前類屬性進行類型檢查。

這意味着您在定義時拋出異常,而不是實例。 為了實現這一點,你需要為你的ABCMeta子類添加一個__call__方法,並根據你自己的__new__方法收集的關於哪些類型錯誤的信息拋出異常; 類似於ABCMetatype.__new__做的兩個階段的過程。 或者,更新類上的__abstractmethods__設置以添加已實現但具有錯誤類型的任何名稱,並使其type.__new__以拋出異常。

以下實現采用最后的方法; 如果實現的類型不匹配(使用映射), __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

使用此元類,您可以獲得預期的輸出:

>>> 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM