Pytest的数据结构Stash

英语 stash的大概意思是宝藏的意思,a secret store of valuables or money 。

Pytest中的 Stash类 是一个“类型安全的异构可变映射”,用于在像 Config / Node 这类对象上存放插件或模块的任意附加数据,而不会发生名字冲突或破坏封装

  • 核心思想
    键由模块级的 StashKey[T]() 实例表示(每个 StashKey() 都是唯一的对象,按对象身份区分键)。
    StashKey 是泛型:StashKey[T] 表示该 key 对应的值类型是 T,便于静态类型检查器推断和检查(类型安全)。
    Stash 内部用普通字典存储:Dict[StashKey[Any], object],通过 StashKey 的对象身份避免字符串命名冲突。

这个类很像字典,但是和字典不太一样——最重要的区别就是 key,stash可以避免名字冲突。即同样的一个键值 "chen" , 可以通过某种技术共存于这个键值对中——它是如何做到的?

简单而言,Stash将键对象化了,Python中对象的id是惟一的。

看看它的实现——这里使用了 Python3.8旧式的泛型

class StashKey(Generic[T]):
    __slots__ = ()

这个StashKey泛型类,内部没有什么内容,除了一个 __slots 的槽,这个东西作用是固定类的属性,让它不会被动态添加属性,同时让它的内存更紧凑一些。


class Stash:
    r"""``Stash`` is a type-safe heterogeneous mutable mapping that
    allows keys and value types to be defined separately from
    where it (the ``Stash``) is created.

    Usually you will be given an object which has a ``Stash``, for example
    :class:`~pytest.Config` or a :class:`~_pytest.nodes.Node`:

    .. code-block:: python

        stash: Stash = some_object.stash

    If a module or plugin wants to store data in this ``Stash``, it creates
    :class:`StashKey`\s for its keys (at the module level):

    .. code-block:: python

        # At the top-level of the module
        some_str_key = StashKey[str]()
        some_bool_key = StashKey[bool]()

    To store information:

    .. code-block:: python

        # Value type must match the key.
        stash[some_str_key] = "value"
        stash[some_bool_key] = True

    To retrieve the information:

    .. code-block:: python

        # The static type of some_str is str.
        some_str = stash[some_str_key]
        # The static type of some_bool is bool.
        some_bool = stash[some_bool_key]
    """

    __slots__ = ("_storage",)

    def __init__(self) -> None:
        self._storage: Dict[StashKey[Any], object] = {}

    def __setitem__(self, key: StashKey[T], value: T) -> None:
        """Set a value for key."""
        self._storage[key] = value

    def __getitem__(self, key: StashKey[T]) -> T:
        """Get the value for key.

        Raises ``KeyError`` if the key wasn't set before.
        """
        return cast(T, self._storage[key])

    def get(self, key: StashKey[T], default: D) -> Union[T, D]:
        """Get the value for key, or return default if the key wasn't set
        before."""
        try:
            return self[key]
        except KeyError:
            return default

    def setdefault(self, key: StashKey[T], default: T) -> T:
        """Return the value of key if already set, otherwise set the value
        of key to default and return default."""
        try:
            return self[key]
        except KeyError:
            self[key] = default
            return default

    def __delitem__(self, key: StashKey[T]) -> None:
        """Delete the value for key.

        Raises ``KeyError`` if the key wasn't set before.
        """
        del self._storage[key]

    def __contains__(self, key: StashKey[T]) -> bool:
        """Return whether key was set."""
        return key in self._storage

    def __len__(self) -> int:
        """Return how many items exist in the stash."""
        return len(self._storage)

注释部分基本上说清楚了Stash的实现要点和用法。

主要是内部的字典结构 Dict[StashKey[T], object]
当你要创建一个 StashKey的时候
这样

stash_key = StashKey[str]() # 注意使用括号是类对象化,获得唯一对象id,这也是Stash 处理名字冲突的技术

注意使用括号是类对象化,获得唯一对象id,这也是Stash 处理名字冲突的技术
比如我们连续使用相同的串 "test" 创建两个对象

obj_key1 = StashKey[str]()
obj_key2 = StashKey[str]()

这两个对象是不一样的 ,虽然在表面上我们看到的是两个名称相同的插件,或者其它什么东西

对象中可以 object[key] 这这样用的类,都实现了 __getitem__
赋值操作 obejct[key] = value 对应 __setitem__

关键字 del key 则对应协议 __delitem__

总结

Stash 其实是一个字典封装,它重新实现了 __getitem__ __setitem 等协议,使他用起来的感觉和字典一样,同时,它配合 StashKey 提供了最关键的功能 解决名称冲突—— 不依赖用户提供的字符串或者其它可哈希的值来建立字典。

  • 下面有个例子展示了 Stash的用法
from typing import IO, Optional

import tempfile
from _pytest.stash import StashKey
from _pytest.config import Config
from _pytest.nodes import Item

# 在模块顶层定义 StashKey(每个 key 实例唯一,避免冲突)
config_logfile_key = StashKey[IO[bytes]]()      # 存放会话级临时文件句柄
item_flag_key = StashKey[bool]()               # 存放 item 级布尔标志


def pytest_configure(config: Config) -> None:
    """在配置阶段创建会话级资源并放到 config.stash"""
    f = tempfile.TemporaryFile("w+b")
    config.stash[config_logfile_key] = f


def pytest_unconfigure(config: Config) -> None:
    """在卸载阶段清理会话级资源并从 stash 删除"""
    if config_logfile_key in config.stash:
        f = config.stash[config_logfile_key]
        try:
            f.close()
        finally:
            del config.stash[config_logfile_key]


def pytest_runtest_setup(item: Item) -> None:
    """为每个测试项设置 item 级 stash 值(比如记录某个状态)"""
    # 标记这个 item 已经通过 setup
    item.stash[item_flag_key] = True


def pytest_runtest_teardown(item: Item) -> None:
    """测试项 teardown 时清理 item 级 stash"""
    if item_flag_key in item.stash:
        del item.stash[item_flag_key]


def pytest_runtest_call(item: Item) -> None:
    """运行阶段读取 item.stash 的示例(只读或做判断)"""
    was_setup = item.stash.get(item_flag_key, False)
    if not was_setup:
        # 如果需要,可以根据 stash 的状态采取动作
        item.warn_outcome = "setup_not_done"
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容