Aspiring Data Science
327 subscribers
390 photos
10 videos
6 files
1.46K links
Заметки экономиста о программировании, прогнозировании и принятии решений, научном методе познания.
Контакт: @fingoldo

I call myself a data scientist because I know just enough math, economics & programming to be dangerous.
Download Telegram
#python #codegems

Для экономии памяти избегайте создания атрибутов экземпляров вне метода init. По умолчанию Python хранит атрибуты экземпляров в специальном атрибуте dict, который представляет собой словарь, являющийся принадлежностью экземпляра. С тех пор как в версии Python 3.3 было реализовано предложение PEP 412 «Key-Sharing Dictionary», экземпляры класса могут разделять общую хеш-таблицу, хранящуюся в самом классе. На эту общую хеш-таблицу ссылаются атрибуты dict каждого нового экземпляра, имеющего такие же имена атрибутов, что и первый экземпляр этого класса, возвращенный init.

Тогда dict в каждом экземпляре может хранить только собственные значения атрибутов в виде простого массива указателей. Добавление атрибута экземпляра после возврата из init заставляет Python создать новую хеш-таблицу для хранения dict только одного этого экземпляра (такое поведение подразумевалось по умолчанию для всех экземпляров до версии Python 3.3). Согласно PEP 412, эта оптимизация сокращает потребление памяти в объектно-ориентированных программах на 10–20%.

Если вы хотите устранить дубликаты, сохранив при этом порядок первого вхождения каждого элемента, можете воспользоваться простым словарем dict:
>>> dict.fromkeys(l).keys()
dict_keys(['spam', 'eggs', 'bacon'])

dict_keys и dict_items реализуют специальные методы для поддержки операторов над множествами & (пересечение), | (объединение), - (разность) и ^ (симметрическая разность). Например, с помощью & легко получить ключи, встречающиеся в двух словарях:
>>> d1 = dict(a=1, b=2, c=3, d=4)
>>> d2 = dict(b=20, d=40, e=50)
>>> d1.keys() & d2.keys()
{'b', 'd'}


Представление dict_items работает как множество, только если все значения в словаре допускают хеширование. Попытка применить операцию над множествами к представлению dict_items с нехешируемыми значениями приводит к исключению TypeError: unhashable type 'T', где T – тип недопустимого значения. С другой стороны, представление dict_keys всегда можно использовать как множество, потому что все ключи словаря являются хешируемыми по определению. Применение операторов множества к представлениям позволяет отказаться от многочисленных циклов и условных предложений при исследовании содержимого словарей. Заставьте эффективную реализацию Python, написанную на C, работать для вас!

Грег Гандербергер выступает за то, чтобы и дальше использовать класс collections.OrderedDict на том основании, что «явное лучше неявного», а также ради обратной совместимости и потому что некоторые инструменты и библиотеки предполагают, что порядок ключей в dict несуществен.

Истоки JSON следует искать в JavaScript, но так получилось, что это почти точное подмножество синтаксиса Python. JSON совместим с Python во всем, кроме написания значений true, false и null. Армин Ронахер пишет, что ему понравилось «подправить» глобальное пространство имен Python, добавив совместимые с JSON псевдонимы для символов Python True, False и None, так что теперь он может копировать JSON прямо на консоль. Вот как выглядит его идея:
>>> true, false, null = True, False, None
>>> fruit = {
... "type": "banana",
... "avg_weight": 123.2,
... "edible_peel": false,
... "species": ["acuminata", "balbisiana", "paradisiaca"],
... "issues": null,
... }
>>> fruit
{'type': 'banana', 'avg_weight': 123.2, 'edible_peel': False,
'species': ['acuminata', 'balbisiana', 'paradisiaca'], 'issues':
None}


Калеб Хэттинг предлагает всегда использовать кодек UTF-8-SIG при чтении файлов в кодировке UTF-8. Это безвредно, потому что UTF-8-SIG правильно читает файлы с BOM и без BOM и не возвращает сам символ BOM.

Код, который должен работать на разных машинах или в разных ситуациях, не должен зависеть от кодировки по умолчанию. Всегда явно задавайте аргумент encoding= при открытии текстовых файлов, потому что умолчания могут зависеть от машины и даже меняться на одной и той же машине.
Please open Telegram to view this post
VIEW IN TELEGRAM
#python #codegems

Если используется @dataclass, то аннотации типов перестают быть факультативными. Последние семь лет, с момента выхода документа PEP 484 «Type Hints», нам обещали, что аннотации всегда будут необязательными. А теперь мы имеем важное новое средство языка, которое без них работать не может. Если вам не нравится весь этот уклон в сторону статической типизации, то можете вместо этого пользоваться attrs.

Синтаксис, предлагаемый в PEP 526 для аннотирования атрибутов класса и экземпляра, выворачивает наизнанку установившееся соглашение о предложениях class: все, что объявлено на верхнем уровне блока class, является атрибутом класса (методы – тоже атрибуты класса). А в PEP 526 и @dataclass всякий атрибут, объявленный на верхнем уровне с аннотацией типа, становится атрибутом экземпляра:
@dataclass
class Spam:
repeat: int # атрибут экземпляра


Но если аннотаций типов нет, то мы внезапно возвращаемся в старые добрые времена, когда объявления, находящиеся на верхнем уровне класса, принадлежали самому классу, а не его экземплярам:
@dataclass
class Spam:
repeat = 99 # атрибут класса!

