我有一个代码库,它使用一种非常奇怪的模式来定义命令行选项。看起来像这样:
# opts.py
def group():
o = OptionsGroup()
return o, o.define
options = _SomeOptionsSingletonClass()
def define(name: str, type_: Type, default: bool, ...):
# Create and set attribute on `options` singleton object to track this option.
...
class OptionsGroup:
def define(name: str, type_: Type, default: bool, ...):
define(*args)
然后,在代码库中的各个文件中,模式 1
# some_module.py
from opts import group
options, define = group()
define("foo", type="str", ...)
# ...
print(options.foo) # How the heck do I typecheck this?
或模式 2:
# some_module.py
from opts import options, define
define("foo", type="str", ...)
# ...
print(options.foo) # How the heck do I typecheck this?
我们广泛使用
mypy
来对代码进行类型检查,其中许多选项结构都处于需要覆盖的关键路径中。
mypy
。我不喜欢这个解决方案,因为它意味着 mypy
无法针对 actual 代码库运行,而且脚本的逻辑非常脆弱。 (我可以修复脆弱性)。mypy
插件来推断类型并将类型添加到 options
值。
get_function_signature_hook()
或 get_function_hook()
或 get_method_signature_hook()
或 get_method_hook()
钩子在模式 1 中对 define()
的任何调用上触发。__getattr__()
,并且更改这些类的结构非常危险。我强烈希望使用
mypy
插件或其他允许类型检查来理解我的实际源文件中的结构的策略,而不是使用经过修改的源树。
广泛地说:是否有更好的解决方案来检查此模式的类型?
狭义上:有什么方法可以让
mypy
调用我的插件,同时评估对 define()
的调用和/或对 options
属性的引用,以便我可以合成这些属性引用的类型?
因此,下面的解决方案确实涉及稍微更改模式,但与定义完全兼容,因为它提供了围绕它的包装器。希望这对您有用!
我们定义一个类对象,然后将其用于类型检查和定义变量。您可以根据需要更新模式,因为它不应该与定义中发生的情况发生冲突(如果定义使用字符串作为类型,您可能需要在define_all中定义映射)。我在下面对模式 2 进行了描述,因为它更简单,但没有理由不能与模式 1 等效:
from typing import cast
# opts.py
class _SomeOptionsSingletonClass:
pass
options = _SomeOptionsSingletonClass()
def define(name: str, type_: type):
# Just made this up as an example use
print(f"defined attr, {name}, type {type_}")
if type_ == str:
setattr(options, name, "test")
elif type_ == int:
setattr(options, name, 1)
else:
assert False
# New code for opts.py
class BaseTypesToAdd:
pass
def define_all(t: type[BaseTypesToAdd]):
for name, type_ in t.__annotations__.items():
define(name=name, type_=type_)
# NewPattern2
class NewOptions(BaseTypesToAdd):
foo: str
bar: int
define_all(NewOptions)
options = cast(NewOptions, options)
print(options.foo)