gpt4 book ai didi

python - 我怎样才能要求从元类的抽象方法中调用 super() ?

转载 作者:行者123 更新时间:2023-12-05 02:42:20 26 4
gpt4 key购买 nike

我有一个带有抽象方法的中央元类,其中一些我不仅想强制它们出现在子类中,而且还想强制子类显式调用 super() 来自其中一些抽象方法。

以下面的简化示例为例:

import requests
import abc

class ParentCls(metaclass=abc.ABCMeta):
# ...
@abc.abstractmethod
def login(self, username, password):
"""Override with logic for logging into the service"""

@abc.abstractmethod
def download_file(self, resp: requests.Response) -> None:
# repeated logic goes here
# basically, save the streaming download either to the cloud
# or locally depending on other class variables
pass

class ChildCls(ParentCls):
def login(self, username, password):
# perform the login steps, no need to call `super()` in this method
pass

def download_file(self, link):
# download request will be different per child class
dl_resp = requests.get(link, stream=True)
super().download_file(dl_resp) # how can I make an explicit super() call required?

那么,我的问题是:

  1. 是否可以要求一些(不是全部)抽象方法从子类显式调用super()
  2. 如果是,那看起来如何?如果不是,怎么会?

最佳答案

包含对 super() 调用的方法和一个不包含调用的方法的一个区别是第一个方法将有一个 __class__ 非局部变量。这可以通过查看代码对象来检查:

In [18]: class A: 
...: def a(self):
...: super().a()
...: def b(self):
...: pass
...:

In [19]: A.a.__code__.co_freevars
Out[19]: ('__class__',)

In [20]: A.b.__code__.co_freevars
Out[20]: ()

这是在调用元类之前由运行时注入(inject)的,作为 type.__new__ 能够在创建类时插入对类本身的引用的一种方式。它们是由对 super 的无参数调用自动使用的。

问题是这不能确保 super() 的存在,或者它确实在运行,也不能确保它用于调用所需的 super 方法(可以用它来检查一个属性,或调用父类(super class)中的另一个方法,尽管这是不寻常的)。

但是,仅此一项检查就可以捕获大多数情况,并防止意外解雇。

如果你真的需要严格检查超方法调用,那可以在运行时完成,当实际方法被调用时,通过一个更复杂的机制:

在伪代码中:

  • 使用元类用装饰器包装被调用方法的最叶层重写(我们称之为“客户端装饰器”)
  • 装饰必须使用另一个装饰器(比如“checker-decorator”)运行的基本方法
  • “client-decorator”的功能是翻转一个应该是由“检查器装饰器”取消翻转。
  • 在方法退出时,“客户端装饰器”可以知道是否调用了基本方法,如果没有调用则引发错误。

尝试使用传统的装饰器只封装子类中的最叶方法是很复杂的(如果您在标记为元类的问题上寻找我的答案,我至少发布了一次示例代码,并且有很多极端情况)。但是,如果从实例中检索方法时,将方法动态地包装在类 __getattribute__ 上会更容易。

在此示例中,必须协调“client-decorator”和“checker-decorator”,并访问“base_method_had_run”标志。这样做的一种机制是 checker-decorator 将“client-decorator”附加到装饰方法,然后元类 __new__ 方法将建立一个注册表每个类的方法名称和客户端装饰器。然后,此注册表可用于 __getattribute__

的动态包装

写完这些段落后,我已经足够清楚了,我可以举一个工作示例:


from functools import wraps
import abc

def force_super_call(method):
# If the instance is ever used in parallel code, like in multiple threads
# or async-tasks, the flag bellow should use a contextvars.ContectVar
# (or threading.local)
base_method_called = False
@wraps(method)
def checker_wrapper(*args, **kwargs):
nonlocal base_method_called
try:
result = method(*args, **kwargs)
finally:
base_method_called = True
return result

# This will be used dinamically on each method call:
def client_decorator(leaf_method):
@wraps(leaf_method)
def client_wrapper(*args, **kwargs):
nonlocal base_method_called
base_method_called = False
try:
result = leaf_method(*args, **kwargs)
finally:
if not base_method_called:
raise RuntimeError(f"Overriden method '{method.__name__}' did not cause the base method to be called")

base_method_called = False

return result
return client_wrapper

# attach the client-wrapper to the decorated base method, so that the mechanism
# in the metaclass can retrieve it:
checker_wrapper.client_decorator = client_decorator

# ordinary decorator return
return checker_wrapper


def forcecall__getattribute__(self, name):

cls = type(self)

method = object.__getattribute__(self, name)
registry = type(cls).forcecall_registry

for superclass in cls.__mro__[1:]:
if superclass in registry and name in registry[superclass]:
# Apply the decorator with ordinary, function-call syntax:
method = registry[superclass][name](method)
break
return method


class ForceBaseCallMeta(abc.ABCMeta):
forcecall_registry = {}

def __new__(mcls, name, bases, namespace, **kwargs):
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
mcls.forcecall_registry[cls] = {}
for name, method in cls.__dict__.items():
if hasattr(method, "client_decorator"):
mcls.forcecall_registry[cls][name] = method.client_decorator
cls.__getattribute__ = forcecall__getattribute__
return cls

这有效:


In [3]: class A(metaclass=ForceBaseCallMeta):
...: @abc.abstractmethod
...: @force_super_call
...: def a(self):
...: pass
...:
...: @abc.abstractmethod
...: @force_super_call
...: def b(self):
...: pass
...:

In [4]: class B(A):
...: def a(self):
...: return super().a()
...: def b(self):
...: return None
...:

In [5]: b = B()

In [6]: b.a()

In [7]: b.b()
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
...
RuntimeError: Overriden method 'b' did not cause the base method to be called

关于python - 我怎样才能要求从元类的抽象方法中调用 super() ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67661091/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com