Наконец, если мы хотим аннотировать атрибут класса типом, то не можем использовать регулярные типы, потому что тогда получим атрибут экземпляра. И приходится прибегать к аннотации псевдотипа ClassVar:
@dataclass
class Spam:
repeat: ClassVar[int] = 99 # ррррр!
#python #codegems

Для списков и других изменяемых последовательностей присваивание l2 = l1[:] также создает копию. Однако при использовании конструктора и оператора [:] создается поверхностная (shallow) копия (т. е. дублируется только самый внешний контейнер, который заполняется ссылками на те же элементы, что хранятся в исходном контейнере). Это экономит память и не создает проблем, если все элементы неизменяемые. Однако при наличии изменяемых элементов можно столкнуться с неприятными сюрпризами.

Слабые ссылки на объект не увеличивают счетчик ссылок. Таким образом, слабая ссылка не препятствует уничтожению объекта ссылки сборщиком мусора. Слабые ссылки полезны для кеширования, поскольку мы не хотим, чтобы кешированный объект оставался жив только потому, что на него ссылается сам кеш.

Для кортежа t конструкция t[:] не создает копию, а возвращает ссылку на сам объект. Ссылку на исходный кортеж мы получаем также, написав tuple(t). Такое же поведение свойственно экземплярам классов str, bytes и frozenset. frozenset – не последовательность, поэтому, когда fs является объектом frozenset, конструкция fs[:] не работает. Но fs.copy() дает точно такой же эффект: обманывает нас и возвращает ссылку на тот же объект, а вовсе не на его копию.

В некоторых ситуациях полезно иметь ссылку, которая сама по себе не удерживает объект «в мире живых». Примером может служить класс, желающий отслеживать все свои экземпляры. Это можно сделать с помощью слабых ссылок – низкоуровневого механизма, на базе которого построены более полезные коллекции WeakValueDictionary, WeakKeyDictionary, WeakSet и функция finalize – все из модуля weakref.

На самом деле тип объекта тоже можно изменить, просто присвоив другой класс его атрибуту class, но это неприкрытое зло.

Функцией высшего порядка называется функция, которая принимает функцию в качестве аргумента или возвращает в качестве значения. Примером может служить функция map. Другой пример – встроенная функция sorted: ее необязательный аргумент key позволяет задать функцию, которая применяется к каждому сортируемому элементу.

Если в классе определен метод call, то его экземпляры можно вызывать как функции. Обычно при вызове класса создается экземпляр именно этого класса, но такое поведение можно изменить, переопределив метод new.

Класс, в котором реализован метод call, – простой способ создать похожий на функцию объект, обладающий внутренним состоянием, которое должно сохраняться между вызовами. Хороший пример – декоратор. Декоратор должен быть вызываемым объектом, и иногда удобно иметь возможность «запоминать» что-то между вызовами декоратора (например, в случае кеширования результатов длительных вычислений для последующего использования) или разбить сложную реализацию на несколько методов.

Стремление к стопроцентному покрытию аннотациями типов означает, что вы применяете аннотирование без ясной цели, просто ради красивых показателей. К тому же это будет мешать команде добиться максимума от мощи и гибкости Python. Код без аннотаций типов должен без колебаний приниматься, если аннотации сделали бы API менее удобным для пользователя или без нужды усложнили бы реализацию.

Если факультативный параметр имеет изменяемый тип, то None – единственно разумное значение по умолчанию

К работе с утиной типизацией проще приступить и она обладает большей гибкостью, но возможность, что неподдерживаемые операции приведут к ошибке во время выполнения, остается. Номинальная типизация обнаруживает ошибки до начала выполнения, но иногда отвергает код, который работал бы правильно.
#python #codegems

Any – необычный тип, который располагается и в самом верху, и в самом низу иерархии типов. Это одновременно и самый общий тип (т. е. аргумент n: Any принимает значения любого типа), и самый специализированный, т. е. поддерживает любую операцию. По крайней мере, именно так средство проверки типов воспринимает Any. Разумеется, никакой тип не может поддерживать все возможные операции, поэтому использование Any лишает средство проверки типов возможности выполнить свою основную миссию: обнаруживать потенциально некорректные операции до того, как программа аварийно завершится с исключением во время выполнения.

Начиная с версии Python 3.10 мы можем писать str | bytes вместо Union[str, bytes]. Это короче и не нужно импортировать Optional и Union из модуля typing. Сравните старый и новый синтаксисы аннотаций типов для параметра plural функции show_count:
plural: Optional[str] = None # до
plural: str | None = None # после
Оператор | можно также применять в сочетании с isinstance и issubclass для построения второго аргумента: isinstance(x,int | str).

Есть функции, которые принимают аргументы типа str или bytes, но возвращают str, если аргумент имел тип str, и bytes, если он имел тип bytes. В таких случаях тип возвращаемого значения определяется типом входа, поэтому Union – не совсем подходящее решение. Чтобы правильно аннотировать подобные функции, нам нужна переменная-тип TypeVar.

Чтобы включить в аннотацию кортеж неопределенной длины, используемый как неизменяемый список, необходимо задать один тип, за которым следуют запятая и многоточие ...
Например, tuple[int, ...] – кортеж, состоящий из элементов типа int. Многоточие означает, что допустимо любое число элементов >= 1. Не существует способа задать поля разных типов в кортежах произвольной длины.

Аннотации stuff: tuple[Any, ...] и stuff: tuple означают одно и то же: stuff является кортежем неопределенной длины, содержащим объекты любого типа.

В общем случае в аннотациях типов параметров лучше использовать abc.Mapping или abc.MutableMapping, а не dict (или typing.Dict в унаследованном коде).

