如何验证Python中的鸭子类型接口?
class ITestType(object):
""" Sample interface type """
__metaclass__ = ABCMeta
@abstractmethod
def requiredCall(self):
return
class TestType1(object):
""" Valid type? """
def requiredCall(self):
pass
class TestType2(ITestType):
""" Valid type """
def requiredCall(self):
pass
class TestType3(ITestType):
""" Invalid type """
pass
在上面的例子中,issubclass(TypeType*, ITestType)
对于 2 会返回真,而对于 1 和 3 则返回假。
有没有其他的方法可以使用 issubclass,或者有其他的接口测试方法,可以让 1 和 2 通过,但拒绝 3 呢?
对我来说,能够使用鸭子类型(duck typing)而不是明确将类绑定到抽象类型是非常有帮助的,同时也能在鸭子类型的对象通过特定接口时进行对象检查。
是的,我知道 Python 的人不太喜欢接口,标准的方法论是“在失败时找到问题,并用异常包裹一切”,但这和我的问题完全无关。不,我不能在这个项目中简单地不使用接口。
编辑:
太好了!对于其他看到这个问题的人,这里有一个如何使用 subclasshook 的例子:
class ITestType(object):
""" Sample interface type """
__metaclass__ = ABCMeta
@abstractmethod
def requiredCall(self):
return
@classmethod
def __subclasshook__(cls, C):
required = ["requiredCall"]
rtn = True
for r in required:
if not any(r in B.__dict__ for B in C.__mro__):
rtn = NotImplemented
return rtn
3 个回答
这里有一个替代方案,在实际使用中效果也很好,不需要在每次创建类实例时都检查整个字典,省去了很多麻烦。
(兼容Python 2和Python 3)
用法:
class Bar():
required_property_1 = ''
def required_method(self):
pass
# Module compile time check that Foo implements Bar
@implements(Bar)
class Foo(UnknownBaseClassUnrelatedToBar):
required_property_1
def required_method(self):
pass
# Run time check that Foo uses @implements or defines its own __implements() member
def accepts_bar(self, anything):
if not has_api(anything, Bar):
raise Exception('Target does not implement Bar')
...
你还可以做一些显而易见的事情,比如 @implements(Stream, Folder, Bar),当它们都需要一些相同的方法时,这样在实际应用中比继承更有用。
代码:
import inspect
def implements(*T):
def inner(cls):
cls.__implements = []
for t in T:
# Look for required methods
t_methods = inspect.getmembers(t, predicate=lambda x: inspect.isfunction(x) or inspect.ismethod(x))
c_methods = inspect.getmembers(cls, predicate=lambda x: inspect.isfunction(x) or inspect.ismethod(x))
sig = {}
for i in t_methods:
name = 'method:%s' % i[0]
if not name.startswith("method:__"):
sig[name] = False
for i in c_methods:
name = 'method:%s' % i[0]
if name in sig.keys():
sig[name] = True
# Look for required properties
t_props = [i for i in inspect.getmembers(t) if i not in t_methods]
c_props = [i for i in inspect.getmembers(cls) if i not in c_methods]
for i in t_props:
name = 'property:%s' % i[0]
if not name.startswith("property:__"):
sig[name] = False
for i in c_props:
name = 'property:%s' % i[0]
if name in sig.keys():
sig[name] = True
missing = False
for i in sig.keys():
if not sig[i]:
missing = True
if missing:
raise ImplementsException(cls, t, sig)
cls.__implements.append(t)
return cls
return inner
def has_api(instance, T):
""" Runtime check for T in type identity """
rtn = False
if instance is not None and T is not None:
if inspect.isclass(instance):
if hasattr(instance, "__implements"):
if T in instance.__implements:
rtn = True
else:
if hasattr(instance.__class__, "__implements"):
if T in instance.__class__.__implements:
rtn = True
return rtn
class ImplementsException(Exception):
def __init__(self, cls, T, signature):
msg = "Invalid @implements decorator on '%s' for interface '%s': %r" % (cls.__name__, T.__name__, signature)
super(ImplementsException, self).__init__(msg)
self.signature = signature
这事儿虽然晚了几年,但我来分享一下我的做法:
import abc
class MetaClass(object):
__metaclass__ = abc.ABCMeta
[...]
@classmethod
def __subclasshook__(cls, C):
if C.__abstractmethods__:
print C.__abstractmethods__
return False
else:
return True
如果 C
是一个尝试创建的 MetaClass
类,那么只有当 C
实现了所有的抽象方法时,C.__abstractmethods__
才会是空的。
详细信息可以查看这里:https://www.python.org/dev/peps/pep-3119/#the-abc-module-an-abc-support-framework(在“实现”部分,但搜索 __abstractmethods__
应该能找到相关段落)
我在哪些地方用过这个:
我可以创建 MetaClass
。然后我可以从 BaseClass
和 MetaClass
继承,创建一个需要额外功能的 SubClass
。但是我需要把一个 BaseClass
的实例转换成 SubClass
,因为我不拥有 BaseClass
,但我有它的实例想要进行转换。
不过,如果我不正确地实现 SubClass
,我仍然可以进行转换,除非我使用上面提到的 __subclasshook__
,并在转换过程中添加一个子类检查(我应该这样做,因为我只想尝试将父类转换为子类)。如果有人需要,我可以提供一个最小可重现示例(MWE)。
补充:这里是一个最小可重现示例。我之前的建议可能不正确,所以以下内容似乎实现了我想要的效果。
目标是能够将一个 BaseClass
对象转换为 SubClass
,再转换回来。从 SubClass
转换到 BaseClass
很简单。但从 BaseClass
转换到 SubClass
就不那么容易了。通常的做法是更新 __class__
属性,但这样会有风险,因为 SubClass
可能并不是真正的子类,或者是从一个抽象元类派生的,但没有正确实现。
下面的转换是在 BaseMetaClass
实现的 convert
方法中完成的。不过,在这个逻辑中,我检查了两个事情。首先,为了转换为子类,我检查它是否确实是一个子类。其次,我检查 __abstractmethods__
属性,看它是否为空。如果为空,那么它也是一个正确实现的元类。如果不符合条件,就会引发一个类型错误。否则,对象就会被转换。
import abc
class BaseMetaClass(object):
__metaclass__ = abc.ABCMeta
@classmethod
@abc.abstractmethod
def convert(cls, obj):
if issubclass(cls, type(obj)):
if cls.__abstractmethods__:
msg = (
'{0} not a proper subclass of BaseMetaClass: '
'missing method(s)\n\t'
).format(
cls.__name__
)
mthd_list = ',\n\t'.join(
map(
lambda s: cls.__name__ + '.' + s,
sorted(cls.__abstractmethods__)
)
)
raise TypeError(msg + mthd_list)
else:
obj.__class__ = cls
return obj
else:
msg = '{0} not subclass of {1}'.format(
cls.__name__,
type(obj).__name__
)
raise TypeError(msg)
@abc.abstractmethod
def abstractmethod(self):
return
class BaseClass(object):
def __init__(self, x):
self.x = x
def __str__(self):
s0 = "BaseClass:\n"
s1 = "x: {0}".format(self.x)
return s0 + s1
class AnotherBaseClass(object):
def __init__(self, z):
self.z = z
def __str__(self):
s0 = "AnotherBaseClass:\n"
s1 = "z: {0}".format(self.z)
return s0 + s1
class GoodSubClass(BaseMetaClass, BaseClass):
def __init__(self, x, y):
super(GoodSubClass, self).__init__(x)
self.y = y
@classmethod
def convert(cls, obj, y):
super(GoodSubClass, cls).convert(obj)
obj.y = y
def to_base(self):
return BaseClass(self.x)
def abstractmethod(self):
print "This is the abstract method"
def __str__(self):
s0 = "SubClass:\n"
s1 = "x: {0}\n".format(self.x)
s2 = "y: {0}".format(self.y)
return s0 + s1 + s2
class BadSubClass(BaseMetaClass, BaseClass):
def __init__(self, x, y):
super(BadSubClass, self).__init__(x)
self.y = y
@classmethod
def convert(cls, obj, y):
super(BadSubClass, cls).convert(obj)
obj.y = y
def __str__(self):
s0 = "SubClass:\n"
s1 = "x: {0}\n".format(self.x)
s2 = "y: {0}".format(self.y)
return s0 + s1 + s2
base1 = BaseClass(1)
print "BaseClass instance"
print base1
print
GoodSubClass.convert(base1, 2)
print "Successfully casting BaseClass to GoodSubClass"
print base1
print
print "Cannot cast BaseClass to BadSubClass"
base1 = BaseClass(1)
try:
BadSubClass.convert(base1, 2)
except TypeError as e:
print "TypeError: {0}".format(e.message)
print
print "Cannot cast AnotherBaseCelass to GoodSubClass"
anotherbase = AnotherBaseClass(5)
try:
GoodSubClass.convert(anotherbase, 2)
except TypeError as e:
print "TypeError: {0}".format(e.message)
print
print "Cannot cast AnotherBaseCelass to BadSubClass"
anotherbase = AnotherBaseClass(5)
try:
BadSubClass.convert(anotherbase, 2)
except TypeError as e:
print "TypeError: {0}".format(e.message)
print
# BaseClass instance
# BaseClass:
# x: 1
# Successfully casting BaseClass to GoodSubClass
# SubClass:
# x: 1
# y: 2
# Cannot cast BaseClass to BadSubClass
# TypeError: BadSubClass not a proper subclass of BaseMetaClass: missing method(s)
# BadSubClass.abstractmethod
# Cannot cast AnotherBaseCelass to GoodSubClass
# TypeError: GoodSubClass not subclass of AnotherBaseClass
# Cannot cast AnotherBaseCelass to BadSubClass
# TypeError: BadSubClass not subclass of AnotherBaseClass
可以看看ABC
模块。这个模块让你可以定义一个抽象基类,它里面有一个叫__subclasshook__
的方法。这个方法可以根据你设定的标准来判断某个类是否是这个抽象基类的子类,比如你可以规定“这个类有方法X、Y和Z”或者其他条件。然后你就可以用issubclass()
或者isinstance()
来检查类和实例是否符合这些接口。