The Python Oracle

How to make an Python subclass uncallable

--------------------------------------------------
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
and get $2,000 discount on your first invoice
--------------------------------------------------

Take control of your privacy with Proton's trusted, Swiss-based, secure services.
Choose what you need and safeguard your digital life:
Mail: https://go.getproton.me/SH1CU
VPN: https://go.getproton.me/SH1DI
Password Manager: https://go.getproton.me/SH1DJ
Drive: https://go.getproton.me/SH1CT


Music by Eric Matyas
https://www.soundimage.org
Track title: Book End

--

Chapters
00:00 How To Make An Python Subclass Uncallable
00:22 Accepted Answer Score 14
01:34 Answer 2 Score 1
02:27 Thank you

--

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

--

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.