Идея специального типа TypeAlias в том, чтобы сделать создаваемые псевдонимы типов хорошо видимыми и упростить для них проверку типов. Начиная с версии Python 3.10 это рекомендуемый способ создания псевдонимов типов:

typing.Protocol дает нам статическую утиную типизацию.

Тип NoReturn - это специальный тип, использующийся только для аннотирования типов возвращаемых значений функций, которые вообще не возвращают управления. Обычно они существуют, чтобы возбуждать исключение. В стандартной библиотеке десятки таких функций. Например, функция sys.exit() возбуждает исключение SystemExit, чтобы завершить процесс Python.
#python #codegems

Нотация / для чисто позиционных параметров появилась в версии Python 3.8. В Python 3.7 и более ранних версиях она приводит к синтаксической ошибке. В документе PEP 484 предлагается соглашение: имя любого чисто позиционного параметра начинать с двух знаков подчеркивания.

Переменные-типы (TypeVar) повышают гибкость аннотаций типов, не жертвуя типобезопасностью. Параметризованные обобщенные типы становятся еще более выразительными при использовании протоколов. Тип Protocol появился только в версии Python 3.8 и пока еще не получил широкого распространения, но его важность невозможно переоценить. Protocol открывает возможность для статической утиной типизации, мосту между ядром Python, основанным на утиной типизации, и номинальной типизацией, позволяющей средствам статической проверки типов отлавливать ошибки.

Во время экспериментов тесты и аннотации типов не особенно полезны. Они только тормозят работу.

Если кодирование – не ваша профессия, а лишь полезный инструмент, применяемый в профессиональных занятиях, или нечто такое, что вы изучаете ради удовольствия, то, пожалуй, аннотации типов нужны вам не больше, чем жесткие ботинки и металлические набойки большинству велосипедистов. Просто кодируйте.

Одно из самых плохо понимаемых зарезервированных слов в Python – nonlocal, оно появилось в версии Python 3.0. Программист на Python может безбедно существовать, и не используя его, если будет строго придерживаться объектно-ориентированной диеты, основанной на классах. Но если вы захотите реализовать собственные декораторы функций, то должны досконально разбираться в замыканиях, а тогда потребность в слове nonlocal становится очевидной.

Декораторы функций выполняются сразу после импорта модуля.

Замыкание (closure) – это функция, которая запоминает привязки свободных переменных, существовавшие на момент определения функции, так что их можно использовать впоследствии при вызове функции, когда область видимости, в которой она была определена, уже не существует. Отметим, что единственная ситуация, когда функции может понадобиться доступ к внешним неглобальным переменным, – это когда она вложена в другую функцию и эти переменные являются частью локальной области видимости внешней функции.

Преимущество @singledispath – в поддержке модульного расширения: каждый модуль может зарегистрировать специализированную функцию для того типа, который поддерживает.

Леннарт Регебро считает, что декораторы лучше писать как классы, реализующие метод call, а не как функции.
import time
DEFAULT_FMT = '[{elapsed:0.8f}s] {name}({args}) -> {result}'
class clock:
def __init__(self, fmt=DEFAULT_FMT):
self.fmt = fmt
def __call__(self, func):
def clocked(*_args):
t0 = time.perf_counter()
_result = func(*_args) 
elapsed = time.perf_counter() - t0
name = func.__name__
args = ', '.join(repr(arg) for arg in _args)
result = repr(_result)
print(self.fmt.format(**locals()))
return _result
return clocked


В некоторых случаях функции могут проделать ту же работу, что и классы, а код при этом получается проще и короче.

По определению, инструменты статического анализа не способны понять динамическую структуру Python. Если тупо следовать каждому их совету, то мы начнем писать угрюмый и многословный код а-ля Java с синтаксисом Python.

Декоратор classmethod, очевидно, полезен, но мне очень редко встречались хорошие примеры употребления staticmethod. Быть может, функция тесно связана с классом, хотя и не залезает в его «потроха», так что лучше разместить ее код поблизости. Но даже если так, размещение функции сразу до или после класса в том же модуле – это достаточно близко для любых практических целей.

В модулях одиночный подчерк в начале имени верхнего уровня имеет специальный смысл: если написать from mymod import *, то имена с префиксом _ не будут импортироваться из mymod. Но ничто не мешает явно написать from mymod import _privatefunc.
#python #codegems

Метод getattr вызывается интерпретатором, если поиск атрибута завершается неудачно. Иначе говоря, анализируя выражение my_obj.x, Python проверяет, есть ли у объекта my_obj атрибут с именем x; если нет, поиск повторяется в классе (my_obj.class), а затем вверх по иерархии наследования1. Если атрибут x все равно не найден, то вызывается метод getattr, определенный в классе my_obj, причем ему передается self и имя атрибута в виде строки (например, 'x').

При использовании reduce рекомендуется задавать третий аргумент, reduce(function, iterable, initializer), чтобы предотвратить появление исключения TypeError: reduce() of empty sequence with no initial value (отличное сообщение: описывается проблема и способ исправления).

zip останавливается, не выдавая предупреждения, как только один из итерируемых объектов оказывается исчерпанным. Функция itertools.zip_longest ведет себя иначе: она подставляет вместо отсутствующих значений необязательный аргумент fillvalue (по умолчанию None), поэтому генерирует кортежи, пока не окажется исчерпанным самый длинный итерируемый объект.В документе PEP 618 «Add Optional Length-Checking To zip» предложено добавить в zip факультативный аргумент strict, при наличии которого она ведет себя именно таким образом. Предложение реализовано в Python 3.10.

