深入理解 Python 中的 __init_subclass__


起源

在研究graphql-python源碼的時候被__init_subclass_with_meta__這個類方法吸引,進而發現除元類外改變子類行為的另一種方式:__init_subclass__

類方法 __init_subclass__ 從 3.6 引入,作用是可以在不使用元類的情況下改變子類的行為。也就是說它是獨立於元類編程的,也能達到編輯其他類的一種手段。

示例1

# defining a SuperClass
class SuperClass:
  
     # defining __init_subclass__ method
    def __init_subclass__(cls, **kwargs):
        cls.default_name ="Inherited Class"
  
# defining a SubClass
class SubClass(SuperClass):
  
     # an attribute of SubClass
    default_name ="SubClass" 
    print(default_name)
  
subclass = SubClass()
print(subclass.default_name)

輸出

SubClass
Inherited Class

了解代碼

  • 在上面的示例中,有 2 個類(即超類和子類),子類繼承自超類。default_name是子類的一個屬性。
  • 屬性default_name的值由 SuperClass 使用__init_subclass__方法更改。
  • cls是指繼承的子類。提供給新類的關鍵字參數 (**kwargs) 將傳遞給父類的類__init_subclass__。
  • 為了與使用__init_subclass__的其他子類兼容,應該取出所需的關鍵字參數,並將其他子類傳遞給基類(Super Class)。

這個__init_subclass__ 子類與Decorator類非常相似。但是,如果類裝飾符只影響它們應用於的特定類,但是__init_subclass__只應用於定義該方法的類的未來子類。這意味着我們可以改變/定義從超類繼承的任何新類的行為。

示例2

# defining a SuperClass
class SuperClass:
	def __init_subclass__(cls, default_name, **kwargs):
		cls.default_name = default_name

# defining a subclass
class SubClass1(SuperClass, default_name ="SubClass1"):
	pass

# defining another subclass
class SubClass2(SuperClass, default_name ="SubClass2"):
	default_name = "InheritedClass"


# references for subclasses
subClass1 = SubClass1()
subClass2 = SubClass2()

print(subClass1.default_name)
print(subClass2.default_name)

輸出

SubClass1
SubClass2

創建類對象后的自定義步驟

盡管 __init_subclass__ 是獨立於元類編程的,但類都是由默認元類 type 創建的,那么在 type.__new__() 創建了類之后會有哪些步驟:

  • 首先,type.__new__ 收集類命名空間定義的 set_name() 方法的所有描述符;
  • 其次,這些 __set_name__ 的特定描述符在特定的情況下調用;
  • 最后,在父類上調用鈎子 __init_subclass__()

若類被裝飾器裝飾,那么就將上述生成的對象傳遞給類裝飾器。

總結

總的來說,__init_subclass__() 是鈎子函數,它解決了如何讓父類知道被繼承的問題。鈎子中能改變類的行為,而不必求助與元類或類裝飾器。鈎子用起來也更簡單且容易理解。
雖然本文還提到了 __set_name__ ,但它和 __init_subclass__ 並不相互關聯, __set_name__ 主要是解決了如何讓描述符知道其屬性的名稱。

__init_subclass__ 的目標是提供更簡單的定制方式,在簡單的場景下是元類的替代品。值得試一試。

下面是graphql-python對該方法的使用,值得學習

點擊查看代碼
from inspect import isclass

from .props import props


class SubclassWithMeta_Meta(type):
    _meta = None

    def __str__(cls):
        if cls._meta:
            return cls._meta.name
        return cls.__name__

    def __repr__(cls):
        return f"<{cls.__name__} meta={repr(cls._meta)}>"


class SubclassWithMeta(metaclass=SubclassWithMeta_Meta):
    """This class improves __init_subclass__ to receive automatically the options from meta"""

    def __init_subclass__(cls, **meta_options):
        """This method just terminates the super() chain"""
        _Meta = getattr(cls, "Meta", None)
        _meta_props = {}
        if _Meta:
            if isinstance(_Meta, dict):
                _meta_props = _Meta
            elif isclass(_Meta):
                _meta_props = props(_Meta)
            else:
                raise Exception(
                    f"Meta have to be either a class or a dict. Received {_Meta}"
                )
            delattr(cls, "Meta")
        options = dict(meta_options, **_meta_props)

        abstract = options.pop("abstract", False)
        if abstract:
            assert not options, (
                "Abstract types can only contain the abstract attribute. "
                f"Received: abstract, {', '.join(options)}"
            )
        else:
            super_class = super(cls, cls)
            if hasattr(super_class, "__init_subclass_with_meta__"):
                super_class.__init_subclass_with_meta__(**options)

    @classmethod
    def __init_subclass_with_meta__(cls, **meta_options):
        """This method just terminates the super() chain"""


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM