
Новая подборка советов про Python и программирование из моего авторского канала @pythonetc.
< Предыдущие подборки

Если у экземпляра класса нет атрибута с заданным именем, то он пытается обратиться к атрибуту класса с тем же именем.
>>> class A:
...     x = 2
...
>>> A.x
2
>>> A().x
2Экземпляр легко может иметь атрибут, которого нет у класса, или иметь атрибут с другим значением:
>>> class A:
...     x = 2
...     def __init__(self):
...         self.x = 3
...         self.y = 4
...
>>> A().x
3
>>> A.x
2
>>> A().y
4
>>> A.y
AttributeError: type object 'A' has no attribute 'y'Если же вы хотите, чтобы экземпляр вёл себя так, словно у него нет атрибута, хотя он есть у класса, то придётся создать кастомный дескриптор, который запрещает обращаться из этого экземпляра:
class ClassOnlyDescriptor:
    def __init__(self, value):
        self._value = value
        self._name = None  # see __set_name__
    def __get__(self, instance, owner):
        if instance is not None:
            raise AttributeError(
                f'{instance} has no attribute {self._name}'
            )
        return self._value
    def __set_name__(self, owner, name):
        self._name = name
class_only = ClassOnlyDescriptor
class A:
    x = class_only(2)
print(A.x)  # 2
A().x       # raises AttributeError
См. также, как работает Django-декоратор
classonlymethod: https://github.com/django/django/blob/b709d701303b3877387020c1558a590713b09853/django/utils/decorators.py#L6
Функциям, объявленным в теле класса, область видимости этого класса недоступна. Это сделано потому, что эта область видимости существует только в ходе создания класса.
>>> class A:
...     x = 2
...     def f():
...         print(x)
...     f()
...
[...]
NameError: name 'x' is not definedОбычно это не является проблемой: методы объявляются внутри класса только для того, чтобы стать методами и быть вызванными позднее:
>>> class A:
...     x = 2
...     def f(self):
...         print(self.x)
...
>>>
>>>
>>> A().f()
2Удивительно, но то же самое верно и для comprehensions. У них собственные области видимости и они тоже не имеют доступа к областям видимости классов. Это очень логично с точки зрения generator comprehensions: код в них исполняется, когда класс уже создан.
>>> class A:
...     x = 2
...     y = [x for _ in range(5)]
...
[...]
NameError: name 'x' is not definedОднако у comprehensions нет доступа к
self. Единственный способ обеспечить доступ к x заключается в добавлении ещё одной области видимости (ага, дурацкое решение):>>> class A:
...     x = 2
...     y = (lambda x=x: [x for _ in range(5)])()
...
>>> A.y
[2, 2, 2, 2, 2]

В Python
None эквивалентно None, так что может показаться, что проверять на None можно с помощью ==:ES_TAILS = ('s', 'x', 'z', 'ch', 'sh')
def make_plural(word, exceptions=None):
    if exceptions == None:  # < < <
        exceptions = {}
    if word in exceptions:
        return exceptions[word]
    elif any(word.endswith(t) for t in ES_TAILS):
        return word + 'es'
    elif word.endswith('y'):
        return word[0:-1] + 'ies'
    else:
        return word + 's'
exceptions = dict(
    mouse='mice',
)
print(make_plural('python'))
print(make_plural('bash'))
print(make_plural('ruby'))
print(make_plural('mouse', exceptions=exceptions))
Но это будет ошибкой. Да,
None равно None, но не только оно. Пользовательские объекты тоже могут быть равны None:>>> class A:
...     def __eq__(self, other):
...             return True
...
>>> A() == None
True
>>> A() is None
FalseЕдинственный правильный способ сравнения с
None заключается в использовании is None.
Числа с плавающей запятой в Python могут иметь значения NaN. Например, такое число можно получить с помощью
math.nan. nan не равно ничему, включая себя:>>> math.nan == math.nan
FalseКроме того, NaN-объект не уникален, у вас может быть несколько разных NaN-объектов из разных источников:
>>> float('nan')
nan
>>> float('nan') is float('nan')
FalseЭто означает, что, в целом, вы не можете использовать NaN в качестве ключа словаря:
>>> d = {}
>>> d[float('nan')] = 1
>>> d[float('nan')] = 2
>>> d
{nan: 1, nan: 2}

