es
Feedback

¡No caigas en manos de tramposos! Telemetrio encuentra y marca estos canales 👉 Si quieres ver la etiqueta, suscríbete

Python Заметки

Python Заметки

Ir al canal en Telegram

Интересные заметки и обучающие материалы по Python Контакт: @paulwinex ⚠️ Рекламу на канале не делаю!⚠️ Хештеги для поиска: #tricks #libs #pep #basic #regex #qt #django #2to3 #source #offtop

Mostrar más
2 206
Suscriptores
-324 horas
-47 días
+130 días
Archivo de publicaciones
В Python очень классной идеей является возможность переопределять взаимодействие объекта с любыми операторами, в том числе и унарные операторы. И это можно очень интересно применить! Долгое время меня смущало отсутствие возможности инкрементировать число на 1 с помощью синтаксиса С++. То есть вот так: val++ Эта команда просто прибавляет единицу к значению val. В Python аналогичная операция делается так: val += 1 Ну такой cебе ZEN😭. Давайте напишем класс, который сможет провернуть что-то подобное. А именно, сделаем чтобы величина значения увеличивалась на 1 с помощью такой записи: >>> c = Counter() >>> print(c) 0 >>> +c 1 Да, вместо x++ мы сделаем +x, чтобы не конфликтовать со стандартным синтаксисом. Но и это уже не плохо! Для взаимодействия с оператором "+" есть магический метод __add__, но для бинарного оператора. То есть когда операнда два. Для унарной версии оператора "+" есть метод __pos__, что означает positive. То есть как себя ведёт объект когда его пытаются сделать положительным. Вот его и используем: class Counter(object): def __init__(self, init=0): self.val = init def __pos__(self, *args): self.val += 1 def __repr__(self): return 'Count: {}'.format(self.val) Чтобы удобно было смотреть на результат, добавил метод __repr__. Проверяем что получится. >>> c = Counter() >>> print(c) Count: 0 >>> +c Count: 1 >>> +c Count: 2 Отлично, сделали счётчик с необычным синтаксисом! Добавим аналогичный метод для оператора "-" __neg__, что означает negate и описывает реакцию на попытку сделать число отрицательным. Теперь можем двигать значение в обе стороны: >>> class Counter(object): ... >>> def __neg__(self): >>> self.val -= 1 >>> c = Counter(5) >>> print(c) Count: 5 >>> +c Count: 6 >>> -c Count: 5 Стоит учесть, что данная реализация методов изменяет сам объект а не возвращает новый изменённый, как это принято в Python. Так что весь пример не очень Pythonic-way). Но такое решение реализует необходимый нам минималистичный синтаксис. Не буду утверждать, что пример получился полезным с точки зрения использования в реальной работе. Но для практики изучения вышло вполне интересно. #tricks

Есть забавный трик с defaultdict, это бесконечно-рекурсивное дерево. Самое забавное что создать его можно в одну короткую строку.
def tree(): return defaultdict(tree)

Теперь можете создавать вложенные словари любой глубины!
levels = tree()
levels['lvl1']['lvl2']['lvl3'] = 'item'

И таким образом строить любую иерархическую систему. Примеры и описание на странице автора: 🌎 https://gist.github.com/hrldcpr/2012250 #tricks

Бывают задачи когда из большого массива объектов требуется отфильтровать эти объекты по категориям. В результате получаем словарь примерно такого вида:
data = {
    "category1": [item1, item2, ...],
    "category2": [item1, item2, ...],
    ...
    }

При этом заранее мы не знаем список категорий и их необходимо добавлять в процессе итерации. Как такой код будет выглядеть:
items = [...]
sorted_items = {}

for item in items:
    cat = get_category(item)
    if cat not in sorted_items:
        sorted_items[cat] = []
    sorted_items[cat].append(item)

Значением ключа является не сам элемент а промежуточный объект это список. Потому нам следует сначала убедиться, что он есть, а если нету то создать. Альтернативный код делающий тоже самое:
for item in items:
    cat = get_category(item)
    if cat not in sorted_items:
        sorted_items[cat] = [item]
    else:
        sorted_items[cat].append(item)

