Python - 为子类强制执行特定的方法签名?
Python - Enforce specific method signature for subclasses?
我想创建一个class,它定义了一个特定的接口,然后要求所有子class都符合这个接口。例如,我想定义一个 class
class Interface:
def __init__(self, arg1):
pass
def foo(self, bar):
pass
然后请放心,如果我持有任何类型为 A
的元素 a
,Interface
的子 class,那么我可以调用 a.foo(2)
它会起作用。
看起来 this question 几乎解决了问题,但在那种情况下,要由 subclass 明确更改它的 metaclass.
理想情况下,我正在寻找的是类似于 Rust 的 Traits 和 Impls 的东西,我可以在其中指定一个特定的 Trait 和一个 trait 需要定义的方法列表,然后我可以确信任何对象该特征定义了那些方法。
在 Python 中有什么方法可以做到这一点吗?
您可以遵循 pyspark
模式,其中基础 class 的方法执行(可选的)参数有效性检查,然后调用子 [=] 的 "non-public" 方法15=],例如:
class Regressor():
def fit(self, X, y):
self._check_arguments(X, y)
self._fit(X, y)
def _check_arguments(self, X, y):
if True:
pass
else:
raise ValueError('Invalid arguments.')
class LinearRegressor(Regressor):
def _fit(self, X, y):
# code here
所以,首先,要说明一个显而易见的事实 - Python 有一个内置机制来测试 存在性 方法和 属性 在派生的 classes 中 - 它只是不检查他们的签名。
其次,一个不错的包是 zope.interface
。 Despte zope
命名空间,它是一个完整的独立包,允许使用真正简洁的方法来拥有可以公开多个接口的对象,但仅在需要时使用 - 然后释放命名空间。在习惯之前,它肯定需要一些学习,但它可以非常强大,并为大型项目提供非常好的模式。
它是为 Python 2 设计的,当时 Python 的功能比现在少得多 - 我认为它不执行自动接口检查(必须手动调用方法来查找-out 如果 class 是合规的) - 但是自动化这个调用会很容易,尽管如此。
第三,How to enforce method signature for child classes? 处链接的已接受答案几乎可以工作,只需进行一处更改就足够了。该示例的问题在于它硬编码了对 type
的调用以创建新的 class,并且不传递有关元 class 本身的 type.__new__
信息。替换行:
return type(name, baseClasses, d)
用于:
return super().__new__(cls, name, baseClasses, d)
然后,使基础class——定义你需要的方法的那个使用元class——它将被任何子class正常继承。 (只需使用 Python 的 3 语法来指定元 classes)。
抱歉 - 该示例是 Python 2 - 它也需要更改另一行,我最好重新发布它:
from types import FunctionType
# from
class SignatureCheckerMeta(type):
def __new__(mcls, name, baseClasses, d):
#For each method in d, check to see if any base class already
#defined a method with that name. If so, make sure the
#signatures are the same.
for methodName in d:
f = d[methodName]
for baseClass in baseClasses:
try:
fBase = getattr(baseClass, methodName)
if not inspect.getargspec(f) == inspect.getargspec(fBase):
raise BadSignatureException(str(methodName))
except AttributeError:
#This method was not defined in this base class,
#So just go to the next base class.
continue
return super().__new__(mcls, name, baseClasses, d)
在查看时,我发现其中没有机制来强制实际 实现了一个方法。 IE。如果派生 class 中存在同名方法,则强制执行其签名,但如果派生 class 中根本不存在该方法,则上面的代码将无法找到它(并且将调用 superclass 上的方法 - 这可能是所需的行为)。
答案:
第四-
虽然这会起作用,但它可能有点粗糙 - 因为它确实 any 覆盖任何 superclass 中的另一个方法的方法必须符合其签名。甚至兼容的签名也会中断。也许建立在 ABCMeta
和 @abstractmethod
existind 机制的基础上会很好,因为这些机制已经适用于所有极端情况。但是请注意,此示例基于上面的代码,并在 class 创建时检查签名,而 Python 中的抽象class 机制使它检查 class 何时被实例化。保持不变将使您能够使用大型 class 层次结构,这可能会在中间 classes 中保留一些抽象方法,而只有最终的具体 classes 必须实现所有方法。
只需使用它而不是 ABCMeta
作为接口 class 的元 class,并像往常一样将要检查接口的方法标记为 @abstractmethod
。
class M(ABCMeta):
def __init__(cls, name, bases, attrs):
errors = []
for base_cls in bases:
for meth_name in getattr(base_cls, "__abstractmethods__", ()):
orig_argspec = inspect.getfullargspec(getattr(base_cls, meth_name))
target_argspec = inspect.getfullargspec(getattr(cls, meth_name))
if orig_argspec != target_argspec:
errors.append(f"Abstract method {meth_name!r} not implemented with correct signature in {cls.__name__!r}. Expected {orig_argspec}.")
if errors:
raise TypeError("\n".join(errors))
super().__init__(name, bases, attrs)
我想创建一个class,它定义了一个特定的接口,然后要求所有子class都符合这个接口。例如,我想定义一个 class
class Interface:
def __init__(self, arg1):
pass
def foo(self, bar):
pass
然后请放心,如果我持有任何类型为 A
的元素 a
,Interface
的子 class,那么我可以调用 a.foo(2)
它会起作用。
看起来 this question 几乎解决了问题,但在那种情况下,要由 subclass 明确更改它的 metaclass.
理想情况下,我正在寻找的是类似于 Rust 的 Traits 和 Impls 的东西,我可以在其中指定一个特定的 Trait 和一个 trait 需要定义的方法列表,然后我可以确信任何对象该特征定义了那些方法。
在 Python 中有什么方法可以做到这一点吗?
您可以遵循 pyspark
模式,其中基础 class 的方法执行(可选的)参数有效性检查,然后调用子 [=] 的 "non-public" 方法15=],例如:
class Regressor():
def fit(self, X, y):
self._check_arguments(X, y)
self._fit(X, y)
def _check_arguments(self, X, y):
if True:
pass
else:
raise ValueError('Invalid arguments.')
class LinearRegressor(Regressor):
def _fit(self, X, y):
# code here
所以,首先,要说明一个显而易见的事实 - Python 有一个内置机制来测试 存在性 方法和 属性 在派生的 classes 中 - 它只是不检查他们的签名。
其次,一个不错的包是 zope.interface
。 Despte zope
命名空间,它是一个完整的独立包,允许使用真正简洁的方法来拥有可以公开多个接口的对象,但仅在需要时使用 - 然后释放命名空间。在习惯之前,它肯定需要一些学习,但它可以非常强大,并为大型项目提供非常好的模式。
它是为 Python 2 设计的,当时 Python 的功能比现在少得多 - 我认为它不执行自动接口检查(必须手动调用方法来查找-out 如果 class 是合规的) - 但是自动化这个调用会很容易,尽管如此。
第三,How to enforce method signature for child classes? 处链接的已接受答案几乎可以工作,只需进行一处更改就足够了。该示例的问题在于它硬编码了对 type
的调用以创建新的 class,并且不传递有关元 class 本身的 type.__new__
信息。替换行:
return type(name, baseClasses, d)
用于:
return super().__new__(cls, name, baseClasses, d)
然后,使基础class——定义你需要的方法的那个使用元class——它将被任何子class正常继承。 (只需使用 Python 的 3 语法来指定元 classes)。
抱歉 - 该示例是 Python 2 - 它也需要更改另一行,我最好重新发布它:
from types import FunctionType
# from
class SignatureCheckerMeta(type):
def __new__(mcls, name, baseClasses, d):
#For each method in d, check to see if any base class already
#defined a method with that name. If so, make sure the
#signatures are the same.
for methodName in d:
f = d[methodName]
for baseClass in baseClasses:
try:
fBase = getattr(baseClass, methodName)
if not inspect.getargspec(f) == inspect.getargspec(fBase):
raise BadSignatureException(str(methodName))
except AttributeError:
#This method was not defined in this base class,
#So just go to the next base class.
continue
return super().__new__(mcls, name, baseClasses, d)
在查看时,我发现其中没有机制来强制实际 实现了一个方法。 IE。如果派生 class 中存在同名方法,则强制执行其签名,但如果派生 class 中根本不存在该方法,则上面的代码将无法找到它(并且将调用 superclass 上的方法 - 这可能是所需的行为)。
答案:
第四-
虽然这会起作用,但它可能有点粗糙 - 因为它确实 any 覆盖任何 superclass 中的另一个方法的方法必须符合其签名。甚至兼容的签名也会中断。也许建立在 ABCMeta
和 @abstractmethod
existind 机制的基础上会很好,因为这些机制已经适用于所有极端情况。但是请注意,此示例基于上面的代码,并在 class 创建时检查签名,而 Python 中的抽象class 机制使它检查 class 何时被实例化。保持不变将使您能够使用大型 class 层次结构,这可能会在中间 classes 中保留一些抽象方法,而只有最终的具体 classes 必须实现所有方法。
只需使用它而不是 ABCMeta
作为接口 class 的元 class,并像往常一样将要检查接口的方法标记为 @abstractmethod
。
class M(ABCMeta):
def __init__(cls, name, bases, attrs):
errors = []
for base_cls in bases:
for meth_name in getattr(base_cls, "__abstractmethods__", ()):
orig_argspec = inspect.getfullargspec(getattr(base_cls, meth_name))
target_argspec = inspect.getfullargspec(getattr(cls, meth_name))
if orig_argspec != target_argspec:
errors.append(f"Abstract method {meth_name!r} not implemented with correct signature in {cls.__name__!r}. Expected {orig_argspec}.")
if errors:
raise TypeError("\n".join(errors))
super().__init__(name, bases, attrs)