Серия «Не Ван Россум»

Парсинг?

Расскажу вам о трюке, как можно обрабатывать (например, парсить) данные по какому-то ключу. Допустим, есть у нас поток с пакетами данных, которые необходимо принять и разложить по полочкам или какая-то другая задача, но наша проблема в том, что мы пишем код и понимаем, что сейчас у нас будет очень много if-ов и elif-ов и мы в них утонем.

Об оптимизации по скорости мы здесь не говорим, это просто пример.

class MyParser:

····def parse(self, some_chunk: dict) -> str:

········assert isinstance(some_chunk, dict)

········assert "action" in some_chunk

········assert "param" in some_chunk

········action, param = some_chunk["action"], some_chunk["param"]

········method = getattr(self, action, None)

········print(dir(method))

········res = method(some_chunk) if method else self.not_parsed(some_chunk)

········# Могли бы также вызвать method(param), если так будет удобней

········return res

····"""

····Здесь вся обработка полученных данных: парсинг или другое действие.

····"""

····def run(self, some_data: dict) -> str:

········"""

········Здесь действие для 'run', и так далее...

········"""

········...

········return "run processed"

····def stop(self, some_data: dict) -> str:

········...

········return "stop processed"

····def freeze(self, some_data: dict) -> str:

········...

········return "freeze processed"

····def restart(self, some_data: dict) -> str:

········...

········return "restart processed"

····def kill(self, some_data: dict) -> str:

········...

········return "kill processed"

····def info(self, some_data: dict) -> str:

········...

········return "info processed"

····def not_parsed(self, some_data):

········wtf_key = some_data["action"]

········raise RuntimeError(f"Undefined key found: {wtf_key}")

········# Или что-то другое, что должны сделать по умолчанию при

········# отсутствии обработчика такого ключа.

if __name__ == "__main__":

····data = [

········{"action": "run", "param": "some_0"},

········{"action": "stop", "param": "some_1"},

········{"action": "freeze", "param": "some_2"},

········{"action": "restart", "param": "some_3"},

········{"action": "kill", "param": "some_4"},

········{"action": "info", "param": "some_5"},

········{"action": "undefined", "param": "some_6"},

····]

····...

····mp = MyParser()

····for chunk in data:

········res = mp.parse(chunk)

········print(res)

Как видите, если у нас в классе есть обработчик для action, названный тем же именем, то мы его и вызываем. А если нет - особым образом обрабатываем такой случай.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью

Слышали ли вы о cloc

Слышали ли вы о cloc?

Эта маленькая утилита командной строки здорово помогает мне понять приблизительный объём количества написанных мной строк кода. Название расшифровывается просто: Count Lines Of Code.

Запускаем в одном из проектов, и видим:



50 text files.
classified 50 files
49 unique files.
29 files ignored.