Чтобы избежать этой проверки можно использовать тип defaultdict, это очень простой класс который сам за вас сделает проверку и добавит нужный тип если его нет. Просто укажите нужный тип в конструкторе.
from collections import defauldict

items = [...]
sorted_items = defaultdict(list)

for item in items:
    sorted_items[get_category(item)].append(item)

Если указанного ключа нет в словаре, то он создаётся сразу со списком в значении и возвращается как будто он там и был. #tricks #libs

Порой бывает необходимо работать с JSON файлами ручками, читая или изменяя данные. И очень не удобно, когда юникод в файле записан в виде кодированных символов.
>>> import json
>>> data = {'title': 'Привет Медвед!'}
>>> print(json.dumps(data))
'{"title": "\\u041f\\u0440\\u0438\\u0432\\u0435\\u0442 \\u041c\\u0435\\u0434\\u0432\\u0435\\u0434!"}'

Эх, безобразие! Ни прочитать нормально, ни поправить. Чтобы такое поведение изменить, достаточно добавить аргумент ensure_ascii=False
>>> json.dumps(data, ensure_ascii=False)
'{"title": "Привет Медвед!"}'

Теперь символы не кодируются в Unicode. В файл запишется в таком же виде. ____________________ Для тех кто в танке (всё еще на Python 2 🚂 ). Строку следует делать как unicode, и для записи в файл использовать модуль codecs.
>>> import json, codecs
>>> data = {'title': u'Привет Медвед!'}
>>> with codecs.open(path, "w", encoding='utf-8') as f:
>>>     json.dump(data, f, ensure_ascii=False)

#libs #tricks

Часто используете Python в терминале? Скорее всего вас не особо устраивает дефолтный REPL. Советую попробовать прокаченные ве
Часто используете Python в терминале? Скорее всего вас не особо устраивает дефолтный REPL. Советую попробовать прокаченные версии интерактивного шела: 🔸bpython - подсветка синтаксиса - автокомплиты - инлайн подсказки параметров функций - история команд - авто отступы Установка: pip3 install bpython Сайт 🌎 https://bpython-interpreter.org/ 🔸 ptpython - подсветка синтаксиса - автокомплиты - поддержка мышки - авто отступы - цветовые темы Установка: pip3 install ptpython Сайт 🌎 https://github.com/prompt-toolkit/ptpython Также можно глянуть: ➡️ www.asmeurer.com/mypython ➡️ xon.sh ➡️ ipython.org #libs

Схематичное представление структуры linked list
Схематичное представление структуры linked list