Функцию zip можно также использовать для транспонирования матрицы, представленной в виде вложенных итерируемых объектов. Например:
>>> a = [(1, 2, 3),
... (4, 5, 6)]
>>> list(zip(*a))
[(1, 4), (2, 5), (3, 6)]


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

В динамически типизированном языке принцип быстрого отказа – прекрасный совет по созданию более безопасных и удобных для сопровождения программ. Быстрый отказ означает, что нужно возбудить исключение как можно раньше, например отвергать недопустимые аргументы в самом начале тела функции.
#python #codegems

Гусиная типизация означает следующее: вызов isinstance(obj, cls) теперь считается приемлемым… при условии, что cls – абстрактный базовый класс, т. е.
метаклассом cls является abc.ABCMeta.

>>> class Struggle:
... def len(self): return 23
...
>>> from collections import abc
>>> isinstance(Struggle(), abc.Sized)
True
Как видим, abc.Sized распознал Struggle как свой «подкласс» безо всякой регистрации просто потому, что для этого необходимо только наличие специального метода len.

Пусть имеется класс FrenchDeck, и я хочу проверять его тип следующим образом: issubclass(FrenchDeck, Sequence). Для этого я могу сделать его виртуальным подклассом ABC Sequence:
from collections.abc import Sequence
Sequence.register(FrenchDeck)

Обычно не должно быть цепочек предложений if/elif/elif, в которых с помощью insinstance определяется тип объекта и в зависимости от него выполняются те или иные действия; для этой цели следует использовать полиморфизм, т. е. проектировать классы, так чтобы интерпретатор сам вызывал правильные методы, а не «зашивать» логику диспетчеризации в блоки if/elif/elif.
С другой стороны, нет возражений против использования insinstance для сравнения с типом ABC, если требуется убедиться в соблюдении контракта: «Эй, чтобы меня вызывать, ты должен реализовать то-то и то-то». Особенно это полезно в системах, основанных на архитектуре плагинов.

Если insinstance(obj, Hashable) возвращает False, то можно точно сказать, что obj не хешируемый. Но если она возвращает True, то возможен ложноположительный результат.

Единственно надежный способ определить, является ли объект итерируемым, – вызвать iter(obj).

Использование ABC вместо конкретных типов для аннотирования типов аргументов функции дает больше гибкости вызывающей стороне.

У абстрактного метода может существовать реализация. Но даже если так, подклассы все равно обязаны переопределить его, однако имеют право вызывать абстрактный метод с помощью функции super(), расширяя имеющуюся функциональность, вместо того чтобы реализовывать ее с нуля.

Вот как рекомендуется объявлять абстрактный метод класса:
class MyABC(abc.ABC):
@classmethod
@abc.abstractmethod
def an_abstract_classmethod(cls, ...):
pass


Если abstractmethod() применяется в сочетании с другими дескрипторами метода, он должен быть самым внутренним декоратором.

Важнейшая характеристика гусиной типизации, благодаря которой она и заслужила «водоплавающее» имя, – возможность регистрировать класс как виртуальный подкласс ABC, даже без наследования. При этом мы обещаем, что класс честно реализует интерфейс, определенный в ABC, а Python верит нам на слово, не производя проверку. Если мы соврем, то будем наказаны исключением во время выполнения. Это делается путем вызова метода register абстрактного базового класса. В результате зарегистрированный класс становится виртуальным подклассом ABC и распознается в качестве такового функцией issubclass, однако не наследует ни методы, ни атрибуты ABC.

Несмотря на наличие формального определения интерфейса в ABC и скрупулезных проверок, осуществляемых функцией isinstance, в определенных контекстах вполне можно использовать никак не связанный с ABC класс просто потому, что в нем реализован определенный метод (или потому, что он постарался убедить subclasshook, что за него можно поручиться).

Удивительно, но тип decimal.Decimal не зарегистрирован как виртуальный подкласс numbers.Real. Причина в том, что если в программе нужна точность Decimal, то, наверное, вы предпочли бы застраховаться от случайного смешения десятичных чисел и менее точных чисел с плавающей точкой.

Множественное наследование – это способность класса иметь более одного базового класса. C++ его поддерживает, а Java и C# нет. Многие считают, что множественное наследование порождает больше проблем, чем решает. Оно было сознательно изъято из Java после печального опыта злоупотребления в ранних кодовых базах на C++.
#python #codegems

Прямое наследование таким встроенным типам, как dict, list или str, чревато ошибками, потому что встроенные методы, как правило, игнорируют написанные пользователем переопределенные методы. Вместо создания подклассов встроенных объектов наследуйте свои классы от классов в модуле collections – UserDict, UserList и UserString, которые специально предназначены для беспроблемного наследования.

Порядок MRO принимает в расчет не только граф наследования, но также порядок, в котором перечислены суперклассы в объявлении подкласса.

Класс-примесь (mixin) предназначен для того, чтобы ему наследовали вместе с еще хотя бы одним классом при организации множественного наследования. Примесь не должна быть единственным базовым классом конкретного класса, потому что не предоставляет полную функциональность конкретного объекта,а лишь добавляет или настраивает поведение классов-потомков или братьев.

Иногда лучше проявить разумный прагматизм и оставить часть кода неаннотированной.

Начиная с Python 3.10 следует использовать новую функцию inspect.get_annotations(...), а не typing.get_type_hints.

