<div>The current ABC implementation in Python implies that the class of a conformant instance complies with the ABC. The implication does not carry down to the compliance of the instance itself.</div><div><br></div><div>This means that if you inherit from an ABC that has an abstract property, your subclass must have a matching name to that property, or you will get a TypeError. (Same goes for abstract methods--a matching name must be bound, even if not to a function). For example:</div>
<div><br></div><div>class X(metaclass=ABCMeta):</div><div> @abstractproperty</div><div> def id(self): pass</div><div><br></div><div>class Y(X):</div><div> id = 1</div><div><br></div><div>class Z(X):</div><div> def __init__(self, id):</div>
<div> self._id = id</div><div> @property</div><div> def id(self):</div><div> return self._id</div><div> </div><div>class Fail(X):</div><div> def __init__(self, id):</div><div> <a href="http://self.id">self.id</a> = id</div>
<div><br></div><div>So classes Y and Z will work fine, but class Fail will raise a TypeError when you instantiate [1] Fail, even though it "implemented" id in the instance __init__ [2]. I looked at this all yesterday and did not see a great way to approach this. The best I could come up with was the following:</div>
<div><br></div><div><div>class X(metaclass=ABCMeta):</div><div> @abstractproperty</div><div> def id(self): pass</div><div><br></div></div><div>@X.register</div><div><div>class Y:</div><div> def __init__(self, id):</div>
<div> <a href="http://self.id">self.id</a> = id</div></div><div><br></div><div>So this is a promise that Y comforms to X without any of the automatic validation. However, you don't get _any_ validation. You also lose any otherwise inherited features, so it is more like an interface than an abstract class. I am not so sure about that above solution because it seems like such a loose constraint. I discussed the validation problem in another email [1].</div>
<div><br></div><div>I am not sure if there is a way to bake into Python an effective check that an instance (not the class of the instance) is compliant with an ABC. However, it would be cool if there was. The current checking mechanism for ABCs happens in object.__new__ at instantiation time. At that point it has no knowledge of what names your instance will have, other than those that come from the class.</div>
<div><br></div><div>I spent a while looking at this whole problem yesterday and came up with a bunch of approaches for that Fail situation above. However, they mostly seem like overkill to me. I have included them below. If anyone has ideas on how to approach the problem of using an ABC but satisfying it with instance names, I would love to hear it. Thanks!</div>
<div><br></div><div>-eric</div><div><br></div><div><br></div><div>[1] In this case it would be nice to know at definition time that the class is missing the abstract "method". You don't want an exception at definition time for every subclass, though, since some you may want to keep abstract. I wrote up a decorator that allows you to validate at definition time in an email yesterday (<a href="http://mail.python.org/pipermail/python-list/2011-May/1272541.html">http://mail.python.org/pipermail/python-list/2011-May/1272541.html</a>).</div>
<div>[2] A related issue opened just yesterday: <a href="http://bugs.python.org/issue12128">http://bugs.python.org/issue12128</a></div><div><br></div><div><br></div><div><br></div><div><div><div>################################################################</div>
</div><div><br></div><div>1 - properties, with a getter and setter. At definition time. This seems like overkill:</div><div><br></div><div>class X(object):</div><div><div> __metaclass__ = ABCMeta</div><div> @abstractproperty</div>
<div> def name(self): pass</div></div><div><br></div><div>class Y(X):</div><div> def __init__(self, name):</div><div> self._name = name</div><div> super(Y, self).__init__()</div><div> @property</div>
<div> def name(self):</div><div> return self._name</div><div> @name.setter</div><div> def name(self, val):</div><div> self._name = val</div><div><br></div><div>2 - getter/setter functions. At definition time. This does not guarantee the name, only access around it:</div>
<div><br></div><div>class X(object):</div><div><div> __metaclass__ = ABCMeta</div><div><div><div> @abstractmethod</div><div> def get_name(self): pass</div></div></div></div><div><div><div> @abstractmethod</div>
<div> def set_name(self): pass</div></div></div><div><br></div><div>class Y(X):</div><div> def __init__(self, name):</div><div> <a href="http://self.name/" target="_blank">self.name</a> = name</div><div> super(Y, self).__init__()</div>
<div> def get_name(self):</div><div> return <a href="http://self.name/" target="_blank">self.name</a></div><div> def set_name(self, val):</div><div> <a href="http://self.name/" target="_blank">self.name</a> = val</div>
<div><br></div><div>3 - descriptors directly. At definition time. Like the properties example:</div><div><br></div><div>class Name(object):</div><div> def __get__(self, obj, cls):</div><div> if obj is None:</div>
<div> return self</div><div> return obj._name</div><div> def __set__(self, obj, val):</div><div> obj._name = val</div><div><br></div><div>class X(object)</div><div><div><div> __metaclass__ = ABCMeta</div>
<div> @abstractproperty</div><div> def name(self): pass</div></div></div><div><br></div><div>class Y(X):</div><div> name = Name()</div><div><div> def __init__(self, name):</div><div> <a href="http://self.name/" target="_blank">self.name</a> = name</div>
</div><div><div> super(Y, self).__init__()</div></div><div><br></div><div>4 - getattribute. At run time. More overkill:</div><div><br></div><div>class Enforcer(object):</div><div> API = ()</div><div><div> def __getattribute__(self, attr):</div>
<div> if attr in API and attr not in dir(self):</div><div> raise TypeError("Expected attribute: %s" % attr)</div><div> return object.__getattribute__(self, attr)</div><div> def __setattr(self, attr, val): </div>
</div><div><div> if attr in API and attr not in dir(self):</div><div> raise TypeError("Expected attribute: %s" % attr)</div><div> object.__setattribute__(self, attr, val)</div></div><div>
<br></div><div>class X(Enforcer):</div><div> API = ("name",)</div><div><br></div><div>class Y(X):</div><div> def __init__(self, name):</div><div> <a href="http://self.name/" target="_blank">self.name</a> = name</div>
<div><div> super(Y, self).__init__()</div></div><div><br></div><div>5 - metaclass. At instantiation time. Overkill again:</div><div><br></div><div>class Enforcer(object):</div><div> class SomeMeta(type):</div>
<div> def enforces_API(f):</div><div> def __init__(self, *args, **kwargs):</div><div> f(self, *args, **kwargs)</div><div> for name in self.API:</div><div> if name not in dir(self):</div>
<div> raise TypeError("Expected attribute: %s" % attr)</div><div> __init__.__doc__ = f.__doc__</div><div> return __init__</div><div><br></div><div> def __new__(self, name, bases, namespace):</div>
<div> cls = super(SomeMeta, self).__new__(self, name, bases, namespace)</div><div> __init__ = namespace.get("__init__")</div><div> if not __init__:</div><div> def __init__(self, *args, **kwargs):</div>
<div> super(cls, self).__init__(*args, **kwargs)</div><div> namespace["__init__"] = self.enforces_API(__init__)</div><div> return cls</div><div> API = ()</div><div><br>
</div><div>class X(Enforcer):</div><div> API = ("name",)</div><div><br></div><div>class Y(X):</div><div><div> def __init__(self, name):</div><div> <a href="http://self.name/" target="_blank">self.name</a> = name</div>
</div><div> super(Y, self).__init__()</div><div><br></div><div>6 - decorator. At instantiation time. Apply to the __init__ of each class that must enforce the API or to the base __init__ and call super after the assignments...</div>
<div><br></div><div>class Enforcer(object):</div><div> API = ()</div><div><div> def enforces_API(f):</div><div> def __init__(self, *args, **kwargs):</div></div><div> f(self, *args, **kwargs)</div><div>
for name in self.API:</div><div> if name not in dir(self):</div><div> raise TypeError("Expected attribute: %s" % attr)</div><div> __init__.__doc__ = f.__doc__</div>
<div> return __init__</div><div> </div><div>class X(Enforcer):</div><div> API = ("name",)</div><div><br></div><div>class Y(X):</div><div><div> @X.enforces_API</div><div> def __init__(self, name):</div>
<div> <a href="http://self.name/" target="_blank">self.name</a> = name</div></div><div><div> super(Y, self).__init__()</div></div><div><br></div><div>7 - class decorator. At definition time. Apply to each class that must enforce the API...</div>
<div><br></div><div>class Enforcer(object):</div><div> API = ()</div><div><div> def enforces_API(cls):</div><div> def __init__decorator(f):</div><div> def __init__(self, *args, **kwargs):</div><div>
f(self, *args, **kwargs)</div><div> for name in self.API:</div><div> if name not in dir(self):</div><div> raise TypeError("Expected attribute: %s" % attr)</div>
<div> __init__.__doc__ = f.__doc__</div><div> return __init__</div><div><br></div><div> __init__ = cls__dict__.get("__init__")</div><div> if not __init__:</div><div> def __init__(self, *args, **kwargs):</div>
<div> super(cls, self).__init__(*args, **kwargs)</div><div> cls.__init__ = self.enforces_API(__init__)</div><div> </div></div><div>@Enforcer.enforces_API</div><div>class X(Enforcer):</div>
<div> API = ("name",)</div><div><br></div><div>@Enforcer.enforces_API</div><div>class Y(X):</div><div><div> def __init__(self, name):</div><div> <a href="http://self.name/" target="_blank">self.name</a> = name</div>
</div><div><div> super(Y, self).__init__()</div></div><div><br></div></div>