Кеннет Рейтц – Автостопом по Python (страница 17)
Пользовательские классы в Python необходимо применять для того, чтобы аккуратно изолировать функции, имеющие контексты и побочные эффекты, от функций, которые имеют логику (называются
Рассмотрим преимущества чистых функций:
• их проще изменить или заменить, если нужно выполнить рефакторинг;
• их проще тестировать с помощью юнит-тестов, не нужно выполнять сложную настройку контекста и очищать данные после ее работы;
• ими проще манипулировать, их легче декорировать (к этой теме мы сейчас вернемся) и передавать.
В итоге для некоторых инфраструктур чистые функции выступают более эффективными строительными блоками, чем классы или объекты, поскольку не имеют контекста и побочных эффектов. В качестве примера рассмотрим функции ввода-вывода, связанные с каждым форматом файла в библиотеке Tablib (tablib/formats/*.py — мы опишем Tablib в следующей главе). Они являются чистыми функциями, а не частью класса, поскольку лишь считывают данные из отдельного объекта типа Dataset, в котором хранятся, либо записывают объект типа Dataset в файл. Но объект типа Session в библиотеке Requests (ее мы также рассмотрим в следующей главе) — это класс, поскольку он должен сохранять cookies и информацию об аутентификации, которая может пригодиться при обмене данными в ходе сессии HTTP.
Объектно-ориентированное программирование — полезная и даже необходимая парадигма программирования во многих случаях, например при разработке графических приложений для десктопа или игр, где вы можете манипулировать объектами (окнами, кнопками, аватарами, машинами), которые долго живут в памяти компьютера; является одной из причин использовать объектно-реляционное отображение, которое соотносит строки базы данных с объектами в коде. Этот вопрос рассматривается в разделе «Библиотеки для работы с базами данных» главы 11.
Декораторы
>>> def foo():
… ····print("I am inside foo.")
…
…
…
>>> import logging
>>> logging.basicConfig()
>>>
>>> def logged(func, *args, **kwargs):
… ····logger = logging.getLogger()
… ····def new_func(*args, **kwargs):
… ········logger.debug("calling {} with args {} and kwargs {}".format(
… ···················· func.__name__, args, kwargs))
… ········return func(*args, **kwargs)
… ····return new_func
…
>>>
>>>
… @logged
… def bar():
… ····print("I am inside bar.")
…
>>> logging.getLogger(). setLevel(logging.DEBUG)
>>> bar()
DEBUG: root: calling bar with args () and kwargs {}
I am inside bar.
>>> foo()
I am inside foo.
Этот механизм подойдет, чтобы изолировать основную логику функции или метода. Примером задачи, для которой нужно использовать декорирование, можно назвать запоминание или кэширование: вы хотите сохранить результат дорогой функции в таблице и использовать его вместо того, чтобы выполнять повторные вычисления. Очевидно, это не является частью логики функции. В PEP 3129 (https://www.python.org/dev/peps/pep-3129/), начиная с Python 3, декораторы также можно применять к классам.
Динамическая типизация
Python — динамически типизированный язык (в противоположность статически типизированным). Это означает, что переменные не имеют фиксированного типа. Переменные реализуются как указатели на объект, что дает возможность задать сначала значение 42, затем значение thanks for all the fish, а потом установить в качестве значения функцию.
Динамическая типизация, используемая в Python, зачастую считается недостатком, поскольку может привести к сложностям и появлению кода, для которого сложно выполнять отладку: если именованный объект может иметь в качестве значения множество разных вещей, разработчик поддерживающий код, должен отслеживать это имя в коде, чтобы убедиться, что оно не получило неуместное значение. В табл. 4.2 перечислены правила хорошего и плохого тона при именовании.
Повторное использование имен не повышает эффективность: операция присваивания все равно создаст новый объект. При росте сложности, когда операции присваивания разделены другими строками кода, сложно определить тип переменной.
В некоторых видах программирования, включая функциональное, не рекомендуется пользоваться возможностью повторного присваивания значения переменным. В Java вы можете указать, что переменная всегда будет содержать одно и то же значение после присваивания, с помощью ключевого слова final. В Python такого ключевого слова нет (это шло бы вразрез с его философией). Но присваивание значения переменной всего один раз может быть признаком дисциплинированности. Это помогает поддержать концепцию изменяемых и неизменяемых типов.
Pylint (https://www.pylint.org/) предупредит вас, если вы попытаетесь присвоить переменной, уже содержащей значение одного типа, значение другого типа.
Изменяемые и неизменяемые типы
В Python имеются два типа встроенных или определяемых пользователем[44] типов:
# Списки можно изменять
my_list = [1, 2, 3]
my_list[0] = 4
print my_list # [4, 2, 3] <- тот же список, измененный.
# Целые числа изменять нельзя
x = 6
x = x + 1 # Новое значение x занимает другое место в памяти.
•
•
Одно из последствий такого поведения — объекты изменяемых типов не могут быть использованы как ключи для словаря, ведь если их значение изменится, то изменится и его хэш (словари используют хэширование[45] для хранения ключей). Неизменяемым эквивалентом списка является кортеж. Он создается добавлением круглых скобок, например (1, 2). Кортеж нельзя изменить на месте, поэтому его можно использовать как ключ словаря.
Правильное применение изменяемых типов для объектов, которые по задумке должны изменяться (например, my_list = [1, 2, 3]), и неизменяемых типов для объектов, которые по задумке должны иметь фиксированное значение (например, islington_phone = ("220", "7946", "0347")), поможет другим разработчикам понять код.
В Python строки неизменяемы и это может удивить новичков. Попытка изменить строку вызовет ошибку:
>>> s = "I'm not mutable"
>>> s[1:7] = " am"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment