Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

python subclasscheck & subclasshook

The methods __subclasscheck__ and __subclasshook__ are used to determine if a class is regarded as subclass of another. However, their documentation is very limited, even in advanced Python books. How are they meant to be used and what is their difference (higher priority, side of relationship they refer to etc...)?

like image 820
blue_note Avatar asked Nov 23 '16 12:11

blue_note


People also ask

What is __ Subclasshook __ in Python?

It returns True when a class is found to be subclass of a ABC class, it returns False if it is not and returns NotImplemented if the subclass check is continued with the usual mechanism. This method is defined in the ABC class with some conditions.

What does super () __ Init__ do?

Understanding Python super() with __init__() methods It is known as a constructor in Object-Oriented terminology. This method when called, allows the class to initialize the attributes of the class. The super() function allows us to avoid using the base class name explicitly.

How do you check if a class is a subclass in Python?

Python issubclass() is built-in function used to check if a class is a subclass of another class or not. This function returns True if the given class is the subclass of given class else it returns False . Return Type: True if object is subclass of a class, or any element of the tuple, otherwise False.

What is __ new __ in Python?

The __new__() is a static method of the object class. It has the following signature: object.__new__(class, *args, **kwargs) Code language: Python (python) The first argument of the __new__ method is the class of the new object that you want to create.


1 Answers

Both methods can be used to customize the result of the issubclass() built-in function.

__subclasscheck__

class.__subclasscheck__(self, subclass)

Return true if subclass should be considered a (direct or indirect) subclass of class. If defined, called to implement issubclass(subclass, class).

Note that these methods are looked up on the type (metaclass) of a class. They cannot be defined as class methods in the actual class. This is consistent with the lookup of special methods that are called on instances, only in this case the instance is itself a class.

This method is the special method that is responsible for the customization of the issubclass check. Like the "Note" states it has to implemented on the metaclass!

class YouWontFindSubclasses(type):     def __subclasscheck__(cls, subclass):         print(cls, subclass)         return False  class MyCls(metaclass=YouWontFindSubclasses):     pass  class MySubCls(MyCls):     pass 

This implementation will return False even if you have genuine subclasses:

>>> issubclass(MySubCls, MyCls) <class '__main__.MyCls'> <class '__main__.MySubCls'> False 

There are actually more interesting uses for __subclasscheck__ implementations. For example:

class SpecialSubs(type):     def __subclasscheck__(cls, subclass):         required_attrs = getattr(cls, '_required_attrs', [])         for attr in required_attrs:             if any(attr in sub.__dict__ for sub in subclass.__mro__):                 continue             return False         return True  class MyCls(metaclass=SpecialSubs):     _required_attrs = ['__len__', '__iter__'] 

With this implementation any class that defines __len__ and __iter__ will return True in a issubclass check:

>>> issubclass(int, MyCls)  # ints have no __len__ or __iter__ False >>> issubclass(list, MyCls)  # but lists and dicts have True >>> issubclass(dict, MyCls) True 

In these examples I haven't called the superclasses __subclasscheck__ and thereby disabled the normal issubclass behavior (which is implemented by type.__subclasscheck__). But it's important to know that you can also choose to just extend the normal behavior instead of completely overriding it:

class Meta(type):     def __subclasscheck__(cls, subclass):         """Just modify the behavior for classes that aren't genuine subclasses."""         if super().__subclasscheck__(subclass):             return True         else:             # Not a normal subclass, implement some customization here. 

__subclasshook__

__subclasshook__(subclass)

(Must be defined as a class method.)

Check whether subclass is considered a subclass of this ABC. This means that you can customize the behavior of issubclass further without the need to call register() on every class you want to consider a subclass of the ABC. (This class method is called from the __subclasscheck__() method of the ABC.)

This method should return True, False or NotImplemented. If it returns True, the subclass is considered a subclass of this ABC. If it returns False, the subclass is not considered a subclass of this ABC, even if it would normally be one. If it returns NotImplemented, the subclass check is continued with the usual mechanism.

The important bit here is that it's defined as classmethod on the class and it's called by abc.ABC.__subclasscheck__. So you can only use it if you're dealing with classes that have an ABCMeta metaclass:

import abc  class MyClsABC(abc.ABC):     @classmethod     def __subclasshook__(cls, subclass):         print('in subclasshook')         return True  class MyClsNoABC(object):     @classmethod     def __subclasshook__(cls, subclass):         print('in subclasshook')         return True 

This will only go into the __subclasshook__ of the first:

>>> issubclass(int, MyClsABC) in subclasshook True  >>> issubclass(int, MyClsNoABC) False 

Note that subsequent issubclass calls don't go into the __subclasshook__ anymore because ABCMeta caches the result:

>>> issubclass(int, MyClsABC) True 

Note that you generally check if the first argument is the class itself. That's to avoid that subclasses "inherit" the __subclasshook__ instead of using normal subclass-determination.

For example (from the CPython collections.abc module):

from abc import ABCMeta, abstractmethod  def _check_methods(C, *methods):     mro = C.__mro__     for method in methods:         for B in mro:             if method in B.__dict__:                 if B.__dict__[method] is None:                     return NotImplemented                 break         else:             return NotImplemented     return True  class Hashable(metaclass=ABCMeta):      __slots__ = ()      @abstractmethod     def __hash__(self):         return 0      @classmethod     def __subclasshook__(cls, C):         if cls is Hashable:             return _check_methods(C, "__hash__")         return NotImplemented 

So if you check if something is a subclass of Hashable it will use the custom __subclasshook__ implementation that is guarded by the if cls is Hashable. However if you have an actual class implementing the Hashable interface you don't want it to inherit the __subclasshook__ mechanism but you want the normal subclass mechanism.

For example:

class MyHashable(Hashable):     def __hash__(self):         return 10  >>> issubclass(int, MyHashable) False 

Even though int implements __hash__ and the __subclasshook__ checks for an __hash__ implementation the result in this case is False. It still enters the __subclasshook__ of Hashable but it immediately returns NotImplemented which signals to ABCMeta that it should proceed using the normal implementation. If it didn't have the if cls is Hashable then issubclass(int, MyHashable) would return True!

When should you use __subclasscheck__ and when __subclasshook__?

It really depends. __subclasshook__ can be implemented on the class instead of the metaclass, but requires that you use ABCMeta (or a subclass of ABCMeta) as metaclass because the __subclasshook__ method is actually just a convention introduced by Pythons abc module.

You can always use __subclasscheck__ but it has to be implemented on the metaclass.

In practice you use __subclasshook__ if you implement interfaces (because these normally use abc) and want to customize the subclass mechanism. And you use __subclasscheck__ if you want to invent your own conventions (like abc did). So in 99.99% of the normal (not fun) cases you only need __subclasshook__.

like image 120
MSeifert Avatar answered Sep 22 '22 22:09

MSeifert