rv.*_*tch 4 python python-dataclasses python-typing
假设我有一个自定义用例,我需要动态创建或定义__init__数据类的方法。
例如,假设我需要像这样装饰它,@dataclass(init=False)然后修改__init__()方法以采用关键字参数,例如**kwargs. 但是,在kwargs对象中,我仅检查已知数据类字段是否存在,并相应地设置这些属性(下面的示例)
我想向我的 IDE (PyCharm) 键入提示,修改后的内容__init__仅接受列出的数据类字段作为参数或关键字参数。我不确定是否有办法解决这个问题,使用typing库或其他方式。我知道 PY3.11 已计划进行数据类转换,这可能会也可能不会满足我的要求(我的直觉是否定的)。
这是我正在使用的示例代码,这是一个基本案例,说明了我遇到的问题:
from dataclasses import dataclass
# get value from input source (can be a file or anything else)
def get_value_from_src(_name: str, tp: type):
return tp() # dummy value
@dataclass
class MyClass:
foo: str
apple: int
def __init__(self, **kwargs):
for name, tp in self.__annotations__.items():
if name in kwargs:
value = kwargs[name]
else:
# here is where I would normally have the logic
# to read the value from another input source
value = get_value_from_src(name, tp)
if value is None:
raise ValueError
setattr(self, name, value)
c = MyClass(apple=None)
print(c)
c = MyClass(foo='bar', # here, I would like to auto-complete the name
# when I start typing `apple`
)
print(c)
Run Code Online (Sandbox Code Playgroud)
如果我们假设字段的数量或名称不固定,我很好奇是否可以有一种通用方法,基本上对类型检查器说,“__init__此类的仅接受与字段匹配的(可选)关键字参数在数据类本身中定义”。
附录,基于以下评论中的注释:
通过@dataclass(kw_only=True)是行不通的,因为假设我正在为一个库编写此代码,并且需要支持 Python 3.7+。此外,在实现kw_only自定义时也没有效果,如本例所示。__init__()
上面只是一个存根__init__方法。它可以具有更复杂的逻辑,例如基于文件源设置属性。基本上,上面只是一个更大用例的示例实现。
我无法更新每个字段,foo: Optional[str] = None因为该部分将在用户代码中实现,而我无法对其进行任何控制。__init__()此外,当您知道将为您生成自定义方法时,以这种方式注释它是没有意义的- 意味着不是由dataclasses. 最后,为每个字段设置默认值,以便可以在没有参数的情况下实例化类,例如MyClass(),对我来说似乎不是最好的主意。
让dataclasses自动生成一个__init__并实现一个是行不通的__post_init__()。这是行不通的,因为我需要能够在没有参数的情况下构造类,例如MyClass(),因为字段值将从另一个输入源设置(认为本地文件或其他地方);这意味着所有字段都是必需的,因此Optional在这种情况下对它们进行注释是错误的。我仍然需要能够支持用户输入可选的关键字参数,但这些参数**kwargs始终与数据类字段名称匹配,因此我希望有某种方式可以自动完成与我的 IDE (PyCharm) 一起使用
希望这篇文章能够澄清期望和期望的结果。如果有任何问题或任何不清楚的地方,请告诉我。
你所描述的在理论上是不可能的,在实践中也不可能可行。
类型检查器不会运行您的代码,它们只是读取它。动态类型注释是一个矛盾的术语。
我相信您知道,静态类型检查器一词并非巧合。静态类型检查器不会执行您编写的代码。它只是解析它并根据它自己的内部逻辑推断类型,方法是将某些规则应用于它从代码派生的图表。
这很重要,因为与其他一些语言不同,Python 是动态类型的,正如您所知,这意味着“事物”(变量)的类型可以在任何时候完全改变。一般来说,理论上,如果不实际单步执行整个算法(也就是说运行代码),就无法知道代码中所有变量的类型。
作为一个愚蠢但说明性的示例,您可以决定将类型的名称放入文本文件中以在运行时读取,然后用于注释代码中的某些变量。你能用有效的 Python 代码和输入来做到这一点吗?当然。但我认为静态类型检查器永远不会知道该变量的类型是非常清楚的。
抽象出方法中的所有内容和dataclass可能的逻辑,您所要求的内容可归结为以下内容。__init__
“我想定义一个方法(__init__),但它的参数类型只有在运行时才知道。”
我为什么这么说?我的意思是,您确实注释了类属性的类型,对吧?所以你就有了这些类型!
__init__当然,但正如您自己指出的那样,一般来说,这些与您可以传递给该方法的参数没有任何关系。您希望该__init__方法接受任意关键字参数。然而,您还需要一个静态类型检查器来推断那里允许/期望哪些类型。
要连接两者(属性类型和方法参数类型),您当然可以编写某种逻辑。您甚至可以以强制遵守这些类型的方式来实现它。该逻辑可以读取类属性的类型注释,匹配并在其中之一不匹配时**kwargs引发。TypeError这是完全可能的,并且您几乎已经在示例代码中实现了这一点。但这仅在运行时有效!
同样,静态类型检查器无法推断这一点,特别是因为您所需的类应该只是基类,并且任何后代都可以在任何时候引入自己的属性/类型。
dataclasses工作,不是吗?您可能会争辩说,这种动态注释__init__方法的方式适用于数据类。那么为什么它们如此不同呢?为什么它们可以正确推断,但您提出的代码却不能?
答案是,他们不是。
即使他们在动态dataclasses构造.__init___init_fn
mypy正确推断这些类型的唯一原因是因为它们仅为数据类实现了一个单独的插件。这意味着它有效,因为他们通读了PEP 557并手工制作了一个插件,mypy专门促进基于那里描述的规则的类型推断。
您可以看到该方法中发生的魔法DataclassTransformer.transform。您不能将此行为推广到任意代码,这就是为什么他们必须为此编写整个插件的原因。
我不太熟悉 PyCharm 如何进行类型检查,但我强烈怀疑他们使用了类似的东西。
因此,您可能会认为dataclasses在静态类型检查方面是“作弊”。虽然我当然不是在抱怨。
即使像我个人喜欢并广泛使用的 Pydantic 这样“高调”的东西,也需要它自己的插件来正确mypy实现类型推断(请参阅此处)。对于 PyCharm,他们有自己单独的Pydantic 插件,没有它,内部类型检查器就无法为初始化等提供那些不错的自动建议。__init__
如果你真的想更进一步,这种方法将是你最好的选择。请注意,这将是(从最好的意义上来说)一种黑客行为,允许特定类型检查器捕获“错误”,否则它们将无法捕获。
我认为它不太可行的原因是,它本质上会增加项目的工作量,以涵盖您想要满足的类型检查器的特定技巧。如果你有足够的决心并且有足够的资源,那就去做吧。
我并不是想劝阻你。但了解环境所施加的限制很重要。它要么是动态类型和不完美的类型检查(仍然喜欢mypy),要么是静态类型并且没有“kwargs可以是任何东西”的行为。
希望这是有道理的。如果我犯了任何错误,请告诉我。这只是基于我对Python输入的理解。