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.
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: class X(metaclass=ABCMeta): @abstractproperty def id(self): pass class Y(X): id = 1 class Z(X): def __init__(self, id): self._id = id @property def id(self): return self._id class Fail(X): def __init__(self, id): self.id = id 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: class X(metaclass=ABCMeta): @abstractproperty def id(self): pass @X.register class Y: def __init__(self, id): self.id = id 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]. 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. 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! -eric [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 ( http://mail.python.org/pipermail/python-list/2011-May/1272541.html). [2] A related issue opened just yesterday: http://bugs.python.org/issue12128 ################################################################ 1 - properties, with a getter and setter. At definition time. This seems like overkill: class X(object): __metaclass__ = ABCMeta @abstractproperty def name(self): pass class Y(X): def __init__(self, name): self._name = name super(Y, self).__init__() @property def name(self): return self._name @name.setter def name(self, val): self._name = val 2 - getter/setter functions. At definition time. This does not guarantee the name, only access around it: class X(object): __metaclass__ = ABCMeta @abstractmethod def get_name(self): pass @abstractmethod def set_name(self): pass class Y(X): def __init__(self, name): self.name = name super(Y, self).__init__() def get_name(self): return self.name def set_name(self, val): self.name = val 3 - descriptors directly. At definition time. Like the properties example: class Name(object): def __get__(self, obj, cls): if obj is None: return self return obj._name def __set__(self, obj, val): obj._name = val class X(object) __metaclass__ = ABCMeta @abstractproperty def name(self): pass class Y(X): name = Name() def __init__(self, name): self.name = name super(Y, self).__init__() 4 - getattribute. At run time. More overkill: class Enforcer(object): API = () def __getattribute__(self, attr): if attr in API and attr not in dir(self): raise TypeError("Expected attribute: %s" % attr) return object.__getattribute__(self, attr) def __setattr(self, attr, val): if attr in API and attr not in dir(self): raise TypeError("Expected attribute: %s" % attr) object.__setattribute__(self, attr, val) class X(Enforcer): API = ("name",) class Y(X): def __init__(self, name): self.name = name super(Y, self).__init__() 5 - metaclass. At instantiation time. Overkill again: class Enforcer(object): class SomeMeta(type): def enforces_API(f): def __init__(self, *args, **kwargs): f(self, *args, **kwargs) for name in self.API: if name not in dir(self): raise TypeError("Expected attribute: %s" % attr) __init__.__doc__ = f.__doc__ return __init__ def __new__(self, name, bases, namespace): cls = super(SomeMeta, self).__new__(self, name, bases, namespace) __init__ = namespace.get("__init__") if not __init__: def __init__(self, *args, **kwargs): super(cls, self).__init__(*args, **kwargs) namespace["__init__"] = self.enforces_API(__init__) return cls API = () class X(Enforcer): API = ("name",) class Y(X): def __init__(self, name): self.name = name super(Y, self).__init__() 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... class Enforcer(object): API = () def enforces_API(f): def __init__(self, *args, **kwargs): f(self, *args, **kwargs) for name in self.API: if name not in dir(self): raise TypeError("Expected attribute: %s" % attr) __init__.__doc__ = f.__doc__ return __init__ class X(Enforcer): API = ("name",) class Y(X): @X.enforces_API def __init__(self, name): self.name = name super(Y, self).__init__() 7 - class decorator. At definition time. Apply to each class that must enforce the API... class Enforcer(object): API = () def enforces_API(cls): def __init__decorator(f): def __init__(self, *args, **kwargs): f(self, *args, **kwargs) for name in self.API: if name not in dir(self): raise TypeError("Expected attribute: %s" % attr) __init__.__doc__ = f.__doc__ return __init__ __init__ = cls__dict__.get("__init__") if not __init__: def __init__(self, *args, **kwargs): super(cls, self).__init__(*args, **kwargs) cls.__init__ = self.enforces_API(__init__) @Enforcer.enforces_API class X(Enforcer): API = ("name",) @Enforcer.enforces_API class Y(X): def __init__(self, name): self.name = name super(Y, self).__init__()
-- http://mail.python.org/mailman/listinfo/python-list