首页 > 编程问答 >缩写数据类装饰器而不丢失 IntelliSense

缩写数据类装饰器而不丢失 IntelliSense

时间:2024-07-28 06:32:56浏览次数:5  
标签:python decorator intellisense python-dataclasses pylance

场景

假设我想为具有特定参数的 dataclasses.dataclass 装饰器创建别名。例如:

# Instead of repeating this decorator all the time:
@dataclasses.dataclass(frozen=True, kw_only=True)
class Entity:
    ...

# I just write something like this:
@struct
class Entity:
    ...

我使用的静态分析器是 Pylance ,在 Visual Studio Code 中。

我使用的是 Python 3.11。

尝试 1:直接赋值(运行时 ✅、静态分析 ❌ )

我的第一直觉是利用函数是一等公民这一事实,并简单地将创建的装饰器函数分配给自定义名称。这在运行时有效,但 Pylance 不再将 Entity 识别为数据类,从静态分析错误中可以明显看出:

struct = dataclasses.dataclass(frozen=True, kw_only=True)

@struct
class Entity:
    name: str
    value: int

# STATIC ANALYZER:
# Expected no arguments to "Entity" constructor Pylance(reportCallIssue)
valid_entity = Entity(name="entity", value=42)

# RUNTIME:
# Entity(name='entity', value=42)
print(valid_entity)

尝试 2:包装(运行时❌,静态分析❌)

然后我想也许有一些信息如果我只是分配给另一个名称,就会以某种方式丢失(尽管我不明白为什么会出现这种情况),所以我希望用 functools 来包装它。然而,当我应用 @struct 时,这在静态分析中仍然具有相同的行为,甚至导致运行时错误

import dataclasses
import functools

def struct(cls):
    decorator = dataclasses.dataclass(frozen=True, kw_only=True)
    decorated_cls = decorator(cls)
    functools.update_wrapper(decorated_cls, cls)
    return decorated_cls

# No error reported by static analyzer, but runtime error at `@struct`:
# AttributeError: 'mappingproxy' object has no attribute 'update'
@struct
class Entity:
    name: str
    value: int

# STATIC ANALYZER:
# Expected no arguments to "Entity" constructor Pylance(reportCallIssue)
# RUNTIME:
# (this line doesn't even get reached)
valid_entity = Entity(name="entity", value=42)

Traceback (most recent call last):
  File "C:\Users\***\temp.py", line 12, in <module>
    @struct
     ^^^^^^
  File "C:\Users\***\temp.py", line 7, in struct
    functools.update_wrapper(decorated_cls, cls)
  File "C:\Users\***\AppData\Local\Programs\Python\Python311\Lib\functools.py", line 58, in update_wrapper
    getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'mappingproxy' object has no attribute 'update'

完整回溯:

尝试3:包装器工厂(运行时✅,静态分析❌) struct 然后我尝试制作 functools.wraps() 一个装饰器工厂,并在一个仅转发到数据类函数的闭包函数上使用

def struct():
    decorator = dataclasses.dataclass(frozen=True, kw_only=True)

    @functools.wraps(decorator)
    def decorator_wrapper(*args, **kwargs):
        return decorator(*args, **kwargs)
    return decorator_wrapper

@struct()
class Entity:
    name: str
    value: int

# STATIC ANALYZER:
# Expected no arguments to "Entity" constructor Pylance(reportCallIssue)
valid_entity = Entity(name="entity", value=42)

# RUNTIME:
# Entity(name='entity', value=42)
print(valid_entity)

现在,这可以在运行时运行,但 Pylance 仍然报告与尝试 1 中相同的错误: dataclasses.dataclass 我还发现使用普通 () 函数本身(没有


)在所有 3 次尝试中都存在完全相同的问题。| ||有什么方法可以让它工作而不扰乱 IntelliSense?

可选后续: 为什么尝试 2 在运行时失败?


很不幸,你無法用你想要的方式來縮短數據類別裝飾器,同時又保留 Pylance 的完整類型提示支援。這是因為 Pylance(和其他類型檢查器)仰賴數據類別裝飾器的存在來辨識和分析數據類別。當你嘗試透過別名或包裝函數來抽象化裝飾器時,類型檢查器無法再直接推斷出它是個數據類別。

為什麼你的嘗試失敗了:

  1. 嘗試 1:直接賦值

Pylance(和大多數類型檢查器)不分析賦值來推斷類型。它們只看到你將 dataclasses.dataclass 結果 (一個裝飾器函數)賦值給 struct ,但無法辨識 struct 本身就是個數據類別裝飾器。

  1. 嘗試 2:包裝

這個嘗試在執行階段失敗,是因為 functools.update_wrapper 嘗試從被包裝函數(在此例中為由 dataclass 返回的類別)複製屬性到包裝函數(你的 struct 函數)。然而,數據類別修改了類別的 __dict__ 屬性,使其變成一個不可變的映射代理物件,導致 update 方法發生 AttributeError

  1. 嘗試 3:包裝函數工廠