В стандартной поставке Python есть один полезный инструмент в библиотеке collections, это класс deque. он очень похож на простой список но он намного быстрее работает в некоторых случаях. Например для обработки элементов в начале списка у него есть дополнительные методы: extendleft(), appendleft() и popleft(). Запустим пару тестов!!! 🚀
>>> from collections import deque
>>> import time
>>> 
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>>     l1 = deque()
>>>     for i in range(5000):
>>>         l1.append(i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test deque: {en}sec')
>>> 
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>>     l2 = list()
>>>     for i in range(5000):
>>>         l2.append(i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test  list: {en}sec')
Test deque: 0.452sec
Test  list: 0.436sec

Добавление в конец списка работает примерно одинаково. Теперь попробуем вставлять элемент в начало массива.
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>>     l1 = deque()
>>>     for i in range(5000):
>>>         l1.appendleft(i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test deque: {en}sec')
>>> 
>>> st = time.perf_counter()
>>> for _ in range(1000):
>>>     l2 = list()
>>>     for i in range(5000):
>>>         l2.insert(0, i)
>>> en = round(time.perf_counter()-st, 3)
>>> print(f'Test  list: {en}sec')
Test deque: 0.435sec
Test  list: 6.347sec

Прирост производительности почти в 15 раз! 😲 Тестим функцию pop() (опустим код теста для краткости)
Test deque: 0.48sec
Test  list: 0.529sec

Теперь pop(0) для list и popleft() для deque
Test deque: 0.476sec
Test  list: 3.101sec

Быстрей примерно в 6.5 раз. Почему так быстро? Дело в том что deque это некий аналог такого типа данных как "linked list data structure", в Python это занывается "двусвязные списки" (doubly-linked lists). Это список, но не в привычном представлении, а с особой оптимизированной структурой. При создании такого массива данные никуда не переносятся а только линкуются оттуда где были. Да, это похоже на Python-лист, но линковка происходит иначе. Вместо того чтобы собирать некий стек ссылок и назвать его списком, в doubly-linked list каждый элемент просто ссылается на следующий. Такая структура позволяет значительно ускорить, создание списка, изменение с любой стороны. Где это может пригодиться? Конечно же в двусторонних очередях (double-ended queue). То есть когда мы добавляем элементы в начало а забираем с конца, или наоборот. Минус такого подхода в просадке производительности для произвольного доступа к элементам в середине очереди. Полный код тестов 🌎 ______________ Реальное описание несколько сложней, я постарался передать основную суть. #libs #tricks

Почему не стоит в коде использовать assert для проверки данных? Действительно, команда очень удобна для быстрой проверки правдивости какого-либо факта. assert isinstance(value, int), "Value must be type int" Но почему советуют делать это только в тестах? Дело в том, что эта команда сделана именно для тестов и есть специальный режим когда она глобально отключается и не работает. Есть такая builtin константа __debug__, которая по умолчанию имеет значение True (и это не изменить в коде). Именно она указывает, будут ли работать ваши assert'ы. Стоит запустить интерпретатор в режиме оптимизации (флаг -O), константа __debug__ будет равна False, и все ваши проверки будут проигнорированы. python -O script.py Поэтому всегда используйте raise. if not isinstance(value, int): raise TypeError("Value must be type int") #tricks

Вы всё еще проверяете секретные данные оператором сравнения?
>>> if password == user_password:
>>>     ...

Это небезопасный способ сравнения для стендалон приложений. Он уязвим к такому типу атаки как timing attack, позволяющий делать выводы и угадывать пароль на основании времени проверки. Чтобы сделать безопасное сравнение используйте метод secrets.compare_digest(). Он защитит операцию проверки от подобных атак.
>>> import secrets
>>> if secrets.compare_digest(password, user_password):
>>>     ...

Возможно вы ранее слышали про метод hmac.compare_digest(). Он не только делает то же самое, это один и тот же метод!
>>> import secrets
>>> import hmac
>>> hmac.compare_digest is secrets.compare_digest
True

#libs

В прошлом посте мы рассмотрели синхронный планировщик задач sched. Функционал вроде не плох, но синхронное выполнение с блокировкой всё портит☹️. Можем ли мы как-то поправить ситуацию? На самом деле можем (помимо отправки всего в subprocess). Функция run() принимает аргумент blocking, который по умолчанию True. То есть, если мы укажем blocking=False то получим неблокирующее выполнение? Нет. Этот параметр работает иначе. Если мы делаем неблокирующий запуск, то после вызова метода run(blocking=False) планировщик выполнит все задачи, которым пришло время исполниться в обычном блокирующем синхронном режиме и вернёт время, через которое следует запуститься следующей задаче. То есть через какое время нужно запустить run(...) еще раз. Получается, что вместо ожидания таймаута без полезной нагрузки планировщик освобождает поток и сообщает через сколько ему пора будет продолжить работу. А что делать с этой информацией, решаете сами. Выполнение следующих задач произойдет после следующего вызова run() и если пришло их время выполниться. Например, вместо ожидания следующей задачи будем делать что-то полезное:
import sched, time

def func(name):
    # задача
    t = round(time.time()-start_time, 2)
    print(f"Execute {name} ({t}s)")

start_time = time.time()
# создаём планировщик
s = sched.scheduler(time.time, time.sleep)
s.enter(0, 1, func, argument=('ev1',))
s.enter(1, 1, func, argument=('ev2',))
s.enter(2, 1, func, argument=('ev3',))

delay = 0
last_run_time = time.time()
while True:
    # выполнение заданий планировщика
    if last_run_time + delay < time.time():
        delay = s.run(blocking=False)
        if delay is None:
            break
        last_run_time = time.time()

    # здесь делаем что-то полезное
    print('Делаем что-то полезное...')
    time.sleep(0.1)

print('Complete all tasks')

Планировщик остаётся по-прежнему синхронным, но теперь вместо бесполезного ожидания мы можем запустить другой код на исполнение и знаем когда следует вернуться к планировщику. ___________________ PPS. Да, в этих примерах я нагло использую глобальные переменные))) Не делайте так на реальных проектах

