Как бороться с именем cla sh collection.Counter и typing.Counter? - PullRequest
5 голосов
/ 02 мая 2020

Имя Counter определяется как в collections (как класс), так и в typing (как универсальное c имя типа). К сожалению, они немного отличаются. Каков рекомендуемый способ борьбы с этим?

Сходства и различия:

  1. После from collections import Counter,

    1. вы можете позвонить конструктор Counter("foo") для создания объекта fre sh Counter;
    2. вы можете убедиться, что он является подклассом dict: issubclass(Counter, dict) возвращает True;
    3. , который вы не можете используйте его для объявления определенного c варианта Counter, например, cnt: Counter[str] = Counter("foo") повышает TypeError: 'type' object is not subscriptable (подсказка типа не выполняется)
  2. После from typing import Counter,

    1. Вы можете вызвать конструктор Counter("foo") для создания объекта fre sh Counter (на самом деле, к моему удивлению);
    2. вы не можете использовать его, чтобы убедиться, что он является подклассом dict: issubclass(Counter, dict) повышает TypeError: issubclass() arg 1 must be a class;
    3. Вы можете объявить определенный c вариант 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

(Обратите внимание, что порядок имеет значение.)

Что если вы хотите все это иметь? Вот варианты, которые я вижу:

  1. Do
from collections import Counter
import typing

и использование typing.Counter для достижения 1,3. Не красиво, слишком многословно.

Выполните
import collections
from typing import Counter

и используйте collections.Counter для достижения 2,2 (при необходимости; мне это нужно было в обучении).

Do
from collections import Counter as counter
from typing import Counter

и использование counter для достижения 2.2.

Do
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

и используйте

  1. либо co.Counter или Counter в качестве конструктора
  2. используйте co.Counter в качестве класса, например, issubclass(co.Counter, dict)
  3. используйте Counter в подсказках типа, например, cnt: Counter[str]

Это также рекомендуется сделать

from typing import Deque

и использовать Deque в качестве конструктора, а не co.deque? (Я бы подумал / надеюсь, что нет.)

Для других типов (таких как defaultdict и deque) это, похоже, не проблема:

from collections import defaultdict, deque
from typing import DefaultDict, Deque

дает вам все.

Я что-то пропускаю?

Ответы [ 2 ]

0 голосов
/ 02 мая 2020

Наименьшее количество боли может состоять в том, чтобы согласиться на подсказку на основе строки. Однако не каждая IDE будет интерпретировать это; PyCharm обнаруживает несоответствие типов в последней строке этого фрагмента, а 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)
0 голосов
/ 02 мая 2020

Могут быть и более сложные случаи, для которых это не работает, но вы можете создать подкласс, который наследует от обоих:

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
...