雖然這個方法在執行階段有效,因為它實際上只是呼叫了 dataclasses.dataclass 裝飾器,但 Pylance 仍然無法辨識它。這是因為類型檢查器通常不深入分析包裝函數的內部來確定其行為。

可能的解決方案:

  • 使用 Pylance 的類型註記:

你可以使用 Pylance 的類型註記來明確告訴類型檢查器 struct 返回一個數據類別。這會改善類型提示,但仍然需要你使用函數呼叫語法: ```python from typing import TypeVar from dataclasses import dataclass

T = TypeVar('T')

def struct() -> callable[[type[T]], type[T]]: # 註記返回類型 return dataclass(frozen=True, kw_only=True)

@struct() # 注意仍然需要括號 class Entity: name: str value: int

# 現在 Pylance 應該能夠正確推斷類型 valid_entity = Entity(name="entity", value=42) ```

  • 接受冗餘:

不幸的是,為了獲得最佳的類型檢查支援,你可能需要接受在定義數據類別時重複使用完整的 @dataclasses.dataclass(frozen=True, kw_only=True) 裝飾器。

  • 為 Pylance 建議功能:

你可以考慮向 Pylance 團隊提交功能請求,要求他們增強對數據類別裝飾器別名的支援。

關於為什麼嘗試 2 失敗的補充說明:

functools.update_wrapper 的目的是將包裝函數偽裝成被包裝函數,以便在自省時看起來像原始函數。它會複製像 __name__ __doc__ __annotations__ 這樣的屬性。然而,數據類別透過將 __dict__ 替換為映射代理物件來修改類別,而映射代理物件是不可變的。這就是為什麼 update_wrapper 在嘗試更新 __dict__ 時會失敗的原因。

标签:python,decorator,intellisense,python-dataclasses,pylance
From: 78802585

相关文章

  • 当我们创建一个在 https 服务器上获取和发送数据的 python 应用程序时,我们应该如何处
    python脚本使用Urllib3,我的服务器是在Node.js上编写的脚本。我担心(并且不清楚)证书:我是否需要将我的python应用程序上的证书另存为变量?例如我这样做了,http=urllib3.PoolManager(cert_reqs="CERT_REQUIRED",ca_certs='client-cert.pem')并且我不知道......
  • 使用 Python 进行 Web 抓取以获取数据 NoneType ERROR
    我正在努力为我的学校项目获取美元和价格。所以我决定为此使用网络抓取,但我有一个问题。当我尝试在服务器上使用我的代码时,它给我NoneType错误。它可以在googlecolab上使用,但我无法在我的电脑或服务器上使用。我该如何解决这个问题?网页抓取代码;defdolar():he......
  • Python 请求 - response.json() 未按预期工作
    我正在尝试从Python的requests模块调用API。在邮递员上,返回的响应标头中的Content-Type是application/json;charset=utf-8,响应json数据是我期望的样子。但是,在python上的API的get方法之后运行response.json()会抛出错误simplejson.errors......
  • Python 中的“样板”代码?
    Google有一个Python教程,他们将样板代码描述为“不幸的”,并提供了以下示例:#!/usr/bin/python#importmodulesusedhere--sysisaverystandardoneimportsys#Gatherourcodeinamain()functiondefmain():print'Hellothere',sys.argv[1]#Command......
  • Python 3.9.1 中的 collections.abc.Callable 是否有 bug?
    Python3.9包含PEP585并弃用typing模块中的许多类型,转而支持collections.abc中的类型,现在它们支持__class_getitem__例如Callable就是这种情况。对我来说,typing.Callable和collections.abc.Ca......
  • 列表子类的 Python 类型
    我希望能够定义列表子类的内容必须是什么。该类如下所示。classA(list):def__init__(self):list.__init__(self)我想包含键入内容,以便发生以下情况。importtypingclassA(list:typing.List[str]):#Maybesomethinglikethisdef__init__(self):......
  • Python 中类型友好的委托
    考虑以下代码示例defsum(a:int,b:int):returna+bdefwrap(*args,**kwargs):#delegatetosumreturnsum(*args,**kwargs)该代码运行良好,只是类型提示丢失了。在Python中使用*args,**kwargs来实现​​委托模式是很常见的。如果有一种方法可......
  • 使用 python 支持构建自定义 vim 二进制文件
    背景Debian11vim软件包不包含python3支持。请参阅标题为“Debian11vim中不支持python-证据”的部分下面我需要vim支持python3YouCompleteMevim插件为了构建一个新的,我将vim9.0tarball下载到v......
  • 如何在Python 3.12+中正确使用泛型来提高代码质量?
    我正在尝试使用泛型来改进FastAPI应用程序中的类型注释。我有一个抽象存储库类,在其中使用泛型:fromabcimportABC,abstractmethodfromtypingimportListclassAbstractRepository[T](ABC):@abstractmethodasyncdefadd_one(self,data:dict)->T:......
  • python中的while循环不退出
    我试图完成第一年的python商业课程作业,但我的while循环无法退出,有人能帮忙吗?commisionTable=[{"admin_fee":100,"comm_rate":0.10},{"admin_fee":125,"comm_rate":0.12},{"admin_fee":150,"comm_rate":......