В Python есть стандартный модуль sched для синхронного планировщика задач. Что??? Синхронных??? В наш-то век "асинхронщины" и "параллельщины"! Спокойно, сначала смотрим код, потом разбираемся. Работает это так: - создаём планировщик - добавляем задачи в очередь с таймаутом и приоритетом - запускаем и ждём пока завершится вся очередь Смотрим пример: import sched, time def func(name): t = round(time.time()-start_time, 2) print(f"Execute {name} ({t}s)") # просто отметка времени старта start_time = time.time() # создаём планировщик s = sched.scheduler( time.time, # функция замера времени time.sleep # функция ожидания ) # добавляем задачи s.enter(0, 1, func, argument=('ev1',)) s.enter(2, 1, func, argument=('ev2',)) s.enter(1, 1, func, argument=('ev3',)) # запускаем очередь на исполнение s.run() Описание аргументов Вывод получаем в соответствии со временем задержки: Execute ev1 (0.0s) Execute ev3 (1.0s) Execute ev2 (2.0s) Функция run() запускает планировщик и начинается выполнение задач в порядке очереди по времени и приоритету. Время указывается с момента старта очереди. Если время совпадает то сортировка идёт по приоритету. Что значит синхронный планировщик? Это значит что задачи будут выполняться строго по очереди в одном потоке. Никаких мультипотоков и мультипроцессов, модуль прост как бревно! Если у первой задачи стоит задержка 0сек а у второй 1сек, и при этом первая задача выполняется 3сек, то вторая задача выполнится только через 3 сек. Никакого параллельного запуска не будет. Параметр delay следует понимать не как "запусти через N сек" а как "запусти не раньше чем через N сек". Время запуска следующей зависит от выполнения предыдущих задач. Где это может пригодиться? Очередь задач, между которыми должен быть промежуток времени по какой-либо причине. 🔸 несколько синхронных задач, которые должны выполняться друг за другом но требующие ожидания обновления какой-либо инфраструктуры (тормозная сеть?) 🔸 сетевой API который имеет лимит на количество команд в единицу времени 🔸 фейковая задержка для генерации тестов или имитация поведения юзера. Конечно, всё это можно решить банальным time.sleep() в нужном месте, но sched даёт несколько более удобный интерфейс управления задачами. #libs

Python 3.9 готовит нам приятный сюрприз в PEP584. Ранее мы обсуждали как можно удобно сложить вместе два словаря получив новый словарь. Было много вариатов, но ни одного идеального. Наконец-то в Python добавили оператор для слияния словарей!!! Это оператор "|". А это значит, что начиная с 3.9 соединить словари можно таким синтаксисом: dct3 = dct1 | dct2 Чтобы обновить словарь, можно использовать такой синтаксис dct1 |= dct2 Данный функционал уже можно опробовать, установив первые релизы. #pep

Допустим, имеется у нас задача: разделить одно число на другое и получить отдельно целое число и остаток от деления. Например, исходные числа 15 и 2. В результате должны получить 7 и 1. То есть 7 раз двойка входит в состав 15 целиком и потом остаётся еще 1. Как будем действовать? Очевидно же, целое число вхождений получаем через floor division >>> 15//2 7 Остаток через деление по модулю >>> 15%2 1 Но можно сделать проще (ох, ну куда уж проще то 😄). В Python есть builtin функция divmod которая делает эти два действия в одно. >>> divmod(15, 2) (7, 1) #tricks

