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
In the example above issubclass(TypeType*, ITestType)
will return true for 2 and false for 1 and 3.
Is there an alternative way to use issubclass, or an alternative method for interface testing that will allow 1 and 2 to pass, but reject 3?
It'd be extremely helpful for me to be able to use duck typing rather than explicitly binding classes to abstract types, but also allow object checking when duck-typed objects pass through particular interfaces.
Yes, I'm aware that python people don't like interfaces, and the standard methodology is "find it when it fails and wrap everything in exceptions" but also completely irrelevant to my question. No, I cannot simply not use interfaces in this project.
edit:
Perfect! For anyone else who finds this question, here's the an example of how to use 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
Check out the ABC
module. You can define an abstract base class that provides a __subclasshook__
method that defines whether a particular class "is a subclass" of the abstract base class based on any criteria you like -- such as "it has methods X, Y and Z" or whatever. Then you can use issubclass()
or isinstance()
to detect interfaces on classes and instances.
this is a couple of years late but here's the way I did it:
import abc
class MetaClass(object):
__metaclass__ = abc.ABCMeta
[...]
@classmethod
def __subclasshook__(cls, C):
if C.__abstractmethods__:
print C.__abstractmethods__
return False
else:
return True
If C
is an attempted class of MetaClass
, then C.__abstractmethods__
will be empty only if C
implements all the abstract methods.
See here for the details: https://www.python.org/dev/peps/pep-3119/#the-abc-module-an-abc-support-framework (it's under "Implementation" but a search for __abstractmethods__
should get you to the right paragraph)
Where has this worked for me:
I can create MetaClass
. I can then subclass BaseClass
and MetaClass
to create SubClass
which needs some extra functionality. But I have a need to cast an instance of BaseClass
down to SubClass
by changing the __cls__
attribute since I don't own BaseClass
but I get instances of it that I want to cast down.
However, if I improperly implement SubClass
, I can still cast down unless I use the above __subclasshook__
and just add a subclass check when I do the cast down process (which I should do anyway since I want to only try to cast a parent class down). If someone requests, I can provide a MWE for this.
ETA: Here is a MWE. I think what I was suggesting before was incorrect so the following appears to do what I intended.
The goal is to be able to convert a BaseClass
object to SubClass
and back. From SubClass
to BaseClass
is easy. But from BaseClass
to SubClass
not so much. The standard thing to do is update the __class__
attribute but that leaves an opening when SubClass
is not really a subclass or derives from a abstract meta class but is not properly implemented.
Below, the conversion is done in the convert
method that BaseMetaClass
implements. However, in that logic, I check two things. One, in order to convert to the subclass, I check if it is indeed a subclass. Second, I check the attribute __abstractmethods__
to see if it's empty. If it is, then it's also a properly implemented metaclass. Failure leads to a TypeError. Otherwise the object is converted.
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
Here's an alternative that works very well in practice too, without the cumbersome checking the entire dictionary on every class instance creation.
(py2 and py3 compatible)
Usage:
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')
...
You can also do obvious things like @implements(Stream, Folder, Bar), when they all require some of the same methods, which makes this more useful practically than inheritance.
Code:
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With