typing позволяет определять типы для генераторов. Дополнительно можно указать, какой тип генерируется, какой передаётся генератору и какой возвращается с помощью return. Например, Generator[int, None, bool] генерирует целые числа, возвращает булевы и не поддерживает g.send().А вот пример посложнее.
chain_while проксирует данные от других генераторов до тех пор, пока один из них не вернёт значение, которое является сигналом остановки в соответствии с функцией condition:from typing import Generator, Callable, Iterable, TypeVar
Y = TypeVar('Y')
S = TypeVar('S')
R = TypeVar('R')
def chain_while(
    iterables: Iterable[Generator[Y, S, R]],
    condition: Callable[[R], bool],
) -> Generator[Y, S, None]:
    for it in iterables:
        result = yield from it
        if not condition(result):
            break
def r(x: int) -> Generator[int, None, bool]:
    yield from range(x)
    return x % 2 == 1
print(list(chain_while(
    [
        r(5),
        r(4),
        r(3),
    ],
    lambda x: x is True,
)))

Задать аннотации для фабричного метода не так просто, как может показаться. Сразу хочется использовать нечто подобное:
class A:
    @classmethod
    def create(cls) -> 'A':
        return cls()Но это будет неправильно. Хитрость в том, что
create возвращает не A, он возвращает cls, который является A или одним из его потомков. Взгляните на код:class A:
    @classmethod
    def create(cls) -> 'A':
        return cls()
class B(A):
    @classmethod
    def create(cls) -> 'B':
        return super().create()Результатом проверки mypy является ошибка
error: Incompatible return value type (got "A", expected "B"). Повторюсь, проблема в том, что super().create() аннотирован как возвращающий A, хотя в этом случае он возвращает B.Это можно исправить, если аннотировать
cls с помощью TypeVar:AType = TypeVar('AType')
BType = TypeVar('BType')
class A:
    @classmethod
    def create(cls: Type[AType]) -> AType:
        return cls()
class B(A):
    @classmethod
    def create(cls: Type[BType]) -> BType:
        return super().create()Теперь
create возвращает экземпляр класса cls. Однако эти аннотации слишком расплывчаты, мы потеряли информацию о том, что cls является подтипом A:AType = TypeVar('AType')
class A:
    DATA = 42
    @classmethod
    def create(cls: Type[AType]) -> AType:
        print(cls.DATA)
        return cls()Получаем ошибку
"Type[AType]" has no attribute "DATA".Чтобы её исправить, нужно явно определить
AType как подтип A. Для этого используется TypeVar с аргументом bound.AType = TypeVar('AType', bound='A')
BType = TypeVar('BType', bound='B')
class A:
    DATA = 42
    @classmethod
    def create(cls: Type[AType]) -> AType:
        print(cls.DATA)
        return cls()
class B(A):
    @classmethod
    def create(cls: Type[BType]) -> BType:
        return super().create()
Комментарии (9)
 - zloklun06.09.2019 13:08- Пример с - nanне воспроизвёлся:
 - >>> import math >>> d = {} >>> d[math.nan] = 1 >>> d[math.nan] = 2 >>> d {nan: 2} >>>
 - Да и вообще, 
 - >>> math.nan is math.nan True
 - ЧЯДНТ? Win10, Python 3.6  - pushtaev Автор06.09.2019 13:09- NaN — не синглтон, math.nan — это просто какой-то один экземпляр, можно легко получить другой. С помощью float('nan'), например. 
 
 - Andylloyds06.09.2019 13:51- По поводу аннотации для фабричного метода — c 3.7 можно использовать 
 - from __future__ import annotations
 И не придётся указывать TypeVar.
 
 PEP563 - pushtaev Автор06.09.2019 13:51- А можно пример?  - Andylloyds06.09.2019 14:00- Вроде как-то так: 
 - from __future__ import annotations from typing import Type class A: DATA = 42 @classmethod def create(cls: Type[A]) -> A: print(cls.DATA) return cls() - pushtaev Автор06.09.2019 14:17- Это позволяет не ставить кавычки вокруг 'A', но вообще никак не относится к описанной проблеме и TypeVar не заменяет. 
 
 
 
 
           
 
Yuego
Аннотации типов помогают делать очень интересные вещи. Довольно давно начал их активно использовать, но вот с TypeVar познакомился совсем недавно.
А еще аннотации можно использовать не только по прямому назначению — для типизации.
Например, они мне очень пригодились при построении одного из моих велосипедов — маппера из JSON в объекты Python. По началу я использовал для описания структур обычный dict, но пострадало удобство — IDE ничего не знает о полях соответствующих объектов. Привет опечатки и постоянное заглядывание в описания объектов. Тут-то на помощь и пришли аннотации и немного нестандартное их использование. И вуаля, IDE подсказывает мне поля и следит за их типами. Если кому интересно, могу рассказать подробнее.
agi1512
Интересно! Рассказывайте