20 малоизвестных фич и особенностей Python - Библиотека программиста
proglib.ioНебольшая подборка полезных фич и особенностей Python, о которых вы, возможно, никогда не слышали.
Отладка регулярных выражений
Регулярные выражения Python – мощный и полезный инструмент, но отлаживать их – то еще удовольствие. Оказывается, любую регулярку можно визуализировать в виде дерева синтаксического анализа. Эта возможность языка пока экспериментальная, за нее отвечает флаг re.DEBUG
в методе re.compile
.
Посмотрим на регулярное выражение для поиска тегов font
. С ним что-то не так.
re.compile("^\[font(?:=(?P<size>[-+][0-9]{1,2}))?\](.*?)[/font]", re.DEBUG) at at_beginning literal 91 literal 102 literal 111 literal 110 literal 116 max_repeat 0 1 subpattern None literal 61 subpattern 1 in literal 45 literal 43 max_repeat 1 2 in range (48, 57) literal 93 subpattern 2 min_repeat 0 65535 any None in literal 47 literal 102 literal 111 literal 110 literal 116
Теперь ясно, что именно. В закрывающем дескрипторе [/font]
не экранированы квадратные скобки, поэтому он воспринимается не как тег, а как группа символов.
re.compile(""" ^ # начало строки \[font # тег font (?:=(?P<size> # опционально [font=+size] [-+][0-9]{1,2} # определение размера ))? \] # конец тега (.*?) # содержимое тега \[/font\] # закрывающий тег """, re.DEBUG|re.VERBOSE|re.DOTALL)
Выражения-генераторы
В Python есть очень удобные генераторы коллекций (списков, множеств, словарей), которые позволяют легко и быстро создавать отфильтрованные коллекции значений. Например, вот так можно создавать Python списки:
numbers = range(10) x = [n for n in numbers if n % 2 == 0] print(x) # 0 2 4 6 8
А еще есть выражения-генераторы, которые не загружают коллекцию в память целиком, а выдают лишь один элемент по требованию. В некоторых случаях это позволяет существенно сэкономить расходы памяти. Единственное отличие в синтаксисе – это круглые скобки:
y = (n for n in numbers if n % 2 == 0) print(y) # <generator object>
Ряд особенностей Python генераторов:
- невозможно получить их длину;
- нельзя сделать срез элементов, перемотать или получить случайный элемент по его индексу;
- функция print выводит объект генератора, а не список элементов.
Зато их удобно использовать в различных конструкциях, где требуется итерируемый объект. В выражения-генераторы можно включать множественные условия отбора значений и сочетать несколько циклов:
n = ((a,b) for a in range(0,2) for b in range(4,6)) for i in n: print(i) # (0, 4) # (0, 5) # (1, 4) # (1, 5)
Подводные камни дефолтных аргументов
Устанавливая значение аргументов функции по умолчанию, будьте очень осторожны:
def foo(x = []): x.append(1) print(x) foo() # [1] foo() # [1, 1] foo() # [1, 1, 1]
Вряд ли вы хотели, чтобы список x изменялся при каждом вызове функции. Так происходит из-за того, что дефолтные параметры хранятся в неизменном кортеже в атрибуте foo.func_defaults
, который создается в момент определения функции.
Вместо мутабельных списков лучше использовать значение None
, а список присваивать в x
уже внутри функции:
def foo2(x=None): if x is None: x=[] x.append(1) print(x) foo2() # [1] foo2() # [1] foo2() # [1]
Передача значений в генератор
Язык программирования Python поддерживает генераторы – функции со множественными точками входа. В генератор можно передать значение на каждом шаге работы, что очень удобно, если приходится работать с динамическими данными:
def my_generator(): a = 5 # значение, которое вернется при первом вызове while True: f = (yield a) # вернуть a и получить новое значение f if f is not None: a = f # сохранить новое значение
Это бессмысленная в целом функция, которая просто сохраняет полученное значение и возвращает его при следующем вызове.
В старом стандарте языка был метод my_generator.next(value)
, который сразу возвращал текущее значение генератора и принимал новое. В Python 3 необходимо использовать два метода: next(my_generator)
и my_generator.send(value)
.
g = my_generator() # создать новый генератор print(next(g)) # первый возврат по умолчанию (5) g.send(100) # передача нового значения в f print(next(g)) # получение переданного ранее значения (100) g.send(42) print(next(g)) # 42
Фигурные скобки
Python использует для форматирования кода не скобки, как C-подобные языки (Java, C#, PHP), а табуляцию. Если такой синтаксис вам непривычен, используйте пакет braces
из модуля __future__
.
from __future__ import braces
Эта фича очень спорная. Настоящие питонисты возмущены самим наличием подобной Python библиотеки.
Шаг среза
Третий аргумент slice-оператора в Python определяет шаг среза. По умолчанию он равен единице, поэтому в итоговый срез попадают все элементы диапазона подряд.
a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] print(a[2:8:]) # [3, 4, 5, 6, 7, 8]
А можно взять, например, каждый второй элемент:
print(a[2:8:2]) # [3, 5, 7]
Если передать третьим параметром -1
, счет пойдет в обратном порядке. Так можно легко развернуть список или строку.
print(a[::-1]) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
Декораторы
Декоратор – это обертка для функции, позволяющая изменить некоторым образом ее поведение. Например, просто распечатать аргументы перед вызовом:
def print_args(function): def wrapper(*args, **kwargs): print('args:', args) print('kwargs:', kwargs) return function(*args, **kwargs) return wrapper
Теперь необходимо передать функции print_args
другую функцию, аргументы которой необходимо распечатать:
def write(a, b): print(a, b) write_with_print = print_args(write) write_with_print('foo', 'bar') # args: ('foo', 'bar') # kwargs: {} # foo bar
Все работает правильно, но приходится создавать новую функцию и вызывать именно ее.
В Python работа с декораторами устроена гораздо удобнее. Вы можете сохранить исходное имя функции и ее подпись при интроспеции:
@print_args def write(a, b): print(a, b) write('foo', 'bar') # args: ('foo', 'bar') # kwargs: {} # foo bar
Отсутствующие элементы словарей
В Python 2.5 у словарей появился специальный метод __missing__
. Он вызывается при обращении к отсутствующим элементам:
class MyDict(dict): def __missing__(self, key): self[key] = rv = [] return rv m = MyDict() m["foo"].append(1) m["foo"].append(2) dict(m) # {'foo': [1, 2]}
Примерно то же самое делает подкласс defaultdict
: он вызывает для несуществующих элементов функцию без аргументов.
from collections import defaultdict m = defaultdict(list) m["foo"].append(1) m["foo"].append(2) print(m) # {'foo': [1, 2]}
Многострочные регулярные выражения
Одна из самых приятных особенностей Python – возможность разбить длинные регулярки на несколько строк, добавить комментарии и сделать их более читаемыми.
pattern = """ ^ # beginning of string M{0,4} # thousands - 0 to 4 M's (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), # or 500-800 (D, followed by 0 to 3 C's) (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), # or 50-80 (L, followed by 0 to 3 X's) (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), # or 5-8 (V, followed by 0 to 3 I's) $ # end of string """ re.search(pattern, 'M', re.VERBOSE)
Многострочные регулярные выражения Python можно создавать и без re.VERBOSE
, используя обычную конкатенацию строчных литералов:
pattern = ( "^" # beginning of string "M{0,4}" # thousands - 0 to 4 M's "(CM|CD|D?C{0,3})" # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), # or 500-800 (D, followed by 0 to 3 C's) "(XC|XL|L?X{0,3})" # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), # or 50-80 (L, followed by 0 to 3 X's) "(IX|IV|V?I{0,3})" # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), # or 5-8 (V, followed by 0 to 3 I's) "$" # end of string ) print pattern # ^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$
Кроме того, совпадения можно именовать:
p = re.compile(r'(?P<word>\b\w+\b)') m = p.search( '(((( Lots of punctuation )))' ) m.group('word') # 'Lots'
Распаковка аргументов
Параметры можно передать в функцию в виде списка или словаря и распаковать их автоматически, используя синтаксис *
и **
.
def draw_point(x, y): # do some magic point_foo = (3, 4) point_bar = {'y': 3, 'x': 2} draw_point(*point_foo) draw_point(**point_bar)
Эта фича языка очень полезна, так как в Python списки, кортежи и словари широко используются в качестве контейнеров.
Динамическое создание типов
Программирование на Python допускает создание новых типов прямо во время выполнения программы.
NewType = type("NewType", (object,), {"x": "hello"}) n = NewType() print(n.x) # "hello"
Это то же самое, что и:
class NewType(object): x = "hello" n = NewType() print(n.x) # "hello"
Это не самая полезная и часто используемая из особенностей Python, но полезно знать, что она существует. Например, ее можно использовать для динамического определения набора необходимых атрибутов.
Метод словарей get
Если вы обратитесь к несуществующему ключу словаря dict[key]
, то получите исключение. Эту проблему можно решить с помощью метода
, который вернет dict.get(key)
None
для несуществующих ключей. Вторым параметром ему можно передать значение по умолчанию:
dict = { "a": 1, "b": 2 } print(dict.get("c")) # None print(dict.get("c", 0)) # 0
Это удобно, например, при арифметических операциях.
Дескрипторы
Атрибуты можно превратить в дескрипторы, изменив их стандартное поведение с помощью методов __get__
, __set__
или __delete__
. Таким образом можно, например, запретить перезапись или удаление свойства.
Создадим такой дескриптор, используя для удобства декоратор:
class MyDescriptor(object): def __init__(self, fget): self.fget = fget def __get__(self, obj, type): print("__get__({}, {})".format(obj, type)) return self.fget(obj) class MyClass(object): @MyDescriptor def foo(self): print("Foo!") obj = MyClass() obj.foo # __get__(<__main__.MyClass object ...>, <class '__main__.MyClass'>) # Foo!
Теперь при обращении через точку к дескриптору foo
, управление передается его методу __get__
, который сначала печатает строчку с данными дескриптора, а затем вызывает его «родной» геттер (выполняется код функции foo
).
Дескрипторы – довольно сложная фича, но вам стоит разобраться в ней, чтобы глубже понимать как работает язык программирования Python.
Doctest: документация + юнит-тестирование
Модуль doctest
находит в коде фрагменты, похожие на интерактивные сессии, и выполняет их, чтобы проверить заявленный результат. Фактически, с его помощью можно создать «исполняемую документацию».
Вот официальный пример работы doctest
:
""" Это модуль-пример. Этот модуль предоставляет одну функцию - factorial(). Например, >>> factorial(5) 120 """ def factorial(n): """Возвращает факториал числа n, которое является числом >= 0. Если результат умещается в int, возвращается int. Иначе возвращается long. >>> [factorial(n) for n in range(6)] [1, 1, 2, 6, 24, 120] >>> [factorial(long(n)) for n in range(6)] [1, 1, 2, 6, 24, 120] >>> factorial(30) 265252859812191058636308480000000L >>> factorial(30L) 265252859812191058636308480000000L >>> factorial(-1) Traceback (most recent call last): ... ValueError: n must be >= 0 Можно вычислять факториал числа с десятичной частью, если она равна 0: >>> factorial(30.1) Traceback (most recent call last): ... ValueError: n must be exact integer >>> factorial(30.0) 265252859812191058636308480000000L Кроме того, число не должно быть слишком большим: >>> factorial(1e100) Traceback (most recent call last): ... OverflowError: n too large """ import math if not n >= 0: raise ValueError("n must be >= 0") if math.floor(n) != n: raise ValueError("n must be exact integer") if n+1 == n: # перехватываем значения типа 1e300 raise OverflowError("n too large") result = 1 factor = 2 while factor <= n: result *= factor factor += 1 return result if __name__ == "__main__": import doctest doctest.testmod()
Чтобы увидеть результат, запустите этот модуль прямо из командной строки с флагом -v
. Вы получите нечто вроде:
$ python example.py -v Trying: factorial(5) Expecting: 120 ok Trying: [factorial(n) for n in range(6)] Expecting: [1, 1, 2, 6, 24, 120] ok
Именованное форматирование строк
В Python 3 для форматирования строк используется метод format:
print("The {} is {}".format('answer', 42))
Передаваемые в строку параметры можно именовать для удобства:
print("The {foo} is {bar}".format(bar=42, foo='answer'))
Поиск модулей
Путь поиска импортируемых модулей в Python выглядит так:
- Домашний каталог программы, который может отличаться от текущего рабочего каталога
- Адреса из переменной окружения PYTHONPATH.
- Каталоги стандартной Python библиотеки, которые устанавливаются автоматически.
- Директории, перечисленные в *.pth файлах.
- Каталог site-packages, в котором автоматически размещаются все сторонние расширения.
try-except-else
В конструкцию try-except
можно добавить также блок else
. Он отработает только в случае выполнения кода без ошибок:
try: a = float(input("Введите число: ")) print(100 / a) except ValueError: print ("Это не число!") except ZeroDivisionError: print ("На ноль делить нельзя!") except: print ("Неожиданная ошибка.") else: print ("Код выполнился без ошибок")
Использовать блок else
предпочтительнее, чем добавлять дополнительный код в блок try
. Это позволяет избежать случайного перехвата исключений, которые не были вызваны кодом, защищенным конструкцией try-except
.
Ререйз исключений
Применив оператор raise
без параметров внутри обработчика ошибок, вы можете повторно «поднять» пойманное исключение с сохранением его оригинальной трассировки стека. Это полезно, если пойманное исключение должно обрабатываться на верхних уровнях программы:
try: some_operation() except SomeError as e: if is_fatal(e): raise handle_nonfatal(e)
Оригинальную трассировку можно получить с помощью sys.exc_info()
.
Автодополнение для интерактивного интерпретатора
Одна из немногочисленных неприятных особенностей Python консоли: отсутствие встроенного автодополнения вводимых команд. Эту проблему решает модуль rlcompleter
:
try: import readline except ImportError: print "Unable to load readline module." else: import rlcompleter readline.parse_and_bind("tab: complete")
Теперь с помощью клавиши TAB вы можете быстро подобрать нужные атрибуты:
>>> class myclass: ... def function(self): ... print "my function" ... >>> class_instance = myclass() >>> class_instance.<TAB> class_instance.__class__ class_instance.__module__ class_instance.__doc__ class_instance.function >>> class_instance.f<TAB>unction()
import this
Главный священный текст любого питониста всегда даст ценный совет, подкинет полезную идею и подбодрит уставшего разработчика. Просто выполните команду import this
.
Надеемся, вы узнали что-то новое о возможностях и особенностях Python. Своими открытиями делитесь в комментариях.
Лучшие материалы и книги по Python
Интересуетесь программированием на Python?
Подпишитесь на нашу рассылку, чтобы получать больше интересных материалов:
И не беспокойтесь, мы тоже не любим спам. Отписаться можно в любое время.