github.com/AlDanial/cloc v 1.82 T=0.05 s (459.1 files/s, 279540.4 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
C 1 526 2087 10777
Python 10 70 17 220
Cython 1 38 10 207
Bourne Shell 8 0 0 36
DOS Batch 2 0 0 13
Markdown 1 1 0 3
-------------------------------------------------------------------------------
SUM: 23 635 2114 11256
-------------------------------------------------------------------------------

С разбивкой по "языкам", и в наглядной табличке.
Установить в Ubuntu просто: sudo apt-get install cloc
Также на GitHub автора есть и версия для Windows.

p.s. Не могу не отметить как Cython при сборки библиотеки из 220 строк кода на Python сделал почти 11K строк на C. Я б ниасилил такое руками писать, хотя руками получилось бы и короче и эффективней, но сколько моего времени бы на это ушло? Люблю Python.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью

Что такое globals() и locals()?

globals и locals возвращают словари глобальных и локальных переменных. Локальные - это например, те, что находятся в области видимости функции или метода. Глобальные доступны отовсюду.

Но я не могу просто так взять и не упомянуть какую-нибудь особенность.

В globals также присутствуют импортированные модули.

import json

myglobals = globals()

print(f'{myglobals=}\n\r')

j = myglobals['json']

print(j)

print(type(j)) # Всё в Python является объектом, и модуль тоже.

>>> <module 'json' from '/usr/lib/python3.8/json/init.py'>

>>> <class 'module'>

Давайте посмотрим в локальные и глобальные переменные.

a = 15

def test():

····a = 1 # Эта 'a' - в locals

····print(a)

test()

print(a) # Эта - глобальная, она и останется ==15

>>> myglobals={'name': 'main', 'doc': None, 'package': None, 'loader': <_frozen_importlib_external.SourceFileLoader object at 0x7fdb99a471c0>, 'spec': None, 'annotations': {}, 'builtins': <module 'builtins' (built-in)>, 'file': 'ex15.py', 'cached': None, 'json': <module 'json' from '/usr/lib/python3.8/json/init.py'>, 'myglobals': {...}}

>>> 1

>>> 15

Есть такое правило, по которому резолвятся имена, в английском оно называется LEGB. Local - Enclosing - Global - Builtins. То есть, локальные, нелокальные/вложенные (когда функция внутри другой функции), глобальные и встроенные.

В приведенном примере можно сказать, что глобальная переменная a затеняется локальной переменной a. Это такой термин, "затенение переменной". Затеняется она, естественно, внутри функции.

Если мы хотим использовать глобальную переменную в функции, об этом нужно сообщить, дописав 'global a' после определения функции - так код будет более понятным. Если же просто убрать строку 'a = 1', функция тоже будет использовать глобальную переменную, т.к. интерпретатор, не имея локальной 'a', пойдет дальше по правилу LEGB и найдёт ту 'a', которую мы и имели в виду. Глобальную в нашем случае.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью

Python полностью поддерживает ООП?

"Честное слово, я и не подозревал, что вот уже более сорока лет говорю прозой"

(Мещанин во дворянстве, Жан-Батист Мольер).

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

С наследованием, думаю, все понятно. Наверняка каждый создавал класс наследованный от чего-либо, начиная с dict и далее.

class NoneDict(dict):

····def missing(self, key):

········return None

nd = NoneDict({'a': 1, 'b':2})

print(nd['a'], nd['b'], nd['c'])

>>>1 2 None

(Также я обещал одному юному подписчику показать пример, как не париться с ключами даже без defaultdict модуля collections)

Рассматриваем дальше. Инкапсуляция.

"Под инкапсуляцией в объектно-ориентированном программировании понимается упаковка данных и методов для их обработки вместе, т. е. в классе."

Очевидно, что это реализовано тоже, и каждый экземпляр класса всё своё носит с собой.

"Полиморфизм позволяет обращаться с объектами разных классов так, как будто они являются объектами одного класса."

Тоже весьма очевидно. Наследование и перегрузка методов (плюс магические методы) позволяют нам при желании взаимодействовать с классами универсально и единообразно.


На самом деле, мы используем эти принципы ежедневно, даже не задумываясь о том, что "мы используем здесь такой-то принцип, потому что...". Очевидно, что язык явным и естественным образом направляет нас к единственно правильному способу сделать "это" (см. PEP20).

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

(Тайные виды на гору Фудзи, Виктор Пелевин).


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

Таким образом, мы выяснили, что Python полностью поддерживает ООП.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью

Что такое *args и **kwargs в определении функции?

Продолжаем сериал. Сами авторы опросника пишут, что "*args и **kwargs — это специальные параметры в Python, которые позволяют передавать переменное количество аргументов в функцию", что может ввести в заблуждение.

Думаете, args и kwargs - это специальные ключевые слова?

А вот и нет.

Есть только * и **, и нет никаких специальных параметров args и kwagrs. Они так названы только для удобства и в 99% случаев вы увидите эти названия в учебниках, но это сделано только потому, что "так принято". И это не ключевые слова.

Вот, смотрите:

class SomeClass():

····def __init__(self, *bbargs, **zzargs):

········print(f'{bbargs=}; {zzargs=}')

if __name__ == '__main__':

····c = SomeClass(1,2,3, a='a', c='d')

>>>bbargs=(1, 2, 3); zzargs={'a': 'a', 'c': 'd'}

Операторы * и ** используются всего лишь для упаковки и распаковки коллекций в Python, и вовсе не являются какими-то "специальными операторами для функций"!

Распаковка списков и кортежей

a, *b = (1, 2, 3, 4)

# первое значение будет a, все остальные списком станут b

# a == 1, b == [2,3,4]

*a, b = (1, 2, 3, 4)

# наоборот, сколько-то первых будут a, а последнее - точно b

# a == [1,2,3], b == 4

Распаковка словарей

params = {'a':1, 'b':2}

c = {'c':3, **params}

print(c)

>>>{'c': 3, 'a': 1, 'b': 2}

Хорошая ли практика передавать аргументы через args, kwargs?

Я считаю, что в большинстве случаев - нет.

Вот вам пример, допустим нам в классе нужен параметр адреса.

class SomeClass():

····def __init__(self, *bbargs, **zzargs):

········self.addr = zzargs.get('addr', '127.0.0.1') # 1

········self.addr = zzargs.get('addr', None) or '127.0.0.1' # 2

········print(self.addr)

c = SomeClass(1,2,3, a='a', c='d', addr='192.168.1.1')

Красиво, читаемо? Я считаю, что нет.

Кстати, вот ещё пример за гранью добра и зла:

class SomeClass():

····def __init__(self, *bbargs, **zzargs):

········self.__dict__.update(zzargs)

········# Так делать нельзя!

········print(self.addr) # '192.168.1.1' ;)

c = SomeClass(1,2,3, a='a', c='d', addr='192.168.1.1')

Для меня самый правильный вариант - по возможности принципиально избегать использования * и ** до тех пор, пока это не станет абсолютно необходимым:

class SomeClass():

····def __init__(self, addr:str='127.0.0.1') -> None:

········self.addr: str = addr

········print(self.addr)

Потому что так - красивей!

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью

Рефлексии (сдвоенный пост)

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

И я вспомнил две цитаты разных людей из разных эпох, добившихся очень многого.

"On a given day, a given circumstance, you think you have a limit. And you then go for this limit and you touch this limit, and you think, 'Okay, this is the limit.' As soon as you touch this limit, something happens and you suddenly can go a little bit further. With your mind power, your determination, your instinct, and the experience as well, you can fly very high."

"Нужно бежать со всех ног, чтобы только оставаться на месте, а чтобы куда-то попасть, надо бежать как минимум вдвое быстрее!"

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

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

Но возможности и их реализация - не самоцель. На определенном этапе ты начинаешь задумываться о том, как превратить мастерство в искусство. Хотя бы иногда. Хотя бы тёплыми летними вечерами.

...


Наверное, скоро у каждого из нас будет своя домашняя (карманная?) нейросеть.

По крайней мере, у меня она уже есть, работает на моём домашнем сервере и отвечает на мои сообщения в Telegram.

В ответ на мои рефлексии она мне выдала следующее:

Этот процесс можно разделить на два этапа: сбор информации и ее обработка. Сбор информации. На этом этапе нужно собрать как можно больше информации по конкретной теме. Не следует ограничиваться только одним источником информации (книгой, газетной статьей, лекцией).

Однако процесс непрерывный, ведь мир вокруг нас все время меняется. Новые идеи приходят отовсюду – из книг, газет, телепередач, лекций, фильмов, социальных сетей
и т. д.

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

Вы понимаете, что можно сделать что-то для себя, а также сделать что-то значимое. что вы можете не торопиться, чтобы создавать вещи, которые имеют смысл для вас. что вы можете спроектировать свое собственное будущее. И поэтому вы возвращаетесь к чертежной доске.

Кажется, будущее уже наступило.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью
6

Как просмотреть методы объекта?

Интроспекция — это способность объекта во время выполнения получить тип, доступные атрибуты и методы, а также другую информацию, необходимую для выполнения дополнительных операций с объектом.

Python поддерживает полную интроспекцию. Это означает, что для любого объекта можно получить всю информацию о его внутренней структуре. Применение интроспекции является важной частью того, что называют pythonic style, и широко применяется в библиотеках и фреймворках Python.

Инструменты интроспекции - это:

dir(), type(), id(), hasattr(), isinstance().

Используются они, соответственно, для:

dir() - получения списка всех атрибутов и методов объекта

type() - получения типа объекта

id() - идентификатора (адреса в памяти) объекта

hasattr() - проверки на наличие атрибута

isinstance() - проверки на принадлежность к классу или подклассу

добавим в этот же список и callable() - проверка, является ли указанное "свойство" объекта вызываемым (callable) методом.

Ответ, казалось бы, простой - использовать dir().

Но вопрос был задан именно о методах, а про атрибуты нам ничего не было сказано. Если это не ошибка задающего вопрос, а намеренное умолчание, то нам придется перебрать все свойства, полученные из dir() и проверить, являются ли они методами.

some = 'abc'

print([attr_name for attr_name in dir(some) if callable(getattr(some, attr_name))])

>>>['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

Восхитимся, как много хорошего и разного можно сотворить с простой строкой! Попутно отметим, что часть методов, чьё название начинается и заканчивается с двух подчёркиваний - особые, магические. О них я напишу в другой раз.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью

Продолжаем сериал. "Чем отличаются многопоточное и многопроцессорное приложение?""

Я бы назвал не многопроцессорное, а многопроцессное. Ведь нет в Python модуля multiprocessor, есть только multiprocessing, и оперируем мы не сущностями "процессор", а процессами.

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

Что интересно, в multiprocessing имеется модуль multiprocessing.dummy, который позволяет переключить многопроцессное приложение в режим многопоточного.

Часто возникает путаница из-за терминологии. Нужно просто знать, что процесс может порождать другие потоки и/или другие процессы, при этом потоки работают внутри родительского процесса и имеют общие ресурсы, а процессы - нет.

Надеюсь, я не внёс ещё большей путаницы.

Это кросспост из моего Telegram-канала "Не Ван Россум", где я прямо сейчас пишу сериал "101 вопрос про Python" с описанием подводных камней, неочевидностей и загвоздок.

Показать полностью
Отличная работа, все прочитано!