Наличие перегрузки операторов в удобном для работы языке высокого уровня, возможно, и было одной из основных причин поразительного успеха Python как языка обработки данных, в т. ч. для создания финансовых и научных приложений.

Начиная с версии Python 3.10 встроенная функция zip принимает факультативный чисто именованный аргумент strict. Если strict=True, то функция возбуждает исключение ValueError, когда итерируемые объекты имеют разную длину. По умолчанию strict равен False.

В версии Python 3.10 самый точный способ проверить, является ли объект x итерируемым, – вызвать iter(x) и перехватить исключение TypeError, если оно возникнет. Это надежнее, чем использовать isinstance(x, abc.Iterable), потому что iter(x) учитывает также доставшийся в наследство метод getitem, а ABC Iterable этого не делает.

Явно проверять, является ли объект итерируемым, вряд ли стоит, если сразу после проверки вы намереваетесь обойти объект. Ведь если попытаться обойти неитерируемый объект, Python возбудит исключение с недвусмысленным сообщением: TypeError: 'C' object is not iterable. Если вы можете сделать что-то более разумное, чем возбуждать TypeError, делайте это в блоке try/except, а не путем явной проверки. Явная проверка, возможно, имеет смысл, если вы хотите сохранить объект и воспользоваться им для итерирования позже; в таком случае раннее обнаружение ошибки упрощает отладку.

Предложение return в теле генераторной функции приводит к тому, что объект-генератор возбуждает исключение StopIteration. Если написать return x в генераторе, то вызывающая сторона сможет получить значение x из исключения StopIteration, но обычно это делается автоматически с помощью синтаксиса yield from.

Реализуя генераторы, нужно знать, что уже есть в стандартной библиотеке, иначе велики шансы изобрести велосипед.

Функции chain и chain.from_iterable обходят входные итерируемые объекты последовательно (один за другим), а product, zip и zip_longest – параллельно.
#python #codegems

Если субгенератор содержит предложение return, возвращающее значение, то это значение может быть перехвачено делегирующим генератором, если он включит конструкцию yield from в состав выражения.

Генераторы, способные потреблять и возвращать значения, называются сопрограммами.

Генераторы порождают данные для итерирования; сопрограммы являются потребителями данных.

Как аннотировать генератор:
from collections.abc import Generator
def averager() -> Generator[float, float, None]:
total = 0.0
count = 0
average = 0.0
while True:
term = yield average
total += term
count += 1
average = total / count


В этом бесконечном цикле сопрограмма будет отдавать средние, пока клиентский код посылает значения. Здесь предложение yield используется, чтобы приостановить сопрограмму, отдать результат вызывающей стороне и – впоследствии – получить значение, посланное вызывающей стороной, после чего выполнение бесконечного цикла продолжится.
>>> coro_avg = averager()
>>> next(coro_avg)
0.0
>>> coro_avg.send(10)
10.0
>>> coro_avg.send(30)
20.0
>>> coro_avg.send(5)
15.0


В этом тесте вызов next(coro_avg) заставляет сопрограмму дойти до yield, при этом будет отдано начальное значение average. Запустить сопрограмму можно также, вызвав coro_avg.send(None), – именно так и поступает встроенная функция next(). Но отправить какое-то значение, кроме None, нельзя, потому что сопрограмма может принимать отправленные значения, только когда приостановлена в точке yield. Вызов next() или .send(None), чтобы продвинуть выполнение к первому предложению yield, называется «инициализацией сопрограммы».

Сопрограммы являются привлекательной альтернативой обратным вызовам при асинхронном программировании – они сохраняют локальное состояние между активациями.

Обычно нам не нужно завершать генератор, потому что сборщик мусора позаботится о нем, как только на него не останется ни одной ссылки. Если все-таки необходимо завершить генератор явно, воспользуйтесь методом .close().

Я без колебаний пишу # type: ignore, чтобы не натыкаться на ограничения и помехи со стороны средств статической проверки типов и не делать код хуже или сложнее, только чтобы порадовать инструмент.

Предложение with было задумано для того, чтобы упростить конструкцию try/finally, гарантирующую, что некоторая операция будет выполнена после блока, даже если этот блок прерван в результате исключения, предложения return или вызова sys.exit(). Код внутри части finally обычно освобождает критически важный ресурс или восстанавливает временно измененное состояние.

Тонкий, но важный момент: объект контекстного менеджера – это результат вычисления выражения после слова with, но значение, связанное с переменной в части as, – результат вызова метода enter объекта контекстного менеджера.

Когда поток управления покидает блок with любым способом, вызывается метод exit контекстного менеджера, а не объекта, возвращенного методом enter.

Реальные приложения, перехватывающие стандартный вывод, обычно хотят временно подменить sys.stdout похожим на файл объектом, а затем восстановить исходное состояние. Именно это делает контекстный менеджер contextlib.redirect_stdout:
просто передайте ему похожий на файл объект, который подменит sys.stdout.

Декоратор @contextmanager – элегантный и практичный инструмент, объединяющий три разных средства Python: декоратор функции, генератор и предложение with. Использование @contextmanager уменьшает объем стереотипного кода создания контекстного менеджера: вместо того чтобы писать целый класс с методами enter/exit, мы просто реализуем генератор с одним предложением yield, порождающим значение, которое должен вернуть метод enter. Если генератор снабжен декоратором @contextmanager, то yield разбивает тело функции на две части: все, что находится до yield, исполняется в начале блока with, когда интерпретатор вызывает метод enter; а все, что находится после yield, выполняется при вызове метода exit в конце блока.
#python #codegems

