了解 __init_subclass__
Understanding __init_subclass__
我终于升级了我的 python 版本,并且发现了添加的新功能。除此之外,我还在为新的 __init_subclass__
方法摸不着头脑。来自文档:
This method is called whenever the containing class is subclassed. cls
is then the new subclass. If defined as a normal instance method, this
method is implicitly converted to a class method.
所以我按照文档中的示例开始尝试使用它:
class Philosopher:
def __init_subclass__(cls, default_name, **kwargs):
super().__init_subclass__(**kwargs)
print(f"Called __init_subclass({cls}, {default_name})")
cls.default_name = default_name
class AustralianPhilosopher(Philosopher, default_name="Bruce"):
pass
class GermanPhilosopher(Philosopher, default_name="Nietzsche"):
default_name = "Hegel"
print("Set name to Hegel")
Bruce = AustralianPhilosopher()
Mistery = GermanPhilosopher()
print(Bruce.default_name)
print(Mistery.default_name)
产生这个输出:
Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce')
'Set name to Hegel'
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche')
'Bruce'
'Nietzsche'
我知道这个方法在子类定义之后被调用,但是我的问题特别是关于这个特性的用法。我也阅读了 PEP 487 文章,但对我帮助不大。这种方法有什么用?是否用于:
- 在创建时注册子类的超类?
- 强制子类在定义时设置字段?
此外,我是否需要了解 __set_name__
才能完全理解其用法?
__init_subclass__
和 __set_name__
是正交机制——它们彼此不相关,只是在同一个 PEP 中描述。两者都是以前需要全功能 metaclass 的功能。 PEP 487 解决了 两个 元classes 的最常见用途:
- 如何让父级知道它何时被子classed (
__init_subclass__
)
- 如何让描述符 class 知道它用于 (
__set_name__
) 的 属性 的名称
正如PEP 487所说:
While there are many possible ways to use a metaclass, the vast majority of use cases falls into just three categories: some initialization code running after class creation, the initialization of descriptors and keeping the order in which class attributes were defined.
The first two categories can easily be achieved by having simple hooks into the class creation:
- An
__init_subclass__
hook that initializes all subclasses of a given class.
- upon class creation, a
__set_name__
hook is called on all the attribute (descriptors) defined in the class, and
The third category is the topic of another PEP, PEP 520.
另请注意,虽然 __init_subclass__
是在 this class 的继承树中使用 metaclass 的替代品,[ 描述符 class 中的 =11=] 是对具有 [=26] 实例的 class 使用元 class 的替代=]作为属性的描述符.
PEP 487 着手采用两个常见的元类用例,并使它们更易于访问,而无需了解元类 的所有来龙去脉。 __init_subclass__
和 __set_name__
这两个新功能在其他方面是 独立的 ,它们不相互依赖。
__init_subclass__
只是一个钩子方法。你可以用它做任何你想做的事。它对于以某种方式注册 sub类、 和 都非常有用,可以在那些 sub类.
上设置默认属性值
我们最近用这个为不同的版本控制系统提供'adapters',例如:
class RepositoryType(Enum):
HG = auto()
GIT = auto()
SVN = auto()
PERFORCE = auto()
class Repository():
_registry = {t: {} for t in RepositoryType}
def __init_subclass__(cls, scm_type=None, name=None, **kwargs):
super().__init_subclass__(**kwargs)
if scm_type is not None:
cls._registry[scm_type][name] = cls
class MainHgRepository(Repository, scm_type=RepositoryType.HG, name='main'):
pass
class GenericGitRepository(Repository, scm_type=RepositoryType.GIT):
pass
这让我们可以轻松地为特定存储库定义处理程序 类,而不必诉诸于使用元类或装饰器。
正如 PEP 的标题所暗示的,__init_subclass__
的要点是为 classes 提供更简单的自定义形式。
它是一个钩子,可以让你修补 classes w/o 需要了解 metaclasses,跟踪 class 构建的各个方面或者担心 metaclass 冲突。正如 Nick Coghlan 在本 PEP 的早期阶段所说的 a message:
The main intended readability/maintainability benefit is from the
perspective of more clearly distinguishing the "customises subclass
initialisation" case from the "customises runtime behaviour of
subclasses" case.
A full custom metaclass doesn't provide any indication of the scope of
impact, while __init_subclass__
more clearly indicates that there's no
persistent effects on behaviour post-subclass creation.
Metaclasses 被认为是魔法是有原因的,你不知道它们在 class 被创建后会产生什么影响。另一方面,__init_subclass__
只是另一种 class 方法,它运行一次然后就完成了。 (see its documentation for exact functionality.)
PEP 487 的重点是简化(即消除使用)metaclasses 以用于一些常见用途。
__init_subclass__
负责 post-class 初始化,同时添加 __set_name__
(仅对描述符 classes 有意义)以简化初始化描述符。除此之外,它们没有任何关系。
提到的元classes(保持定义顺序)的第三种常见情况,was also simplified。这是通过使用命名空间的有序映射(在 Python 3.6 中是 dict
,但这是一个实现细节:-)
解决了 w/o 一个钩子
我想添加一些与元类和 __init_subclass__
相关的参考,可能会有帮助。
背景
__init_subclass__
被引入作为创建元类的替代方法。
这是核心开发人员之一 Brett Cannon 对 PEP 487 in a talk 的 2 分钟摘要。
推荐参考文献
您还可以使用它在 class 上执行 once-only 昂贵的初始化。
例如,我想用家庭的标准代字号 shorthand 替换以我的用户开头的路径。
/Users/myuser/.profile
-> ~/.profile
.
简单,我可以这样写:
from pathlib import Path
class Replacer:
def __init__(self):
self.home = str(Path("~").expanduser())
def replace(self, value):
if isinstance(value,str) and value.startswith(self.home):
value = value.replace(self.home,"~")
return value
replacer = Replacer()
print(replacer.replace("/Users/myuser/.profile"))
但是,对于任何 运行 主路径都是不变的,无需在每次创建 Replacer 时都计算它。
使用__init_subclass,我只能为class做一次。是的,我还可以在 模块初始化时间 :
将变量分配给 class
class Replacer:
home = str(Path("~").expanduser())
...
但可能有理由要推迟该计算,直到 class 真正第一次被使用。例如,在使用 Django 时,在某些情况下,当您导入 models.py
时,Django 可能尚未完全初始化。
class UselessAncestorNeededToHouseInitSubclass:
"do-nothing"
def __init_subclass__(cls, /, **kwargs):
print("__init_subclass__")
super().__init_subclass__(**kwargs)
cls.home = str(Path("~").expanduser())
class Replacer(UselessAncestorNeededToHouseInitSubclass):
"""__init_subclass__ wont work if defined here. It has to be on
an ancestor
"""
def replace(self, value):
if isinstance(value,str) and value.startswith(self.home):
value = value.replace(self.home,"~")
return value
for ix in range(0,10):
replacer = Replacer()
print(replacer.replace("/Users/myuser/.profile"))
输出:(注意 subclass_init 如何只被调用一次:
__init_subclass__
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
我终于升级了我的 python 版本,并且发现了添加的新功能。除此之外,我还在为新的 __init_subclass__
方法摸不着头脑。来自文档:
This method is called whenever the containing class is subclassed. cls is then the new subclass. If defined as a normal instance method, this method is implicitly converted to a class method.
所以我按照文档中的示例开始尝试使用它:
class Philosopher:
def __init_subclass__(cls, default_name, **kwargs):
super().__init_subclass__(**kwargs)
print(f"Called __init_subclass({cls}, {default_name})")
cls.default_name = default_name
class AustralianPhilosopher(Philosopher, default_name="Bruce"):
pass
class GermanPhilosopher(Philosopher, default_name="Nietzsche"):
default_name = "Hegel"
print("Set name to Hegel")
Bruce = AustralianPhilosopher()
Mistery = GermanPhilosopher()
print(Bruce.default_name)
print(Mistery.default_name)
产生这个输出:
Called __init_subclass(<class '__main__.AustralianPhilosopher'>, 'Bruce')
'Set name to Hegel'
Called __init_subclass(<class '__main__.GermanPhilosopher'>, 'Nietzsche')
'Bruce'
'Nietzsche'
我知道这个方法在子类定义之后被调用,但是我的问题特别是关于这个特性的用法。我也阅读了 PEP 487 文章,但对我帮助不大。这种方法有什么用?是否用于:
- 在创建时注册子类的超类?
- 强制子类在定义时设置字段?
此外,我是否需要了解 __set_name__
才能完全理解其用法?
__init_subclass__
和 __set_name__
是正交机制——它们彼此不相关,只是在同一个 PEP 中描述。两者都是以前需要全功能 metaclass 的功能。 PEP 487 解决了 两个 元classes 的最常见用途:
- 如何让父级知道它何时被子classed (
__init_subclass__
) - 如何让描述符 class 知道它用于 (
__set_name__
) 的 属性 的名称
正如PEP 487所说:
While there are many possible ways to use a metaclass, the vast majority of use cases falls into just three categories: some initialization code running after class creation, the initialization of descriptors and keeping the order in which class attributes were defined.
The first two categories can easily be achieved by having simple hooks into the class creation:
- An
__init_subclass__
hook that initializes all subclasses of a given class.- upon class creation, a
__set_name__
hook is called on all the attribute (descriptors) defined in the class, andThe third category is the topic of another PEP, PEP 520.
另请注意,虽然 __init_subclass__
是在 this class 的继承树中使用 metaclass 的替代品,[ 描述符 class 中的 =11=] 是对具有 [=26] 实例的 class 使用元 class 的替代=]作为属性的描述符.
PEP 487 着手采用两个常见的元类用例,并使它们更易于访问,而无需了解元类 的所有来龙去脉。 __init_subclass__
和 __set_name__
这两个新功能在其他方面是 独立的 ,它们不相互依赖。
__init_subclass__
只是一个钩子方法。你可以用它做任何你想做的事。它对于以某种方式注册 sub类、 和 都非常有用,可以在那些 sub类.
我们最近用这个为不同的版本控制系统提供'adapters',例如:
class RepositoryType(Enum):
HG = auto()
GIT = auto()
SVN = auto()
PERFORCE = auto()
class Repository():
_registry = {t: {} for t in RepositoryType}
def __init_subclass__(cls, scm_type=None, name=None, **kwargs):
super().__init_subclass__(**kwargs)
if scm_type is not None:
cls._registry[scm_type][name] = cls
class MainHgRepository(Repository, scm_type=RepositoryType.HG, name='main'):
pass
class GenericGitRepository(Repository, scm_type=RepositoryType.GIT):
pass
这让我们可以轻松地为特定存储库定义处理程序 类,而不必诉诸于使用元类或装饰器。
正如 PEP 的标题所暗示的,__init_subclass__
的要点是为 classes 提供更简单的自定义形式。
它是一个钩子,可以让你修补 classes w/o 需要了解 metaclasses,跟踪 class 构建的各个方面或者担心 metaclass 冲突。正如 Nick Coghlan 在本 PEP 的早期阶段所说的 a message:
The main intended readability/maintainability benefit is from the perspective of more clearly distinguishing the "customises subclass initialisation" case from the "customises runtime behaviour of subclasses" case.
A full custom metaclass doesn't provide any indication of the scope of impact, while
__init_subclass__
more clearly indicates that there's no persistent effects on behaviour post-subclass creation.
Metaclasses 被认为是魔法是有原因的,你不知道它们在 class 被创建后会产生什么影响。另一方面,__init_subclass__
只是另一种 class 方法,它运行一次然后就完成了。 (see its documentation for exact functionality.)
PEP 487 的重点是简化(即消除使用)metaclasses 以用于一些常见用途。
__init_subclass__
负责 post-class 初始化,同时添加 __set_name__
(仅对描述符 classes 有意义)以简化初始化描述符。除此之外,它们没有任何关系。
提到的元classes(保持定义顺序)的第三种常见情况,was also simplified。这是通过使用命名空间的有序映射(在 Python 3.6 中是 dict
,但这是一个实现细节:-)
我想添加一些与元类和 __init_subclass__
相关的参考,可能会有帮助。
背景
__init_subclass__
被引入作为创建元类的替代方法。
这是核心开发人员之一 Brett Cannon 对 PEP 487 in a talk 的 2 分钟摘要。
推荐参考文献
您还可以使用它在 class 上执行 once-only 昂贵的初始化。
例如,我想用家庭的标准代字号 shorthand 替换以我的用户开头的路径。
/Users/myuser/.profile
-> ~/.profile
.
简单,我可以这样写:
from pathlib import Path
class Replacer:
def __init__(self):
self.home = str(Path("~").expanduser())
def replace(self, value):
if isinstance(value,str) and value.startswith(self.home):
value = value.replace(self.home,"~")
return value
replacer = Replacer()
print(replacer.replace("/Users/myuser/.profile"))
但是,对于任何 运行 主路径都是不变的,无需在每次创建 Replacer 时都计算它。
使用__init_subclass,我只能为class做一次。是的,我还可以在 模块初始化时间 :
将变量分配给 classclass Replacer:
home = str(Path("~").expanduser())
...
但可能有理由要推迟该计算,直到 class 真正第一次被使用。例如,在使用 Django 时,在某些情况下,当您导入 models.py
时,Django 可能尚未完全初始化。
class UselessAncestorNeededToHouseInitSubclass:
"do-nothing"
def __init_subclass__(cls, /, **kwargs):
print("__init_subclass__")
super().__init_subclass__(**kwargs)
cls.home = str(Path("~").expanduser())
class Replacer(UselessAncestorNeededToHouseInitSubclass):
"""__init_subclass__ wont work if defined here. It has to be on
an ancestor
"""
def replace(self, value):
if isinstance(value,str) and value.startswith(self.home):
value = value.replace(self.home,"~")
return value
for ix in range(0,10):
replacer = Replacer()
print(replacer.replace("/Users/myuser/.profile"))
输出:(注意 subclass_init 如何只被调用一次:
__init_subclass__
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile
~/.profile