Chr*_*ial 5 python pytest python-typing
我有一个导入特定模块的 pytest 夹具。这是必需的,因为导入模块非常昂贵,因此我们不想在导入时(即在 pytest 测试收集期间)执行此操作。这会产生如下代码:
@pytest.fixture
def my_module_fix():
import my_module
yield my_module
def test_something(my_module_fix):
assert my_module_fix.my_func() = 5
Run Code Online (Sandbox Code Playgroud)
我正在使用 PyCharm,并希望在我的测试中进行类型检查和自动完成。为了实现这一点,我必须以某种方式将my_module_fix参数注释为具有模块的类型my_module。
我不知道如何实现这一目标。我发现我可以注释my_module_fix为 type types.ModuleType,但这还不够:它不是任何模块,它始终是my_module。
如果我听到你的问题,你有两个(或三个)单独的目标
\nslowmodule我可以想到至少五种不同的方法,但我只会简单地提到最后一种,因为它太疯狂了。
\n这是(到目前为止)最常见和恕我直言首选的解决方案。
\n例如代替
\nimport slowmodule\n\ndef test_foo():\n slowmodule.foo()\n\ndef test_bar():\n slowmodule.bar()\nRun Code Online (Sandbox Code Playgroud)\n你会写:
\ndef test_foo():\n import slowmodule\n slowmodule.foo()\n\ndef test_bar():\n import slowmodule\n slowmodule.bar()\nRun Code Online (Sandbox Code Playgroud)\n[延迟导入]这里,模块将按需/延迟导入。因此,如果您将 pytest 设置为快速失败,并且另一个测试在 pytest 到达您的 ( test_foo,test_bar ) 测试之前另一个测试失败,则该模块将永远不会被导入,并且您将永远不会产生运行时成本。
由于Python的模块缓存,后续的导入语句实际上不会重新导入模块,而只是获取对已导入模块的引用。
\n[自动完成/打字]当然,在这种情况下,自动完成将继续按您的预期工作。这是一个完美的导入模式。
\n虽然它确实需要添加潜在的许多额外的导入语句(每个测试函数中一个),但它会立即清楚发生了什么(无论是否清楚为什么会发生)。
\n__getattr__slowmodule_proxy.py如果您创建一个包含以下内容的模块(例如):
def __getattr__(name):\n import slowmodule\n return getattr(slowmodule, name)\nRun Code Online (Sandbox Code Playgroud)\n在你的测试中,例如
\nimport slowmodule\n\ndef test_foo():\n slowmodule.foo()\n\ndef test_bar():\n slowmodule.bar()\nRun Code Online (Sandbox Code Playgroud)\n代替:
\nimport slowmodule\nRun Code Online (Sandbox Code Playgroud)\n你写:
\nimport slowmodule_proxy as slowmodule\nRun Code Online (Sandbox Code Playgroud)\n[延迟导入]感谢PEP-562,您可以从 中“请求”任何名称slowmodule_proxy,它将从 中获取并返回相应的名称slowmodule。正如上面一样,包含函数import 内部slowmodule将导致仅在调用并执行函数时而不是在模块加载时导入。当然,模块缓存在这里仍然适用,因此每个解释器会话只会遭受一次导入惩罚。
[自动完成]但是,虽然延迟导入可以工作(并且您的测试运行没有问题),但这种方法(如上所述)将“破坏”自动完成:
\n现在我们进入了 PyCharm 的领域。一些 IDE 将对模块执行“实时”分析,并实际加载模块并检查其成员。(PyDev 有这个选项)。如果 PyCharm 这样做,实现module.__dir__(相同的 PEP)或__all__允许您的代理模块伪装成实际的slowmodule和自动完成将工作。\xe2\x80\xa0 但是,PyCharm 不这样做。
尽管如此,您可以欺骗 PyCharm 为您提供自动完成建议:
\nif False:\n import slowmodule\nelse:\n import slowmodule_proxy as slowmodule\nRun Code Online (Sandbox Code Playgroud)\n解释器只会执行else分支,导入代理并命名它slowmodule(这样你的测试代码可以继续引用slowmodule不变)。
但 PyCharm 现在将为底层模块提供自动完成功能:
\n\n\xe2\x80\xa0虽然实时分析非常有用,但它也存在静态语法分析所没有的(潜在的)安全问题。类型提示和存根文件的成熟使其不再是一个问题。
\nslowmodule显式代理如果您真的讨厌动态代理方法(或者您必须以这种方式欺骗 PyCharm),您可以显式代理该模块。
\nslowmodule(如果API 稳定,您可能只想考虑这一点。)
如果slowmodule有方法foo,bar您将创建一个代理模块,例如:
def foo(*args, **kwargs):\n import slowmodule\n return slowmodule.foo(*args, **kwargs)\n\ndef bar(*args, **kwargs):\n import slowmodule\n return slowmodule.bar(*args, **kwargs)\nRun Code Online (Sandbox Code Playgroud)\n(使用args和kwargs将参数传递给底层可调用函数。您可以向这些函数添加类型提示以镜像函数slowmodule。)
在你的测试中,
\nimport slowmodule_proxy as slowmodule\nRun Code Online (Sandbox Code Playgroud)\n和之前一样。在方法内部导入可以为您提供所需的延迟导入,并且模块缓存负责多个导入调用。
\n由于它是一个真实的模块,其内容可以进行静态分析,因此无需“欺骗”PyCharm。
\nif False因此,此解决方案的好处是您的测试导入不会出现奇怪的外观。slowmodule然而,这样做的代价是必须与模块一起维护代理文件,这在API 不稳定的情况下可能会很痛苦。
importlibLazyLoader 代替代理模块您可以遵循与文档中所示slowmodule_proxy类似的模式,而不是代理模块。importlib
\n\nRun Code Online (Sandbox Code Playgroud)\n>>> import importlib.util\n>>> import sys\n>>> def lazy_import(name):\n... spec = importlib.util.find_spec(name)\n... loader = importlib.util.LazyLoader(spec.loader)\n... spec.loader = loader\n... module = importlib.util.module_from_spec(spec)\n... sys.modules[name] = module\n... loader.exec_module(module)\n... return module\n...\n>>> lazy_typing = lazy_import("typing")\n>>> #lazy_typing is a real module object,\n>>> #but it is not loaded in memory yet.\n
不过,你仍然需要愚弄 PyCharm,所以类似:
\nif False:\n import slowmodule\nelse:\n slowmodule = lazy_import(\'slowmodule\')\nRun Code Online (Sandbox Code Playgroud)\n将是必要的。
\n除了模块成员访问的单个附加间接级别(以及两个次要版本可用性差异)之外,我并不清楚从这种方法相对于以前的代理模块方法可以获得什么(如果有的话) , 然而。
\nimportlibFinder/Loader 机制来挂钩导入(不要这样做)您可以创建一个自定义模块查找器/加载器,它(仅)挂钩您的slowmodule导入,并加载例如您的代理模块。
然后,您可以在测试中导入慢模式之前导入“importhook”模块,例如
\nimport myimporthooks\nimport slowmodule\n\ndef test_foo():\n ...\nRun Code Online (Sandbox Code Playgroud)\n(在这里,myimporthooks将使用importlib\ 的查找器和加载器机制来执行与importhook包类似的操作,但拦截并重定向导入尝试,而不仅仅是充当导入回调。)
但这太疯狂了。 不仅你想要的(看似)可以通过(无限)更常见和受支持的方法来实现,而且它非常脆弱,容易出错,而且,如果不深入 PyTest 的内部(这可能会干扰模块加载器本身),很难说它是否有效。
\n| 归档时间: |
|
| 查看次数: |
934 次 |
| 最近记录: |