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

标签 python python-3.x inheritance super metaclass

我有一个带有抽象方法的中央元类,其中一些我不仅想强制它们出现在子类中,而且还想强制子类显式调用 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/

相关文章:

python - 为什么在更改超时变量后,Amazon RDS 上的 MySQL 不断使我的连接超时?

python - 为什么裸 Python 装饰器(没有@)不会产生编译器错误?

c# - 重载、泛型和类型约束 : method resolution

python - 根据各自的行分别对 3D 数组中的 2D 数组列进行排序

python - 在 FormEncode 验证失败后使用查询字符串参数重新呈现 Pylons 表单

未在嵌入式安装中设置 Python 路径

php - 如何在 Doctrine2 中为类表继承指定外键列?

c# - 方法属性在 C# 中是继承的吗?

Python 速记运算符?

python - 在 sphinx 中自定义方法签名