Python 3中通过__getattr__实现动态属性的类型提示

12
为了封装模块中的所有函数调用,并通过包装类的__getattr__方法访问它,我尝试使用typing库,但我无法弄清楚如何正确操作。
import interface

"""
>>> print(interface.__all__)
['execute_foo_operation', ...]
"""

class InterfaceWrapper(object):
    def __init__(self, job_queue):
        self.job_queue = job_queue
        self.callbacks = []

    def __getattr__(self, name):
        func = getattr(interface, name)
        return functools.partial(self._wrapper, func)

    def _wrapper(self, func, *args, **kwargs):
        job = func(*args, **kwargs)
        self.job_queue.push(job)
        for callback in self.callbacks:
            callback(job)
        return job

    def register_callback(self, callback):
        self.callbacks.append(callback)


class Operator(object):
    def __init__(self, job_queue):
        self.interface = InterfaceWrapper(job_queue)

    def after_queuing(self):
        # do something

    def execute_foo_operation(self):
        self.interface.register_callback(self.after_queuing)
        self.interface.execute_foo_operation()  # unresolved attribute

有人能指导我如何使我的代码正常运行吗?


2
你能详细说明一下你想进行类型注释的方法或表达式吗?如果是 InterfaceWrapper.__getattr__,我认为很难做到这一点,因为它需要知道 func 的参数类型,而我猜每个函数的参数类型都是不同的。 - Zecong Hu
2个回答

0
通常情况下,__getattr__ 可以返回任何内容。因此,您可以使用 Typing.Any。
From Typing import Any

def __getattr__(self, name: str) -> Any:
    ...

但是看起来你的__getattr__实现是返回一个可调用对象

From Typing import Callable

def __getattr__(self, name: str) -> Callable:
    ...

通常情况下,避免使用 Any 是更好的选择。如果你将返回值注释为 object,那么使用这个返回值的代码将被强制进行类型检查(或者在最坏的情况下进行转换),并且随后的代码将会更加类型安全。如果你使用 Any,那么使用该返回值的代码就不再安全,尽管你可能不会收到任何错误提示。 - theberzi

0
如果我理解正确,您的要求实际上是要求静态类型检查器推断由`__getattr__`返回的动态属性的引用,对于大多数类型检查器来说,这可能是相当具有挑战性的。实际上,对于PyCharm(>=2020.3.5(203.7717.81)),如果在class中定义了`__getattr__`,则未解析属性的检查将被禁用。
有一个解决方法,但需要您不使用`__getattr__`来访问属性,而是通过`typing.Literal`手动静态定义所有属性名称,并在下面的自定义定义的`get_attr`方法中使用这些Literal来注释`name`。
import functools
from typing import Literal
supported = Literal[Literal['a'], Literal['b']]


class InterfaceWrapper(object):
    def __init__(self, job_queue):
        self.job_queue = job_queue
        self.callbacks = []

    def get_attr(self, name: supported):
        func = getattr(interface, name)
        return functools.partial(self._wrapper, func)

    def _wrapper(self, func, *args, **kwargs):
        job = func(*args, **kwargs)
        self.job_queue.push(job)
        for callback in self.callbacks:
            callback(job)
        return job

    def register_callback(self, callback):
        self.callbacks.append(callback)

class Operator(object):
    interface: InterfaceWrapper

    def __init__(self, job_queue):
        self.interface: InterfaceWrapper = InterfaceWrapper(job_queue)

    def after_queuing(self):
        # do something
        ...

    def execute_foo_operation(self):
        # ... do something
        self.interface.get_attr('a')()

在这里,如果你调用 self.interface.get_attr('c'),它会报告期望的是 'a' 和 'b',但实际得到的是 'c'。
如果你只想通过 __getattr__ (interface.attr_name) 访问方法或属性,那么你至少需要在 InterfaceWrapper 中静态定义 interface 的属性,例如,定义同名的属性,指向接口对象中对应的字段。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接