"Ну и как же нам перекидывать строки ви числа?" спросите вы. Проверять каждый символ, очистив строку от лишних знаков и точек. Потом конвертить допустимые символы в числа и восстанавливать знак, дробную чусть и тд??? Самый быстрый способ это просто "попробовать" 😜
text = "-0.3"
try:
    num = float(text)
except ValueError:
   print('Dough!')

Всё остальное это уже парсинг и разбор символов для иных целей. #basic

В продолжение прошлого поста про цифры в мире строк. Почему методы isdigit() и isnumeric() не определяют в строке float и отрицательные значения? Дело в том, что эти методы работают с ЦИФРАМИ, то есть с единичным символом. А строка "-2" или "3.4" это уже ЧИСЛО. То есть не символ а значение, записанное несколькими символами. Все озвученные методы проходятся по каждому символу строки и проверяют их индивидуально. В юникоде есть символы цифр с точками "🄀⒈⒉⒊⒋⒌⒍⒎⒏⒐" Каждая из них это ОДИН СИМВОЛ, поэтому он будет считаться цифрой
>>> '⒌'.isdigit(), '⒌'.isnumeric()
True, True

Но когда мы пишем это выражение в два символа ( 5+точка), то это не работает.
>>> '5.'.isdigit(), '5.'.isnumeric()
False, False

А еще есть такие символы
>>> '⑴⑵⑶⑷⑸'.isdigit()
True
>>> '🄁🄂🄃'.isdigit()
True

Но они не преобразуются в десятичные цифры >>> '⒈'.isdecimal() False >>> '🄃'.isdecimal() False >>> '⑶'.isdecimal() False #basic

У строки в Python есть два очень похожих метода. На столько похожих что кажется они делают одно и тоже. Это метод isdigit() и isnumeric() Давайте посмотрим зачем нам два одинаковых метода? И так ли они одинаковы? Очевидно что isdigit() говорит нам, состоит ли строка только из чисел 0-9 >>> '12'.isdigit() True >>> '12x'.isdigit() False >>> '-12'.isdigit() False >>> '12.5'.isdigit() False Можно предположить что isnumeric() делает более глубокий анализ и распознаёт в строке float или отрицательное число. >>> '15'.isnumeric() True >>> '-15'.isnumeric() False >>> '15.2'.isnumeric() False Нет, всё так же как и с другим методом. В чем же тогда разница? Для начала посмотрим следующие примеры: >>> '5'.isdigit(), '5'.isnumeric() # Обычная цифра 5 # True, True >>> '꧕'.isdigit(), '꧕'.isnumeric() # Яванская 5 # True, True >>> '෩'.isdigit(), '෩'.isnumeric() # Синхала 3 # True, True >>> '৩'.isdigit(), '৩'.isnumeric() # Бенгальская 3 # True, True >>> '༣'.isdigit(), '༣'.isnumeric() # Тибетская 3 # True, True >>> '³'.isdigit(), '³'.isnumeric() # 3 верхний индекс (степень) # True, True >>> '𝟝'.isdigit(), '𝟝'.isnumeric() # Математическая двойная 5 # True, True >>> '๔'.isdigit(), '๔'.isnumeric() # Тайская 4 # True, True >>> '➑'.isdigit(), '➑'.isnumeric() # 8 в круге # True, True А теперь примеры в которых, по мнению Python, результаты не равны >>> '¾'.isdigit(), '¾'.isnumeric() # дробь три четверти # False, True >>> '⅕'.isdigit(), '⅕'.isnumeric() # дробь одна пятая # False, True >>> '𒐶'.isdigit(), '𒐶'.isnumeric() # клинопись 3 # False, True >>> '三'.isdigit(), '三'.isnumeric() # 3 из унифицированной идеограммы # False, True >>> '⑩'.isdigit(), '⑩'.isnumeric() # цифра 10 в круге # False, True >>> 'Ⅳ'.isdigit(), 'Ⅳ'.isnumeric() # Римская 4 # False, True >>> '𑇪'.isdigit(), '𑇪'.isnumeric() # Сенегальская архаическая 10 # False, True >>> '𐌢'.isdigit(), '𐌢'.isnumeric() # Этрусская цифра 10 # False, True >>> 'ↂ'.isdigit(), 'ↂ'.isnumeric() # Римская цифра 10000 # False, True >>> '〇'.isdigit(), '〇'.isnumeric() # Символ ККЯ ноль # False, True Получается, что isdigit() говорит нам, является ли символ десятичной цифрой или спецсимволом, имеющим цифирное значение после преобразования. В свою очередь isnumeric() включает все дополнительные символы юникода которые имеют отношения к числовым и цифровым представлениям. Ну и пара примеров в которых в обоих случаях символ не является числом, это эмодзи. >>> '🕙'.isdigit(), '🕙'.isnumeric() # эмодзи 10 часов # False, False >>> '7️⃣'.isdigit(), '7️⃣'.isnumeric() # эмодзи 7 # False, False Также есть еще один дополнительный и весьма полезный метод isdecimal(). Он нам сообщает, можно ли из указанного символа сделать простую десятичную цифру. То есть сработает ли метод int(x) >>> '෩'.isdecimal(), int('෩') # Синхала 3 # True, 3 >>> '➑'.isdecimal(), int('➑') # 8 в круге # False, ValueError Какие выводы? 🔸 При определении цифры в строке isdigit() подходит лучше чем isnumeric(), но оба не гарантируют успешную конвертацию в int 🔸 Для однозначного определения возможности преобразования строки в int лучше подходит метод isdecimal() 🔸 Для однозначного определения символов 0...9 лучше использовать regex Полный список символов юникода которые определяются как numeric #basic

