Интересности и полезности 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.

Всего в языке три вида строк: обычные, обозначаемые кавычками без префиксов, сырые\не обрабатываемые(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")        # однако он не получил большого распространения print(s.substitute(res = [3, 4])) 

Теперь добавили ещё и 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 в кеш, основанный на словаре, с другой стороны, если вы добавите в кортеж список, то такой кортеж тоже нельзя будет просто так добавить в словарь.

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

Обсуждали создание типа frozendict, однако пока его не добавили (хотя как минимум одно применение ему уже есть — в качестве 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): 

Внимательнее рассмотрим, чем просто * отличается от *args.

 def func(first, second, *, kwonly=True):     print(first, second, kwonly)   def func2(first, second, *args, kwonly=True):     print(first, second, *args, kwonly)   func(1)           #-> TypeError: func() missing 1 required positional argument: 'second' func(1, 2)        #-> 1 2 True func(1, 2, False) #-> TypeError: func() takes 2 positional arguments but 3 were given                   # используя * в объявлении вы укажите, что                   # ваша функция должна быть вызвана с двумя                   # позиционными параметрами func(1, 2, kwonly=False) #-> 1 2 False  func2(1, 2, False) #-> 1 2 False True                    # *args заберёт в себя все позиционные                    # параметры, то есть вашу функцию может будет                    # вызывать с неограниченным (>2) числом                    # параметров 

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

 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 [] 
FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *