如何处理名称冲突 collections.Counter 和 typing.Counter?
Posted
技术标签:
【中文标题】如何处理名称冲突 collections.Counter 和 typing.Counter?【英文标题】:How to deal with name *** collections.Counter and typing.Counter? 【发布时间】:2020-08-16 10:49:47 【问题描述】:名称Counter
在collections
(作为类)和typing
(作为通用类型名称)中都定义了。不幸的是,它们略有不同。处理此问题的推荐方法是什么?
异同:
在from collections import Counter
之后,
-
您可以调用构造函数
Counter("foo")
来创建一个新的Counter
对象;
你可以验证它是dict
的子类:issubclass(Counter, dict)
返回True
;
您不能使用它来声明Counter
的特定变体,例如cnt: Counter[str] = Counter("foo")
raises TypeError: 'type' object is not subscriptable
(类型提示失败)
在from typing import Counter
之后,
-
您可以调用构造函数
Counter("foo")
来创建一个新的Counter
对象(实际上,有点令我惊讶);
你不能用它来验证它是dict
的子类:issubclass(Counter, dict)
raises TypeError: issubclass() arg 1 must be a class
;
您可以声明Counter
的特定变体,例如cnt: Counter[str] = Counter("foo")
。
在很多情况下 1.1 和 2.1 已经足够好了,所以导入的选择并不重要。
但似乎您不能同时使用 1.3 和 2.2 进行一次导入。后两者中,类型提示比子类检查更重要。
如果你想写类型提示,那么from typing import Counter
就足够了。
不过,如果您编写,我会发现它更清晰(并且更符合某些其他类型的需求)
from collections import Counter # to indicate that you want the implementation
from typing import Counter # to indicate that you want to write type hints
(注意顺序很重要。)
如果你想拥有这一切怎么办? 这些是我看到的选项:
-
做
from collections import Counter
import typing
并使用typing.Counter
实现1.3。不好听,太啰嗦了。
-
做
import collections
from typing import Counter
并使用collections.Counter
实现2.2(如果需要;我在教学中需要它)。
-
做
from collections import Counter as counter
from typing import Counter
并使用counter
实现2.2。
-
做
from collections import Counter
from typing import Counter as Bag # or Multiset
并在类型提示中使用Bag
(或Multiset
)。 (但这肯定会令人困惑。)
-
做(按照评论中的建议)
import collections as co # to access the class
from typing import Counter # to access constructor and provide type hints
并使用
co.Counter
或 Counter
作为构造函数
使用co.Counter
作为类,例如issubclass(co.Counter, dict)
在类型提示中使用Counter
,例如cnt: Counter[str]
那么是不是也值得推荐呢
from typing import Deque
并使用Deque
作为构造函数,而不是co.deque
? (我认为/希望不会。)
对于其他类型(例如defaultdict
和deque
),这似乎不是问题:
from collections import defaultdict, deque
from typing import DefaultDict, Deque
给你所有。
我是否忽略了什么?
【问题讨论】:
您可以添加另一个:import collections as co; c = co.Counter('this is a good question')
为什么不使用完全导入? typing.Counter 和 Collections.Counter 很容易区分并提供更好的可读性。
@MortenB 我在教育环境中需要这个,与刚开始学习编程的学生一起。这似乎是一种不必要的复杂化,尤其是因为其他类型不需要这种方法。
【参考方案1】:
可能有一些更高级的情况这不起作用,但您可以创建一个继承自两者的子类:
import typing
import collections
KT = typing.TypeVar("KT")
class Counter(collections.Counter, typing.Counter[KT]): pass
c: Counter[str] = Counter("foo")
print(isinstance(c, dict)) # True
【讨论】:
但是你不想把这个扔给初级程序员,因为其他类型的东西不是问题。 @TomVerhoeff 如果我正在设计它,我根本不会有单独的typing
类,但我想有一些令人信服的技术原因是不可能的。【参考方案2】:
最小的痛苦可能是满足于基于字符串的类型提示。然而,并不是每个 IDE 都会解释它。 PyCharm 在这个 sn-p 的最后一行检测到类型不匹配,而 VSCode 认为一切正常。
from collections import Counter
mycounter: 'Counter[str]' = Counter('foo')
def func1(counter: 'Counter[str]'):
pass
def func2(counter: 'Counter[int]'):
pass
func1(mycounter)
func2(mycounter)
【讨论】:
当然是个好建议,这将使您有一个统一的方法。但它相当普遍,不是主流,在教学中它会让人觉得做作和不必要。【参考方案3】:从 Python 3.9 开始,您可以:
from collections import Counter
c: Counter[str] = Counter()
见:https://docs.python.org/3/library/typing.html#typing.Counter
3.9 版后已弃用:collections.Counter 现在支持 []。请参阅 PEP 585 和通用别名类型。
【讨论】:
以上是关于如何处理名称冲突 collections.Counter 和 typing.Counter?的主要内容,如果未能解决你的问题,请参考以下文章