У декоратора @contextmanager есть одно малоизвестное свойство: снабженные им генераторы сами могут использоваться как декораторы. Это возможно, по тому что @contextmanager реализован с помощью класса contextlib.ContextDecorator.

Недооцененное средство языка: часть else может встречаться не только в предложениях if, но также в for, while и try.Правила таковы:
for
Блок else выполняется, только если цикл for дошел до конца (т. е. не было преждевременного выхода с помощью break).
while
Блок else выполняется, только если цикл while завершился вследствие того, что условие приняло ложное значение (а не в результате выхода с помощью break).
try
Блок else выполняется, только если в блоке try не возникало исключение. В официальной документации также сказано: «Исключения, возникшие в части else, не обрабатываются в предшествующих частях except». В любом случае часть else не выполняется и тогда, когда исключение либо одно из предложений, return, break или continue, приводят к передаче управления вовне главного блока составного предложения.

Одной из причин отказа от else в конструкции match ... было отсутствие единого мнения о том, какой должен быть отступ внутри match: следует ли помещать else на том же уровне, что match, или на том же уровне, что case.

Профессор Имре Саймон говаривал, что в науке есть два главных греха: использование разных слов для обозначения одного и того же предмета и использование одного слова для обозначения разных предметов. Имре Саймон (1943–2009) был пионером информатики в Бразилии. Он внес значительный вклад в теорию автоматов и стоял у истоков тропической математики. Он также отстаивал принципы бесплатного программного обеспечения и свободной культуры вообще.

Чтобы помешать потоку Python удерживать GIL бесконечно, интерпретатор байт-кода Python периодически (по умолчанию раз в 5 миллисекунд) приостанавливает текущий поток и тем самым освобождает GIL. Поток может попытаться снова захватить GIL, но если его ждут другие потоки, то планировщик ОС, возможно, выберет один из них.

Функция sys.getswitchinterval() возвращает текущее значение интервала, а функция sys.setswitchinterval(s) изменяет его.

Любая стандартная библиотечная функция Python, делающая системный вызов, освобождает GIL. Сюда относятся все функции, выполняющие дисковый ввод-вывод, сетевой ввод-вывод, а также time.sleep(). Многие счетные функции в библиотеках NumPy/SciPy, а также функции сжатия и распаковки из модулей zlib и bz2 также освобождают GIL.

Свободные от GIL потоки в общем случае не могут изменять объекты Python, но могут читать и записывать память объектов, поддерживающих протокол буфера, например bytearray, array.array и массивы NumPy.

Вызов сопрограммы как coro() сразу же возвращает объект сопрограммы, но не выполняет тело функции coro. Активация тел сопрограмм – задача цикла событий.

Никогда не используйте time.sleep(…) в сопрограммах asyncio, если не хотите приостановить всю программу в целом. Если сопрограмма хочет потратить некоторое время, ничего не делая, она должна вызвать await asyncio.sleep(DELAY). Так она уступит управление циклу событий asyncio, который может дать поработать другим ожидающим сопрограммам.

По определению, сопрограмма может быть отменена только тогда, когда приостановлена в выражении await, и ничто не мешает произвести очистку, обработав исключение CancelledError.

Название Jupyter происходит от Julia, Python и R – первых трех языков, поддержанных системой Notebook.

Если max_workers равно None (по умолчанию), то ThreadPoolExecutor вычисляет значение по формуле (начиная с версии Python 3.8): max_workers = min(32, os.cpu_count() + 4).

Декоратор @asyncio.coroutine для классических и генераторных сопрограмм был объявлен нерекомендуемым в версии 3.8, а в версии Python 3.11 его планируется исключить из языка.

Функция asyncio.get_running_loop была добавлена в версии Python 3.7 для использования внутри сопрограмм. Начиная с версии Python 3.10 asyncio.get_event_loop объявлена нерекомендуемой.
#python #codegems

Ключевое слово for работает с итерируемыми объектами. А ключевое слово await – с объектами, допускающими ожидание.

В настоящее время asyncio не предоставляет асинхронного API файловой системы – в отличие от Node.js.

Начиная с Python 3.9 сопрограмма asyncio.to_thread упрощает делегирование файлового ввода-вывода пулу потоков, предоставляемому библиотекой asyncio.

Под капотом цикл событий asyncio обращается к .send, чтобы привести в действие ваши сопрограммы, а ваши сопрограммы с помощью await вызывают другие сопрограммы, в т. ч. библиотечные. await заимствует большую часть реализации у предложения yield from, которое также обращается к .send для управления сопрограммами.

Цепочка await в конце концов достигает низкоуровневого объекта, допускающего ожидание, который возвращает генератор, к которому цикл событий может обращаться в ответ на такие события, как срабатывание таймера или сетевой ввод-вывод. Низкоуровневые объекты, допускающие ожидание, и генераторы в конце таких цепочек await находятся глубоко внутри библиотек, они не являются частью их API и могут быть расширениями, написанными на C.

Для достижения максимальной производительности при работе с asyncio мы должны заменить все функции, осуществляющие ввод-вывод, асинхронными версиями, которые активируются в результате выполнения await или asyncio.create_task, для того чтобы управление возвращалось циклу событий, пока функция ждет завершения ввода-вывода.

У asyncpg есть важное достоинство – он позволяет обойти отсутствие в PostgreSQL поддержки высокой конкурентности (в этой СУБД используется один серверный процесс на каждое подключение), поскольку реализует пул подключений для внутреннего подключения к самой Postgres.

