英語(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"