<p>你的问题似乎有几个部分</p>
<br/>
<h4>(1)在运行时创建类型检查字典
<br/>
<p><a href="https://stackoverflow.com/questions/69555006/how-to-type-hint-type-check-a-dictionary-at-runtime-for-an-arbitrary-number#comment122963198_69555006">As @juanpa.arrivillaga says in the comments</a>,这与类型-<em>检查</em>有关,但似乎与类型-<em>暗示</em>无关。然而,设计自己的自定义类型检查数据结构是相当简单的。您可以使用<a href="https://docs.python.org/3/library/collections.html#collections.UserDict" rel="nofollow noreferrer">^{<cd1>}</a>这样做:</p>
<pre><code>from collections import UserDict
from numbers import Number
class StrNumberDict(UserDict):
def __setitem__(self, key, value):
if not isinstance(key, str):
raise TypeError(
f'Invalid type for dictionary key: '
f'expected "str", got "{type(key).__name__}"'
)
if not isinstance(value, Number):
raise TypeError(
f'Invalid type for dictionary value: '
f'expected "Number", got "{type(value).__name__}"'
)
super().__setitem__(key, value)
</code></pre>
<p>使用中:</p>
<pre><code>>>> d = StrNumberDict()
>>> d['foo'] = 5
>>> d[5] = 6
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "<string>", line 5, in __setitem__
TypeError: Invalid type for dictionary key: expected "str", got "int"
>>> d['bar'] = 'foo'
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "<string>", line 10, in __setitem__
TypeError: Invalid type for dictionary value: expected "Number", got "str"
</code></pre>
<p>如果你想概括这类事情,你可以这样做:</p>
<pre><code>from collections import UserDict
class TypeCheckedDict(UserDict):
def __init__(self, key_type, value_type, initdict=None):
self._key_type = key_type
self._value_type = value_type
super().__init__(initdict)
def __setitem__(self, key, value):
if not isinstance(key, self._key_type):
raise TypeError(
f'Invalid type for dictionary key: '
f'expected "{self._key_type.__name__}", '
f'got "{type(key).__name__}"'
)
if not isinstance(value, self._value_type):
raise TypeError(
f'Invalid type for dictionary value: '
f'expected "{self._value_type.__name__}", '
f'got "{type(value).__name__}"'
)
super().__setitem__(key, value)
</code></pre>
<p>使用中:</p>
<pre><code>>>> from numbers import Number
>>> d = TypeCheckedDict(key_type=str, value_type=Number, initdict={'baz': 3.14})
>>> d['baz']
3.14
>>> d[5] = 5
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "<string>", line 9, in __setitem__
TypeError: Invalid type for dictionary key: expected "str", got "int"
>>> d['foo'] = 'bar'
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "<string>", line 15, in __setitem__
TypeError: Invalid type for dictionary value: expected "Number", got "str"
>>> d['foo'] = 5
>>> d['foo']
5
</code></pre>
<p>注意,您不需要对传递给<code>super().__init__()</code>的字典进行类型检查<code>UserDict.__init__</code>调用<code>self.__setitem__</code>,您已经覆盖了它,因此如果您将无效的字典传递给<code>TypeCheckedDict.__init__</code>,您会发现异常的引发方式与在构造字典后尝试向字典添加无效键或值的方式相同:</p>
<pre><code>>>> from numbers import Number
>>> d = TypeCheckedDict(str, Number, {'foo': 'bar'})
Traceback (most recent call last):
File "<string>", line 1, in <module>
File "<string>", line 5, in __init__
line 985, in __init__
self.update(dict)
line 842, in update
self[key] = other[key]
File "<string>", line 16, in __setitem__
TypeError: Invalid type for dictionary value: expected "Number", got "str"
</code></pre>
<p><code>UserDict</code>是专门为方便以这种方式进行子类化而设计的,这就是为什么在这个实例中它是比<code>dict</code>更好的基类</p>
<p>如果要向<code>TypeCheckedDict</code>添加类型提示,可以这样做:</p>
<pre><code>from collections import UserDict
from collections.abc import Mapping, Hashable
from typing import TypeVar, Optional
K = TypeVar('K', bound=Hashable)
V = TypeVar('V')
class TypeCheckedDict(UserDict[K, V]):
def __init__(
self,
key_type: type[K],
value_type: type[V],
initdict: Optional[Mapping[K, V]] = None
) -> None:
self._key_type = key_type
self._value_type = value_type
super().__init__(initdict)
def __setitem__(self, key: K, value: V) -> None:
if not isinstance(key, self._key_type):
raise TypeError(
f'Invalid type for dictionary key: '
f'expected "{self._key_type.__name__}", '
f'got "{type(key).__name__}"'
)
if not isinstance(value, self._value_type):
raise TypeError(
f'Invalid type for dictionary value: '
f'expected "{self._value_type.__name__}", '
f'got "{type(value).__name__}"'
)
super().__setitem__(key, value)
</code></pre>
<p>(上文<a href="https://mypy-play.net/?mypy=latest&python=3.10&flags=show-error-codes%2Cstrict&gist=6fe31c0ff1d341a56e34b6420a7ea6d0" rel="nofollow noreferrer">passes MyPy</a>。)</p>
<p>但是,请注意,添加类型提示与此数据结构在运行时的工作方式完全无关</p>
<br/>
<h4>(2)类型提示词典“用于灵活数量的任意键”</h4>
<br/>
<p>我不太清楚这是什么意思,但如果希望MyPy在向字典添加字符串值时引发错误,则只希望有数值,<a href="https://mypy-play.net/?mypy=latest&python=3.10&gist=93a1fa01306e4d87df5b7a0f287037c2" rel="nofollow noreferrer">you could do it like this</a>:</p>
<pre><code>from typing import SupportsFloat
d: dict[str, SupportsFloat] = {}
d['a'] = 5 # passes MyPy
d['b'] = 4.67 # passes MyPy
d[5] = 6 # fails MyPy
d['baz'] = 'foo' # fails Mypy
</code></pre>
<p>如果您想要MyPy静态检查<em>和</em>运行时检查,您最好(在我看来)使用上面的<code>TypeCheckedDict</code>类型提示版本:</p>
<pre><code>d = TypeCheckedDict(str, SupportsFloat) # type: ignore[misc]
d['a'] = 5 # passes MyPy
d['b'] = 4.67 # passes MyPy
d[5] = 6 # fails Mypy
d['baz'] = 'foo' # fails Mypy
</code></pre>
<p>Mypy对我们将抽象类型作为参数传递给<code>TypeCheckedDict.__init__</code>不太满意,因此在实例化dict时必须添加一个<code># type: ignore[misc]</code>(这对我来说就像是一个Mypy bug。)但是,除此之外,<a href="https://mypy-play.net/?mypy=latest&python=3.10&flags=show-error-codes%2Cstrict&gist=c7bffa7f03f6a6613c6198aa67100cf3" rel="nofollow noreferrer">it works fine</a></p>
<p>(有关使用<code>SupportsFloat</code>提示数字类型的注意事项,请参见我的<a href="https://stackoverflow.com/questions/69334475/how-to-hint-at-number-types-i-e-subclasses-of-number-not-numbers-themselv/69383462#69383462">previous answer</a>。如果使用Python<;=3.8,则使用<code>typing.Dict</code>而不是<code>dict</code>提示类型。)</p>
<br/>
(3)使用<code>typeguard</code></h4>
<br/>
<p>由于您使用的是<code>typeguard</code>,因此可以稍微简化我的<code>StrNumberDict</code>类中的逻辑,如下所示:</p>
<pre><code>from collections import UserDict
from typeguard import typechecked
from typing import SupportsFloat
class StrNumberDict(UserDict[str, SupportsFloat]):
@typechecked
def __setitem__(self, key: str, value: SupportsFloat) -> None:
super().__setitem__(key, value)
</code></pre>
<p>但是,如果您想要一个更通用的<code>TypeCheckedDict</code>可以通过任意类型检查进行实例化,我认为没有办法通过<code>typeguard</code>实现这一点。以下<em><strong>不起作用:</p>
<pre><code>### THIS DOES NOT WORK ###
from typing import TypeVar, SupportsFloat
from collections.abc import Hashable
from collections import UserDict
from typeguard import typechecked
K = TypeVar('K', bound=Hashable)
V = TypeVar('V')
class TypeCheckedDict(UserDict[K, V]):
@typechecked
def __setitem__(self, key: K, value: V) -> None:
super().__setitem__(key, value)
d = TypeCheckedDict[str, SupportsFloat]()
d[5] = 'foo' # typeguard raises no error here.
</code></pre>
<p>还值得注意的是,typeguard是<a href="https://github.com/agronholm/typeguard/issues/198" rel="nofollow noreferrer">not currently maintained</a>,因此使用该特定库会涉及一定的风险</p>