[Из песочницы] Интересности и полезности python
Я уже несколько лет программирую на python, однако, недавно осознал, что множество полезных приёмов и интересных моментов прошли мимо меня, возможно, я не один такой, поэтому решил перечислить их здесь, надеюсь, данные приёмы пригодятся кому-то в работе или побудят познакомиться с этим языком поближе.
Как и во многих языках в python 1 эквивалентно True, а 0 — False, то есть
1 == True.
Казалось бы, и что в этом такого? Однако, это имеет некоторые побочные эффекты, связанные с тем, что одинаковые объекты обязаны иметь одинаковые хеши, соответственно у вас не получится запихать в один словарь ключ 1 и True.
>>> a = {1: "one", 0: "zero", True: "true", False: "false"}
# -> {1: 'true', 0: 'false'}
Так же это разрешает следующие операции:
>>> print(2 * False + True)
# -> 1
В данном примере строки использовались в качестве значений словаря, однако, зачастую хочется использовать строки в качестве ключей словаря, меня всегда раздражало, что при создании словаря с помощью фигурных скобок, строки нужно указывать в кавычках, хотелось бы их опустить, это возможно, если создавать словарь через конструктор dict ().
>>> {"one": 1, "two": 2, "three": 3} == dict(one=1, two=2, three=3)
# -> True
Кроме того, с помощью фигурных скобок создаются не только словари, но и множества (set).
>>> a = {1, 2, 3}
Для объединения двух множеств мне почему-то хочется воспользоваться оператором +, наверно, из-за способа конкатенации строк. Однако, python не поддерживает данный оператор для множеств. Но разумеется, это не значит, что нам всегда придётся пользоваться функциями, создатели подошли к данному вопросу более системно и добавили в язык поддержку основных операций над множествами (а не только объединения) и «повесили» их на логические операторы.
a = {1, 2, 3}
b = {0, 2, 4}
print(a & b) # -> {2}
print(a | b) # -> {0, 1, 2, 3, 4}
print(a ^ b) # -> {0, 1, 3, 4}
print(a - b) # -> {1, 3}, однако один арифметический
# оператор всё же оставили
Продолжая разговор про словари, начиная с версии 3.7 спецификацией языка гарантируется, что словари сохраняют порядок вставки элементов, OrderedDict больше не нужен.
www.python.org/downloads/release/python-370
mail.python.org/pipermail/python-dev/2017-December/151283.html
d = dict(zero='Cero', one='Uno', two='Dos', three='Tres', four='Cuatro',
five='Cinco', six='Seis', seven='Siete', eight='Ocho', night='Nueve')
for index, (key, value) in enumerate(d.items()):
print(f"{index} is {key} in England and {value} in Spain")
Обратите внимание не строку вывода, она начинается с префикса f — это особый тип строк, введённый в python 3.6.
Всего в python три вида строк: обычные, обозначаемые кавычками без префиксов, сырые\не обрабатываемые (raw), в которых спец-символы, вроде, \n не обрабатываются и вставляются как текст и собственно f-строки.
Созданы они были для упрощения вывода, python поддерживает огромное количество способов вывода:
print("result" + str(2)) # Простая конкатенация строк, python не осуществляет
# автоматическое приведение всех аргументов к
# строковому типу, это остаётся за программистом
print("result", 2) # print может принимать несколько аргументов через запятую,
# в таком случае они будут выводиться через пробел,
# вам не нужны преобразовывать выводимые объекты в строку,
# в отличие от предыдущего способа
print("result %d" % 2) # %-синтаксис, сделан по аналогии с языком C.
print("result %d %.2f" % (2, 2)) # https://docs.python.org/3.4/library/string.html#formatspec
print("result %(name)s" % {"name": 2}) # также разрешено создавать именованные метки
print("{}".format(2)) # У класса строки есть метод format()
# он позволяет опускать тип выводимой переменной
print("{0} {1} {0}".format(1, 2)) # так же можно указать номер переменной и таким образом
# вывести её два раза
# нумерация начинается с нуля
# если число переданных переменных меньше использованных в выводе, будет сгенерированно исключение
print("{} {}".format(2)) # -> IndexError: tuple index out of range
print("{0} {0}".format(2, 3)) # -> 2 2 Однако если передано слишком много переменных
# код отработает без ошибок
from math import pi # при таком выводе так же поддерживаются строки формата
print("{:.2f}".format(pi)) # -> 3.14
from string import Template # возможен и такой способ вывода
s = Template("result $res") # однако он не получил большого распространения
Теперь добавили ещё и f-строки. В них доступны любые переменные из области видимости, можно вызывать функции, получать элементы по ключу, кроме того, они поддерживают строки формата.
from math import pi
result = 4
name = "user"
print(f"{name:84s} pi= {pi:.2f}, result={result}, {name[2]}")
# -> user pi= 3.14, result=4, e
from datetime import datetime
print(f"{datetime.now():%Y:%m-%d}")
Кроме того они быстрее всех остальных способов вывода, так что, если вам доступен python3.6 рекомендуется использовать именно их.
Одна из наикрутейших фишек python — в нём упаковываются и распаковываются не объекты и примитивы, а параметры и коллекции.
def func(*argv, **kwargs)
Однако, есть один архитектурный недостаток в реализации:
- argv — кортеж, его значения нельзя изменять, нельзя добавлять или удалять значения
- kwargs — словарь, изменяемый, поэтому кеширование невозможно
Недостаток, конечно, не большой, но всё же неприятно, что нельзя напрямую передавать kwargs в кеш, основанный на словаре, с другой стороны, если вы добавите в кортеж список, то такой кортеж тоже нельзя будет просто так добавить в словарь.
Множества тоже создаются на основе хеш-таблицы, это значит, что значения должны быть хешируемы, кроме того само множество является изменяемым и не хешируемым типом, есть специальный тип frosenset — не изменяемое множество (не спрашивайте меня, зачем оно нужно).
Обсуждали создание типа frosendict, однако пока его не добавили (хотя как минимум одно применение ему уже есть — в качестве kwargs). За неизменяемый словарь приходится отдуваться namedtuple. А также за записи и простенькие классы.
Кто в студенческие\школьные годы писал циклы для вывода значений массива и бесился из-за запятой в конце, каждый раз решал, забить или переписать, чтобы было красиво, и только на курсе 2–3 узнал о методе join? Или я один такой?
Одна из неприятных особенностей метода join у строк — он работает только с элементами-строками, если в коллекции есть хоть одна не строка приходится использовать генераторное выражение, что выглядит слишком сложным решением такой простой задачи, однако, есть способ упростить вывод значений списков (без скобок).
a = list(range(5))
print(" ".join(a)) # -> TypeError: sequence item 0: expected str instance, int found
print(" ".join(str(i) for i in a)) # -> 0 1 2 3 4
print(*a) # -> 0 1 2 3 4
Так как строки — тоже коллекции, то их так же можно «джойнить».
print('-'.join("hello")) # -> h-e-l-l-o
Рассмотрим строку из предыдущего примера.
print(" ".join(str(i) for i in a)) # -> 0 1 2 3 4
Генераторное выражение передано в ф-цию join без каких-нибудь скобок, круглые скобки можно опускать для упрощения чтения кода. Python заботится о выразительности.
print(sum(i**2 for i in range(10))) # -> 285
Кроме того, круглые скобки можно опускать и при создании кортежей:
article = "python", 2018, "LinearLeopard" # объявление кортежа
theme, year, author = "python", 2018, "LinearLeopard"# распаковка кортежа
theme, year, _ = "python", 2018, "LinearLeopard" # слева и справа должно
# находиться одинакове число
# переменных, можно подчеркнуть,
# что вам какая-то не нужно,
# обозначив её через
# подчёркивание
theme, _, _ = "python", 2018, "LinearLeopard" # имена могут повторяться
theme, * author = "python", 2018, "LinearLeopard" # можно объявить жадный
# параметр, который съест
# все неподходящие,
# разумеется, допустим
# только один
# жадный оператор
Звёздочку можно использовать и в объявления функций, таким образом можно создать параметры, которые можно указать только по ключу.
def sortwords(*wordlist, case_sensitive=False):
Можно передавать в ф-цию сколько угодно параметров без боязни, что один из них будет воспринят как значение параметра case_sensitive.
Можно и так.
def func(first, second, *, kwonly):
С параметрами по умолчанию связана одна интересная особенность: они вычисляются на этапе компиляции модуля в байт код, так что лучше не использовать там изменяемые типы. Объявим функцию, которая добавляет элемент в конец списка, если второй аргумент опущен, функция возвращает новый список в котором содержится только этот элемент.
def add_to(elem, collection=[]):
collection.append(elem)
return collection
a = ["a", "c"]
print(add_to("b", a)) # -> ['a', 'c', 'b']
print(add_to("a")) # -> ['a']
print(add_to("b")) # -> ['a', 'b'] Откуда здесь 'a'?
Значения по умолчанию ф-ция хранит в поле __defaults__, можно в любой момент узнать, что там находится.
print(add_to.__defaults__) # -> (['a', 'b'],)
Так как аргумент по умолчанию (пустой список) создался в момент старта программы и не пересоздаётся каждый раз заново, мы получили именно такое поведение.
Исправить подобное поведение можно, если сделать значение по умолчанию неизменяемым типом,
а список создавать в теле функции:
def add_to(elem, collection=None):
collection = collection or []
collection.append(elem)
return collection
Обратите внимание на команду
collection = collection or []
это более короткий (и менее понятный, хотя не для всех) аналог
collection = collection if collection else []