Pytest的數(shù)據(jù)結(jié)構(gòu)Stash

英語(yǔ) stash的大概意思是寶藏的意思,a secret store of valuables or money 。

Pytest中的 Stash類 是一個(gè)“類型安全的異構(gòu)可變映射”,用于在像 Config / Node 這類對(duì)象上存放插件或模塊的任意附加數(shù)據(jù),而不會(huì)發(fā)生名字沖突或破壞封裝。

  • 核心思想
    鍵由模塊級(jí)的 StashKey[T]() 實(shí)例表示(每個(gè) StashKey() 都是唯一的對(duì)象,按對(duì)象身份區(qū)分鍵)。
    StashKey 是泛型:StashKey[T] 表示該 key 對(duì)應(yīng)的值類型是 T,便于靜態(tài)類型檢查器推斷和檢查(類型安全)。
    Stash 內(nèi)部用普通字典存儲(chǔ):Dict[StashKey[Any], object],通過(guò) StashKey 的對(duì)象身份避免字符串命名沖突。

這個(gè)類很像字典,但是和字典不太一樣——最重要的區(qū)別就是 key,stash可以避免名字沖突。即同樣的一個(gè)鍵值 "chen" , 可以通過(guò)某種技術(shù)共存于這個(gè)鍵值對(duì)中——它是如何做到的?

簡(jiǎn)單而言,Stash將鍵對(duì)象化了,Python中對(duì)象的id是惟一的。

看看它的實(shí)現(xiàn)——這里使用了 Python3.8舊式的泛型

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

這個(gè)StashKey泛型類,內(nèi)部沒有什么內(nèi)容,除了一個(gè) __slots 的槽,這個(gè)東西作用是固定類的屬性,讓它不會(huì)被動(dòng)態(tài)添加屬性,同時(shí)讓它的內(nèi)存更緊湊一些。


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)

注釋部分基本上說(shuō)清楚了Stash的實(shí)現(xiàn)要點(diǎn)和用法。

主要是內(nèi)部的字典結(jié)構(gòu) Dict[StashKey[T], object]
當(dāng)你要?jiǎng)?chuàng)建一個(gè) StashKey的時(shí)候
這樣

stash_key = StashKey[str]() # 注意使用括號(hào)是類對(duì)象化,獲得唯一對(duì)象id,這也是Stash 處理名字沖突的技術(shù)

注意使用括號(hào)是類對(duì)象化,獲得唯一對(duì)象id,這也是Stash 處理名字沖突的技術(shù)
比如我們連續(xù)使用相同的串 "test" 創(chuàng)建兩個(gè)對(duì)象

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

這兩個(gè)對(duì)象是不一樣的 ,雖然在表面上我們看到的是兩個(gè)名稱相同的插件,或者其它什么東西

對(duì)象中可以 object[key] 這這樣用的類,都實(shí)現(xiàn)了 __getitem__
賦值操作 obejct[key] = value 對(duì)應(yīng) __setitem__

關(guān)鍵字 del key 則對(duì)應(yīng)協(xié)議 __delitem__

總結(jié)

Stash 其實(shí)是一個(gè)字典封裝,它重新實(shí)現(xiàn)了 __getitem__ __setitem 等協(xié)議,使他用起來(lái)的感覺和字典一樣,同時(shí),它配合 StashKey 提供了最關(guān)鍵的功能 解決名稱沖突—— 不依賴用戶提供的字符串或者其它可哈希的值來(lái)建立字典。

  • 下面有個(gè)例子展示了 Stash的用法
from typing import IO, Optional

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

# 在模塊頂層定義 StashKey(每個(gè) key 實(shí)例唯一,避免沖突)
config_logfile_key = StashKey[IO[bytes]]()      # 存放會(huì)話級(jí)臨時(shí)文件句柄
item_flag_key = StashKey[bool]()               # 存放 item 級(jí)布爾標(biāo)志


def pytest_configure(config: Config) -> None:
    """在配置階段創(chuàng)建會(huì)話級(jí)資源并放到 config.stash"""
    f = tempfile.TemporaryFile("w+b")
    config.stash[config_logfile_key] = f


def pytest_unconfigure(config: Config) -> None:
    """在卸載階段清理會(huì)話級(jí)資源并從 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:
    """為每個(gè)測(cè)試項(xiàng)設(shè)置 item 級(jí) stash 值(比如記錄某個(gè)狀態(tài))"""
    # 標(biāo)記這個(gè) item 已經(jīng)通過(guò) setup
    item.stash[item_flag_key] = True


def pytest_runtest_teardown(item: Item) -> None:
    """測(cè)試項(xiàng) teardown 時(shí)清理 item 級(jí) stash"""
    if item_flag_key in item.stash:
        del item.stash[item_flag_key]


def pytest_runtest_call(item: Item) -> None:
    """運(yùn)行階段讀取 item.stash 的示例(只讀或做判斷)"""
    was_setup = item.stash.get(item_flag_key, False)
    if not was_setup:
        # 如果需要,可以根據(jù) stash 的狀態(tài)采取動(dòng)作
        item.warn_outcome = "setup_not_done"
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容