Эдсгер Дейкстра изобрел семафор в начале 1960-х годов. Идея простая, но настолько гибкая, что большинство других объектов синхронизации, например блокировки и барьеры, можно построить на основе семафоров. В стандартной библиотеке Python есть три класса Semaphore: по одному в модулях threading,multiprocessing и asyncio.

В классе asyncio.Semaphore имеется внутренний счетчик, который уменьшается на 1 всякий раз, как выполняется await для метода-сопрограммы .acquire(), и увеличивается на 1 при вызове метода .release(), который не является сопрограммой, потому что никогда не блокирует выполнение. Начальное значение счетчика задается при создании объекта Semaphore:
semaphore = asyncio.Semaphore(concur_req)

Ожидание .acquire() не приводит к задержке, когда счетчик больше 0, но если счетчик равен 0, то .acquire() приостанавливает ожидающую сопрограмму до тех пор, пока какая-нибудь другая сопрограмма не вызовет .release() для того же семафора, увеличив тем самым счетчик. Вместо того чтобы обращаться к этим методам напрямую, безопаснее использовать semaphore как асинхронный контекстный менеджер:
async with semaphore:
image = await get_flag(client, base_url, cc)


Стоит написать первое async def, как в программе неизбежно будут появляться все новые и новые async def, await,
async with и async for. И внезапно использование неасинхронных библиотек становится проблемой.

Атрибуты-данные и методы в Python носят общее название «атрибуты»; метод – это просто вызываемый атрибут. Помимо атрибутов-данных и методов,мы можем создавать еще свойства, позволяющие заменить открытые атрибуты-данные методами-акцессорами (т. е. методами чтения и установки), не изменяя интерфейс класса.

Пользовательский класс, в котором имеется метод getattr, может реализовать вариант динамических атрибутов, который я называю виртуальными атрибутами; они не объявлены в исходном коде класса и отсутствуют в экземпляре dict, но могут быть получены из какого-то другого места или вычислены «на лету», когда программа пытается прочитать несуществующий атрибут, например obj.no_such_attr.

Функция keyword.iskeyword(…) позволяет проверить, является ли строка ключевым словом языка.
#python #codegems

Встроенная функция property часто используется как декоратор, но в действительности она является классом. В Python функции и классы нередко взаимозаменяемы, поскольку являются вызываемыми объектами и не существует оператора new для создания объекта, поэтому вызов конструктора ничем не отличается от вызова фабричной функции. Вот полная сигнатура конструктора класса property: property(fget=None, fset=None, fdel=None, doc=None).

Функция vars не умеет работать с классами, в которых определен атрибут slots и нет атрибута dict (в отличие от функции dir, которая справляется с такими экземплярами).
Без аргумента vars() делает то же самое, что locals(): возвращает словарь, описывающий локальную область видимости.

Метод getattr всегда вызывается после getattribute и только в том случае, когда getattribute возбуждает исключение AttributeError. Чтобы при
получении атрибутов obj не возникало бесконечной рекурсии, в реализации getattribute следует использовать super().getattribute(obj, name).

Встроенный тип type на самом деле является метаклассом – классом по умолчанию для определенных пользователем классов.

В способе обработки атрибутов в Python существует важная асимметрия. При чтении атрибута через экземпляр обычно возвращается атрибут, определенный в этом экземпляре, а если такого атрибута в экземпляре не существует, то атрибут класса. С другой стороны, в случае присваивания атрибуту экземпляра обычно создается атрибут в этом экземпляре, а класс вообще никак не затрагивается. Эта асимметрия распространяется и на дескрипторы, в результате чего образуются две категории дескрипторов, различающиеся наличием или отсутствием метода set. Если set присутствует, то класс является переопределяющим дескриптором, иначе непереопределяющим.

Требование явно объявлять self первым аргументом методов – одно из противоречивых проектных решений в Python.Простота – даже элегантность – реализации достигается за счет пользовательского интерфейса: сигнатура метода – def zfill(self, width) – визуально не соответствует его вызову – label.zfill(8).

Метапрограммирование классов – это искусство создания или настройки классов во время выполнения. Классы в Python – полноправные объекты, поэтому функция может в любой момент создать новый класс, не используя ключевое слово class. Декораторы классов – также функции, которые дополнительно умеют инспектировать и изменять декорированный класс и даже заменять его другим. Наконец, метаклассы – самое продвинутое средство метапрограммирования классов: они позволяют создавать целые категории классов со специальными характеристиками.

Обычно мы воспринимаем type как функцию, которая возвращает класс объекта, потому что именно это делает выражение type(my_object): возвращает my_object.class.
Однако type – это класс, который создает новый класс, если вызывается с тремя аргументами. Рассмотрим следующий простой класс:
class MyClass(MySuperClass, MyMixin):
x = 42
def x2(self):
return self.x * 2

С помощью конструктора type мы можем создать MyClass во время выполнения:
MyClass = type('MyClass',
(MySuperClass, MyMixin),
{'x': 42, 'x2': lambda self: self.x * 2},
)

Этот вызов type функционально эквивалентен предыдущему предложению
блока class MyClass.
#python #codegems

Дескрипторы – это способ повторного использования одной и той же логики доступа в нескольких атрибутах. Например, типы полей в объектно-ориентированных отображениях вроде Django ORM и SQL Alchemy – дескрипторы, управляющие потоком данных от полей в записи базы данных к атрибутам Python-объекта и обратно. Дескриптор – это класс, который реализует динамический протокол, содержащий методы get, set и delete. Класс property реализует весь протокол дескриптора.

