The Python Oracle

How to make an Python subclass uncallable

This video explains
How to make an Python subclass uncallable

--

Become part of the top 3% of the developers by applying to Toptal
https://topt.al/25cXVn

--

Music by Eric Matyas
https://www.soundimage.org
Track title: Unforgiving Himalayas Looping

--

Chapters
00:00 Question
00:40 Accepted answer (Score 14)
02:06 Answer 2 (Score 1)
03:12 Thank you

--

Full question
https://stackoverflow.com/questions/3022...

Accepted answer links:
[https://bugs.python.org/issue23990]: https://bugs.python.org/issue23990

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python

#avk47



ACCEPTED ANSWER

Score 14


You can't. As jonrsharpe points out, there's no way to make Child appear to not have the attribute, and that's what callable(Child()) relies on to produce its answer. Even making it a descriptor that raises AttributeError won't work, per this bug report: https://bugs.python.org/issue23990 . A python 2 example:

>>> class Parent(object):
...     def __call__(self): pass
... 
>>> class Child(Parent):
...     __call__ = property()
...
>>> c = Child()
>>> c()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: unreadable attribute
>>> c.__call__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: unreadable attribute
>>> callable(c)
True

This is because callable(...) doesn't act out the descriptor protocol. Actually calling the object, or accessing a __call__ attribute, involves retrieving the method even if it's behind a property, through the normal descriptor protocol. But callable(...) doesn't bother going that far, if it finds anything at all it is satisfied, and every subclass of Parent will have something for __call__ -- either an attribute in a subclass, or the definition from Parent.

So while you can make actually calling the instance fail with any exception you want, you can't ever make callable(some_instance_of_parent) return False.




ANSWER 2

Score 1


It's a bad idea to change the public interface of the class so radically from the parent to the base.

As pointed out elsewhere, you cant uninherit __call__. If you really need to mix in callable and non callable classes you should use another test (adding a class attribute) or simply making it safe to call the variants with no functionality.

To do the latter, You could override the __call__ to raise NotImplemented (or better, a custom exception of your own) if for some reason you wanted to mix a non-callable class in with the callable variants:

class Parent(object):
    def __call__(self):
        print "called"

class Child (Parent):
    def __call__(self):
        raise NotACallableInstanceException()

for child_or_parent in list_of_children_and_parents():
   try:  
      child_or_parent()
   except NotACallableInstanceException:
      pass

Or, just override call with pass:

class Parent(object):
    def __call__(self):
        print "called"

class Child (Parent):
    def __call__(self):
        pass

Which will still be callable but just be a nullop.