В Python всё является объектами. Это значит что у каждой сущности есть тип и какие-либо методы. Мы знаем что есть методы у строк >>> 'string'.upper() у списков >>> [1,2,3].count(2) у словарей >>> {"key": 123}.items() А есть ли какие-то методы у простых чисел? Не много, но есть! Например, возьмём простой int >>> a = 22 Метод bit_length() покажет сколько потребуется бит для отображения данного числа в двоичном представлении, исключая ведущие нули. >>> a.bit_length() 5 Проверяем >>> bin(a).lstrip('-0b') '10110' Всё верно. Проверим float >>> b = 10.5 Мы можем проверить есть ли у числа дробная часть >>> b.is_integer() False Получить наш float в виде простой десятичной дроби >>> b.as_integer_ratio() (21, 2) Конечно же Python не имеет типа "десятичная дробь", поэтому мы просто получаем кортеж из двух элементов: числитель и знаменатель. У int тоже есть такой метод (Python3.8+), но он работает "хитро". Целое число всегда равно дроби где в числителе это же число а в знаменателе 1. Поэтому данный метод у int всегда возвращает (x, 1). 😕 Кстати, чтобы обойтись без переменной просто возьмите число в скобки >>> (10.0).is_integer() True #tricks

Срез это довольно удобная штука для получения определённого диапазона элементов из списка. Но срезы не так просты как кажется, их можно использовать и для изменения оригинального списка так же как мы это делаем просто по индексу. Для примера возьмем простой список ls = [1, 2, 3, 4, 6] И поехали! >>> ls[-1] = 5 [1, 2, 3, 4, 5] Обычное изменение по индексу. >>> ls[2:4] [3, 4] Обычный срез который создаёт новый список на основе старого. >>> ls[::-1] [5, 4, 3, 2, 1] Реверс списка, тоже создаёт новый. Оригинальный список остался без изменений. Воспользуемся оператором присвоения: >>> ls[2:4] = [7, 8] [1, 2, 7, 8, 5] Заменили диапазон элементов в оригинальном списке. >>> ls[4:] = [0, 0, 0, 0] [1, 2, 7, 8, 0, 0, 0, 0] Указав диапазон сверх имеющегося мы расширили список по аналогии с методом extend(), но при этом еще и немного захватили конец списка. Всё это в одно действие! >>> ls[:0] = [9, 8, 7] [9, 8, 7, 1, 2, 7, 8, 0, 0, 0, 0] Добавили элементы в начало >>> del ls[-4:] [9, 8, 7, 1, 2, 7, 8] Удалили часть элементов списка >>> ls[1:3] = [] [9, 1, 2, 7, 8] Еще один способ удалить элементы >>> ls[:] = [] А этим способом можно пользоваться для очистки списка в Python2, в котором еще не было метода clear(). Кажется мы "потратили" весь наш список))) Сделаем новый и продолжим. >>> ls = [1, 2, 3, 4, 5, 6] >>> ls[::2] = [7,8,9] [7, 2, 8, 4, 9, 6] И конечно же мы можем использовать шаг в срезе, но тут требуется соблюдать количество подаваемых элементов. Заменять элементы с каким-то шагом можно, а добавлять нельзя. s = slice(3, 4) Как было показано ранее, срез можно сохранять как переменную и использовать в дальнейших манипуляциях >>> ls[s] = [0]*5 [7, 2, 8, 0, 0, 0, 0, 0, 9, 6] Заменили один элемент на несколько элементов, расширив исходный список из центра. И напоследок. Следующие два действия равнозначны и дадут одинаковый результат — полная замена списка. >>> ls[:] = [1,2,3,4] >>> ls = [1,2,3,4] #tricks

