This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: @abstractmethod does not enforce method signatures
Type: enhancement Stage: resolved
Components: Library (Lib) Versions: Python 3.5
process
Status: closed Resolution: later
Dependencies: Superseder:
Assigned To: Nosy List: Claudiu.Popa, eric.snow, r.david.murray, terry.reedy, the.mulhern
Priority: normal Keywords:

Created on 2014-03-12 12:49 by the.mulhern, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (8)
msg213256 - (view) Author: the mulhern (the.mulhern) Date: 2014-03-12 12:49
Hi!

Here is a simple class hierarchy:

>>> import abc
>>> class META(object):
...     __metaclass__ = abc.ABCMeta
...     @abc.abstractmethod
...     def _junk(self):
...             pass
... 
>>> class Sub(META):
...     def _junk(self, other):
...             pass
... 
>>> class Subber(META):
...     def _junk(self):
...             pass
... 
>>> class SuperSubber(META):
...     pass
... 

In 2.7.5 I can instantiate an object of Sub or Subber, although Sub does not really override META's abstract method.
I think it would be better if I could not instantiate Sub, because the signature of the abstract method in META
is different from the signature of the overriding method in Sub.
I can not instantiate SuperSubber, I get a TypeError. That seems correct.

In 3.3.2 I can instantiate all three, Sub, Subber, and SuperSubber.
I would prefer to only be able to instantiate Subber, since it is the only class that truly overrides the abstract method
_junk in META.

- mulhern
msg213600 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-03-14 23:10
Unless the behavior contradicts the docs, it is not a bug. If the looser behavior in 3.x is intentional, a request to reverse direction will likely be rejected. Like many enhancement requests, I think this would better be discussed on python-ideas first, along with asking if the 2.x to 3.x change is intentional.
msg213958 - (view) Author: the mulhern (the.mulhern) Date: 2014-03-18 13:44
I feel that I worded this in a way that makes it look like I'm asking for an enhancement, not reporting a bug, so I'll try again.

The documentation for 2.7.6 and 3.4.0 says:

Using this decorator requires that the class’s metaclass is ABCMeta or is derived from it. A class that has a metaclass derived from ABCMeta cannot be instantiated unless all of its abstract methods and properties are overridden. The abstract methods can be called using any of the normal ‘super’ call mechanisms.

The second sentence is a little obscure, but what it must mean is that a class that has a metaclass derived from ABCMeta or a class that extends such a class cannot be instantiated unless all of its abstract method and properties are overridden.

Now, in this example, both Sub and SuperSubber have an abstract method _junk(self) and neither of them overrides this method. Therefore, neither should be instantiated.

But they can both be instantiated in 3.3 and one can be instantiated in 2.7.

So, the behavior does seem to me to disagree with the documentation.

In fact, in Python 3.3.2 I can instantiate the META class in the example, which seems really wrong. I can not instantiate the META class in 2.7.6.
msg213960 - (view) Author: PCManticore (Claudiu.Popa) * (Python triager) Date: 2014-03-18 13:54
Hello. In 3.3 you can instantiate META class, because it does not properly say that it wants abc.ABCMeta as a metaclass. For this, you have to write your class as such:

class META(metaclass=abc.ABCMeta):
    @abc.abstractmethod
    def _junk(self):
       ...
msg213962 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-03-18 14:05
The fact that you say the method is "_junk(self)" and say the other classes don't override it makes me think you are thinking that methods with the same name are different from a subclasses perspective if they have different signatures.  In Python this is not true.  You cannot have methods with the same name and different signatures in the same class, so in Python "override" means "has a method with the same name".  Therefore the implementation is currently behaving in accordance with the documentation.

Checking for compatible signatures would be a new feature by our rules regardless of whether it is considered an API bug or not.  This is because it could cause existing working programs to break, and so can't be changed in a maintenance release.

I think I agree with Terry that this should be discussed on python-ideas first, although insofar as ABCs are useful, it seems like a reasonable feature to me.  And probably not hard to do now that we have signature objects.
msg213965 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2014-03-18 14:14
Both abstractnethod and abstractproperty work by setting __isabstractmethod__ to True on the decorated function.  Then type.__new__ looks for any attributes of the current class (including inherited ones) that have __isabstractmethod__ set to True.  The signature of abstract methods is not checked/enforced.  In fact the overriding attribute doesn't even have to be a method at all.  PEP 3119 doesn't say much about this.  I expect it is the way the way it is for performance reasons.

As to SuperSubber working in Python 3, don't forget that metaclasses are declared in the class signature:

class SuperSubber(META):
    pass

Doing things like enforcing signatures is doable, but would require some other mechanism.  If you want to pursue this further I recommend you take it to the python-ideas mailing list.
msg213970 - (view) Author: Eric Snow (eric.snow) * (Python committer) Date: 2014-03-18 14:28
Oops, typos.

> Both abstractmethod and abstractproperty work by setting
__isabstractmethod__ to True on the decorated function.  Then type.__new__

That should be type.__call__ or object.__new__, I don't remember which.

> looks for any attributes of the current class (including inherited ones)
that have __isabstractmethod__ set to True...
>
> As to SuperSubber working in Python 3, don't forget that metaclasses are
declared in the class signature:
>
> class SuperSubber(META):
>     pass

That should be:

class META(metaclass=abc.ABCMeta):
msg213974 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2014-03-18 14:39
Or, in 3.4, class META(abc.ABC).

OK, since Eric agrees that this is python-ideas material, we'll close this issue for now.  If you get consensus for it on python-ideas, the issue can be reopened (or a new one started, whichever turns out to be appropriate).
History
Date User Action Args
2022-04-11 14:57:59adminsetgithub: 65096
2014-03-18 14:39:10r.david.murraysetstatus: open -> closed
type: behavior -> enhancement
messages: + msg213974

resolution: later
stage: needs patch -> resolved
2014-03-18 14:28:32eric.snowsetmessages: + msg213970
2014-03-18 14:14:01eric.snowsetnosy: + eric.snow
messages: + msg213965
2014-03-18 14:05:25r.david.murraysetnosy: + r.david.murray
messages: + msg213962
2014-03-18 13:54:05Claudiu.Popasetnosy: + Claudiu.Popa
messages: + msg213960
2014-03-18 13:44:43the.mulhernsettype: enhancement -> behavior
messages: + msg213958
2014-03-14 23:10:53terry.reedysetversions: - Python 3.1, Python 2.7, Python 3.2, Python 3.3, Python 3.4
nosy: + terry.reedy

messages: + msg213600

type: behavior -> enhancement
stage: needs patch
2014-03-12 12:49:16the.mulherncreate