Умножение списка на число

Студент Макс узнал, что в Python умножать можно не только числа, но и другие объекты, например, строку на число:

>>> "Max" * 3
'MaxMaxMax'

«Вау!» — подумал Макс — «А что если умножить список на число?»:

>>> [42, 26] * 3
[42, 26, 42, 26, 42, 26]

Значит можно создать двумерный массив очень кратко и элегантно?

>>> [[]] * 3
[[], [], []]

Заполнить его:

arr = [[]] * 3
arr[0].append(10)
arr[1].append(20)
arr[2].append(30)

Макс ожидал получить:

[[10], [20], [30]]

А вышло:

[[10, 20, 30], [10, 20, 30], [10, 20, 30]]

😯 Как же так?! Дело в том, что умножение списка на число не копирует сам объект, а лишь ссылку на него. Все три элемента arr ссылаются на один и тот же список. Легко проверить, сравнив адреса объектов:

>>> arr[0] is arr[1]
True
>>> id(arr[0]), id(arr[1])
(4400840776, 4400840776)
Диаграмма: все элементы arr указывают на один и тот же список.

Аналогично в случае классов:

class Dummy: ...
arr = [Dummy()] * 2
arr[0].x = 10
arr[1].x = 20
print(arr[0].x, arr[0] is arr[1])  # 20 True

А вот с числами, строками и кортежами умножение списка будет работать как ожидал Макс, потому что это неизменяемые типы. Вот такая тонкость, которую нужно знать. Максу следовало бы написать так:

arr = [[] for _ in range(3)]  
arr[0].append(10)
arr[1].append(20)
arr[2].append(30)
>>> arr
[[10], [20], [30]]

Менее кратко, но зато работает без сюрпризов: каждую итерацию создается новый пустой список.

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Игра понг ASCII на Python

В продолжение последней темы написал сегодня с утра игру «Понг» для терминала. Обошелся только встроенными модулями. Для графики и ввода использовал модуль curses (обертка над ncurses). Исходный код доступен здесь. Благодаря современным чудо-технологиям в игру можно поиграть прямо в браузере, хоть она и работает не очень стабильно (зависит от вашего интернет соединения). Управление: W — вверх, S — вниз (только английская раскладка).

Скриншот текстовой игры ПОНГ

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

​​Размер окна терминала

Для оформления информации в терминале часто нужно знать размеры окна терминала (количество колонок и строк). Во встроенном модуле shutil можно найти функцию get_terminal_size, которая возвращает именованный кортеж:

>>> shutil.get_terminal_size()
os.terminal_size(columns=208, lines=25)

Или

>>> cols, lines = shutil.get_terminal_size()
>>> cols, lines
(208, 25)

Или

>>> tsz = shutil.get_terminal_size()
>>> tsz.columns, tsz.lines
(208, 25)
Текст с разделителями их дефисов

Например, сделаем разделитель с заголовком, как на фото.

1. Будем форматировать по центру значение в строку с заданной длинной, а пустые места заполнить каким-то символом. Для этого нужен особый формат:

>>> '{:^10}'.format('love')
'   love   '
>>> '{:-^10}'.format('life')
'---life---'

Знак после двоеточия – заполнитель (если его нет, то пробел); а число после крышечки – желаемая ширина строки. Крышечка указывает, что форматирование будет по центру.

2. Так как число неизвестно заранее, то его тоже надо вставить с помощью format, предварительно экранировав фигурные скобки (двойная фигурная скобка в формате воспринимается как соотвествующий символ, а не как место для подстановки):

>>> '{{:-^{}}}'.format(10)
'{:-^10}'
>>> '{{:-^{}}}'.format(10).format('love')
'---love---'
>>> '{{:-^{}}}'.format(shutil.get_terminal_size().columns).format('love')
'---------------------------love----------------------------'

3. Текст, что по центру сделаем заглавным, а также каждый символ отделим пробелами, чтобы заголовок казался заметнее:

>>> ' '.join('love'.upper())
'L O V E'
>>> ' ' + ' '.join('love'.upper()) + ' '
' L O V E '

4. Соеденим все вместе в однострочник, добавив print к итоговой строке:

def sep(s): 
    print('{{:-^{}}}'.format(shutil.get_terminal_size().columns).format(' ' + ' '.join(str(s).upper()) + ' '))

Хочу уточнить, что shutil.get_terminal_size() не всегда способна определить размер терминала. Например, когда собственно и нет никакого окна терминала, а лишь есть поток вывода как при выводе в файл или в канал. У потока вывода нет таких характеристик как размер окна. При выполнении функции в среде PyCharm функция вернет размер по умолчанию (80 на 25), и разделитель будет не на всю ширину области вывода, если она шире 80 символов.

😈 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈 

Полезные декораторы

Как и обещал, приведу список полезных декораторов. Среди них как стандартные поставляемые вместе с Python, так и декораторы из других библиотек и исходные коды прочих интересных декораторов.

Начнем с самых известных.

Continue Reading Полезные декораторы

Coffee, tee or me

Итератор, как известно, выдает значения по одному (например, методом next), и его нельзя «отмотать» назад. Это означает, что получать все подряд значения из итератора может только один потребитель. Однако, если несколько потребителей хотят читать из одного итератора, его можно разделить с помощью функции itertools.tee. Кстати, tee переводится как тройник (тройник похож на букву Т).

tee принимает исходный итератор и число – количество новых итераторов, на которые разделится исходный, а возвращает кортеж из новых итераторов. При этом извлечение значений из одного из итераторов не влияет на остальные.

📎 Пример:

from itertools import tee

def source():
    for i in range(5):
        print(f'next is {i}')
        yield i

# три потребителя
coffee, tea, me = tee(source(), 3)

# первый берет два числа
next(coffee); next(coffee)
# второй одно
next(tea)
# третий - все
for i in me:
    ...

Вывод:

next is 0
next is 1
next is 2
next is 3
next is 4

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

Предостережения:

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

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

Полный пример кода (плюс моя реализация tee на скорую руку) – по ссылке.

🐉 Специально для канала @pyway. Подписывайтесь на мой канал в Телеграм @pyway 👈