Наверняка вы часто используете генераторы списков (List Comprehension). Я тоже, даже порой злоупотребляю ими) Но что поделать, если они такие удобные! Давайте разберёмся как работают составные итерации в генераторе списка. Для начала посмотрим простой вид. >>> array = [1, 2, 3, 4] >>> [x for x in array] Этот код просто делает копию списка. Добавляем некоторое выражение: >>> [x*2 for x in array] Этот генератор создаёт новый список элементы которого в 2 раза больше чем в оригинальном списке. Добавим условие: >>> [x*2 for x in array if x%2] Предыдущему примеру добавили фильтр, который проходят только нечётные числа. В целом, я бы советовал на этом и остановиться. Не нужно усложнять простое! Мы можем итерировать в генераторе сразу два списка так, чтобы каждый элемент одного списка повстречался с каждым элементом из другого. >>> array1 = [1, 2, 3] >>> array2 = [4, 5, 6] >>> print([f'{i}-{j}' for i in array1 for j in array2]) ['1-4', '1-5', '1-6', '2-4', '2-5', '2-6', '3-4', '3-5', '3-6'] Давайте обозначу каждый цикл условными скобками чтобы было понятней (синтаксически это неверно). [f'{i}-{j}' (for i in array1) (for j in array2)] Можем добавить условие и сюда? Можем! >>> print([f'{i}-{j}' (for i in array1 if i >= 2) (for j in array2 if j < 5)]) ['2-4', '3-4'] При этом во второй итерации мы можем использовать переменную из первой >>> array = [(1, 2, 3), (4, 5, 6)] >>> new = [y for x in array for y in x] Кстати, мы разложили вложенные кортежи в один плоский список. Можем ли мы использовать больше двух итераций? Да хоть десять! >>> a1 = [9, 2, 4, 5] >>> a2 = [4, 5, 9, 1] >>> a3 = [5, 9, 3, 0] >>> match = [x for x in a1 for y in a2 for z in a3 if x == y and x == z] [9, 5] Нашли числа которые встречаются во всех списках. Можно еще сложней? Конечно, Python довольно многое позволяет делать. Но, как говорят про Python: если ЭТО можно сделать то еще не значит что ЭТО нужно делать. #tricks

В прошлом посте мы получали полное имя метода. В атрибут ˍˍqualnameˍˍ записано имя только до класса, к которому метод принадлежит. Как быть если нужно полное имя с модулем, а мы имеем только объект метода без инстанса или ссылки на класс? func = get_my_method() В результате этого псведо-выражения мы имеем только сам метод класса. Есть ли в нём информация к какому классу он принадлежит? Конечно есть, это атрибут ˍˍselfˍˍ. Он ссылается на инстанс. А оттуда, как не трудно догадаться, можно получить и ссылку на класс через атрибут ˍˍclassˍˍ: cls = func.__self__.__class__ итого полное имя будет выглядеть так full_name = '.'.join([ func.__self__.__class__.__module__, func.__qualname__] ) #tricks