Пример использования дескриптора:
class Quantity:
def __set_name__(self, owner, name):
self.storage_name = name
def __set__(self, instance, value):
if value > 0:
instance.__dict__[self.storage_name] = value
else:
msg = f'{self.storage_name} must be > 0'
raise ValueError(msg)
# __get__ не нужен
class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price


Глядя на пример, можно подумать, что кода слишком много для управления всего-то парой атрибутов, но важно понимать, что логика дескриптора теперь вынесена в отдельную кодовую единицу: класс Quantity. Обычно мы не определяем дескриптор в том же модуле, в каком он используется, а заводим отдельный служебный модуль, предназначенный для использования во всем приложении, а то и во многих приложениях, если разрабатывается библиотека или фреймфорк.

Воображаемый магазин натуральных пищевых продуктов столкнулся с неожиданной проблемой: каким-то образом была создана строка заказа с пустым описанием, и теперь заказ невозможно выполнить. Чтобы предотвратить такие инциденты в будущем, мы создадим новый дескриптор, NonBlank. Проектируя NonBlank, мы обнаруживаем, что он очень похож на дескриптор Quantity, а отличается только логика проверки. Это наводит на мысль о рефакторинге и заведении двух базовых классов: завести абстрактный класс Validated, переопределяющий метод set, вызывая метод validate, который должен быть реализован в подклассах. Затем мы переписываем Quantity и реализуем NonBlank, наследуя классу Validated, так что остается лишь написать методы validate. Соотношение между классами Validated, Quantity и NonBlank – пример паттерна проектирования Шаблонный метод, который в классической книге «Паттерны проектирования» описывается следующим образом: Шаблонный метод определяет алгоритм в терминах абстрактных операций, которые переопределяются в подклассах для обеспечения конкретного поведения.

import abc
class Validated(abc.ABC):
def __set_name__(self, owner, name):
self.storage_name = name
def __set__(self, instance, value):
value = self.validate(self.storage_name, value)
instance.__dict__[self.storage_name] = value
@abc.abstractmethod
def validate(self, name, value):
"""вернуть проверенное значение или возбудить ValueError"""

class Quantity(Validated):
"""число, большее нуля"""
def validate(self, name, value):
if value <= 0:
raise ValueError(f'{name} must be > 0')
return value
class NonBlank(Validated):
"""строка, содержащая хотя бы один символ, отличный от пробела"""
def validate(self, name, value):
value = value.strip()
if not value:
raise ValueError(f'{name} cannot be blank')
return value

import model_v5 as model
class LineItem:
description = model.NonBlank()
weight = model.Quantity()
price = model.Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
#python #books

Ссылки на посты по книжке "Л. Рамальо. Python – к вершинам мастерства: Лаконичное и эффективное программирование" (в оригинале - Fluent Python, 2nd Edition). Содержат материал, который показался мне интересным и вошёл в категорию #codegems.

Затрагиваются механизмы сопоставления (match), классы данных и их аналоги, аннотирование типами, инструменты itertools, работа с классами/ООП, генераторы, контекстные менеджеры, асинхронка, дескрипторы классов.

Пробегитесь по темам, если есть незнакомые слова, возможно, есть смысл перечитать актуальную доку Питон )

1. [], {} и ()/match/ChainMap/MappingProxyType
2. class init/dict/json
3. unicode: NFC/NDF, strxfrm/NamedTuple/dataclass
4. more dataclass/typehints
5. weakrefs/functional programming/more typehints
6. Any/|/TypeVar/TypeAlias/typing.Protocol
7. positional-only/closures/singledispath/decorator via class
8. getattr/reduce via initializer/zip,zip_longest/principle of failing fast
9. goose typing/vurtual subclass/Hashable/ABC/Decimal
10. UserDict, UserList, UserString/MRO/mixin/get_annotations
11. (sub)generator/coprogram/type: ignore/with/@contextmanager
12. else in for,while,try/scientific sins/GIL/getswitchinterval/asyncio
13. asyncio.to_thread/asyncpg/asyncio.Semaphore/async with/keyword.iskeyword
14. property/vars/metaprogramming
15. class descriptors

Если решите читать книгу - ТОЛЬКО в оригинале, русский перевод плох.
#sklearn #codegems

Прошло больше года, и только сейчас в сайкит-лёрн замёрджили ГОТОВУЮ ветку, которая втрое ускоряет расчёт classification_report.

Adrin Jalali закодил решение в течение суток с момента, как я нашёл дублирующие вызовы в sklearn. Остальное время заняли бюрократические согласования.

Вывод: open-source это страшная бюрократия, не надейтесь, что вам что-то быстро исправят, делайте сами. Вместе с тем, не ленитесь создавать хотя бы багрепорты, чтобы следующие поколения DS-ов хоть немного могли почиллить 😅

Примерно с той же эффективностью можно оставлять багрепорты или запросы функциональности для catboost - да, сделают в течение года-полутора (что по сути вечность), но я таким путём за последние N лет пропихнул улучшений 4-5 туда.
#numpy #codegems #vectorize

Будьте осторожны с vectorize )
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
#codegems #frameworks

С заменой sklearn/pytorch я не согласен, это глупость. selectolax я не знаю, а вот под всем остальным скорее подпишусь. Лучше начинать проекты с рекомендуемых тулз вместо исторически самых популярных.

https://python.plainenglish.io/5-overrated-python-libraries-and-what-you-should-use-instead-106bd9ded180