Новая подборка советов про 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()
ссылка на оригинал статьи https://habr.com/ru/company/mailru/blog/466315/
Добавить комментарий