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返回true,为1和3返回false。
是否有另一种方法可以使用issubclass或其他方法进行接口测试,允许1 和 2通过,但拒绝3?
能够使用duck typing而不是将类显式绑定到抽象类型对我来说非常有帮助,但是当duck-typed对象通过特定接口时也允许对象检查。
是的,我知道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
答案 0 :(得分:9)
查看ABC
module。您可以定义一个抽象基类,该基类提供__subclasshook__
方法,该方法根据您喜欢的任何条件定义特定类“是否是抽象基类的子类” - 例如“它有方法X,Y和Z“或其他什么。然后,您可以使用issubclass()
或isinstance()
来检测类和实例上的接口。
答案 1 :(得分:2)
这已经晚了几年,但这就是我做的方式:
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.__abstractmethods__
只有在C
实现所有抽象方法时才会为空。
请参阅此处了解详细信息:https://www.python.org/dev/peps/pep-3119/#the-abc-module-an-abc-support-framework(它位于“实施”下,但搜索__abstractmethods__
会使您找到正确的段落)
这对我有用:
我可以创建MetaClass
。然后,我可以将BaseClass
和MetaClass
子类化为创建SubClass
,这需要一些额外的功能。但我需要通过更改BaseClass
属性将SubClass
的实例强制转换为__cls__
,因为我不拥有BaseClass
但是我得到了它的实例想要抛弃。
但是,如果我不正确地实现SubClass
,我仍然可以抛弃,除非我使用上面的__subclasshook__
并在我执行强制转换过程时添加一个子类检查(我应该这样做我想只尝试将父类强制转换。如果有人要求,我可以为此提供一个MWE。
ETA:这是一个MWE。我认为我之前提出的建议是不正确的,所以以下似乎可以按照我的意图行事。
目标是能够将BaseClass
对象转换为SubClass
并返回。从SubClass
到BaseClass
很容易。但是从BaseClass
到SubClass
并没有那么多。标准的做法是更新__class__
属性,但是当SubClass
实际上不是子类或从抽象元类派生但未正确实现时,它会留下空缺。
下面,转换是在convert
实现的BaseMetaClass
方法中完成的。但是,在那个逻辑中,我检查了两件事。一,为了转换为子类,我检查它是否确实是一个子类。其次,我检查属性__abstractmethods__
以查看它是否为空。如果是,那么它也是一个正确实现的元类。失败会导致TypeError。否则转换对象。
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
答案 2 :(得分:1)
这里有一个在实践中也很有效的替代方案,没有在每个类实例创建时检查整个字典的麻烦。
(py2和py3兼容)
用法:
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