Продолжая использовать наш сайт, вы даете согласие на обработку файлов cookie, которые обеспечивают правильную работу сайта. Благодаря им мы улучшаем сайт!
Принять и закрыть

Читать, слущать книги онлайн бесплатно!

Электронная Литература.

Бесплатная онлайн библиотека.

Читать: Автостопом по Python - Unknown на бесплатной онлайн библиотеке Э-Лит


Помоги проекту - поделись книгой:

"What kind of talk is that?, look, why did he fall "

"flat on his back the moment I got 'im home?\n"

)

...

123

Распространенные подводные камни

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

Изменяемые аргументы по умолчанию

Наиболее частый сюрприз, с которым сталкиваются новые программисты Python, — это отношение Python к изменяемым аргументам по умолчанию в определениях функции.

Что вы написали:

def append_to(element, to=[]):

to.append(element)

return to

Чего вы ожидаете:

my_list = append_to(12)

print(my_list)

my_other_list = append_to(42)

print(my_other_list)

Новый список создается всякий раз, когда вызывается функция, если второй аргумент не предоставлен, поэтому результат работы функции выглядит так:

[12]

[42]

Что происходит на самом деле:

[12]

[12, 42]

Новый список создается при определении функции, он же используется в момент каждого последующего вызова: аргументы по умолчанию в Python оцениваются при определении функции, а не при каждом ее вызове (как это происходит, например, в Ruby).

Это означает, что если вы используете изменяемый по умолчанию аргумент и измените его, то он изменится для всех последующих вызовов этой функции.

Что вам нужно сделать вместо этого? Создавайте новый объект при каждом вызове функции, используя аргумент по умолчанию, чтобы показать, что аргумент не был передан (в качестве такого значения подойдет None): def append_to(element, to=None):

if to is None:

to = []

to.append(element)

return to

Когда подводный камень вовсе не подводный камень. Иногда вы можете намеренно задействовать (то есть использовать в качестве нормального варианта поведения) этот подводный камень, чтобы сохранять состояние между вызовами функции. Зачастую это делается при написании функции кэширования (которая сохраняет результаты в памяти), например: def time_consuming_function(x, y, cache={}):

args = (x, y)

if args in cache:

return cache[args]

# В противном случае функция работает с аргументами в первый раз.

# Выполняем сложную операцию...

cache[args] = result

return result

Замыкания с поздним связыванием

Еще один распространенный источник путаницы — способ связывания переменных в замыканиях (или в окружающей глобальной области видимости).

Что вы написали:

def create_multipliers():

return [lambda x : i * x for i in range(5)]

Чего вы ожидаете:

for multiplier in create_multipliers():

print(multiplier(2), end=" ... ")

print()

Список, содержащий пять функций, каждая из них имеет собственную замкнутую переменную i, которая умножается на их аргумент, что приводит к получению следующего результата: 0 ... 2 ... 4 ... 6 ... 8 ...

Что происходит на самом деле:

8 ... 8 ... 8 ... 8 ... 8 ...

Создаются пять функций, все они умножают х на 4. Почему? В Python замыкания имеют позднее связывание. Это говорит о том, что значения переменных, использованных в замыканиях, определяются в момент вызова внутренней функции.

В нашем примере, когда вызывается любая из возвращенных функций, значение переменной i определяется с помощью окружающей области видимости в момент вызова. К этому моменту цикл завершает свою работу и i получает итоговое значение 4.

Особенно неудобно то, что вам может показаться, будто ошибка как-то связана с лямбда-выражениями (https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions). Функции, создаваемые с помощью лямбда-выражений, не отличаются от других. Фактически то же самое поведение проявляется и при использовании самого обычного def: def create_multipliers():

multipliers = []

for i in range(5):

def multiplier(x):

return i * x

multipliers.append(multiplier)

return multipliers

Что вам нужно сделать вместо этого? Наиболее общее решение, возможно, станет «костылем» — временным вариантом устранения проблемы. Из-за уже упомянутого поведения Python, связанного с определением аргументов по умолчанию для функций (см. предыдущий пункт «Изменяемые аргументы функций»), вы можете создать замыкание, которое немедленно связывается со своими аргументами с помощью аргумента по умолчанию: def create_multipliers():

return [lambda x, i=i : i * x for i in range(5)]

Помимо этого вы можете использовать функцию functools.partial():

from functools import partial

from operator import mul

def create_multipliers():

return [partial(mul, i) for i in range(5)]

Когда подводный камень вовсе не подводный камень. Иногда нужно, чтобы замыкания вели себя подобным образом. Позднее связывание может быть полезным во многих ситуациях (например, в проекте Diamond, см. пункт «Пример использования замыкания (когда подводный камень вовсе не подводный камень)» на с. 136). Наличие уникальных функций в циклах, к сожалению, может привести к сбоям.

Структурируем проект

Под структурированием мы понимаем решения, которые вы принимаете по поводу функционирования вашего проекта. Его цель состоит в использовании возможностей Python для создания чистого и эффективного кода. На практике это означает, что логика и зависимости в коде и структуре файлов и каталогов прозрачны.

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

В книге Python Cookbook есть глава, посвященная модулям и пакетам (http://bit.ly/python-cookbook-ch10), в которой подробно описывается, как работают выражения __import__ и упаковка. Цель этого раздела — осветить основные аспекты системы модулей и импортирования Python, необходимые для структурирования ваших проектов. Далее мы рассмотрим разные подходы к сборке кода, который легко будет расширять и тестировать.

Благодаря тому, как в Python налажен процесс импортирования и разбиения на модули, структурировать проект довольно просто: существует всего несколько ограничений, модель для импортирования также нетрудно освоить. Поэтому перед вами стоит исключительно архитектурная задача — создать различные части проекта и продумать их взаимодействие.

Модули

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

Например, если один уровень проекта предназначен для взаимодействия с пользователем, а другой обрабатывает данные на низком уровне, наиболее логичным способом разделения этих двух слоев является размещение всей функциональности, связанной со взаимодействием, в одном файле, а всех низкоуровневых операций — в другом. Такая группировка разметит их в два разных модуля. Файл для взаимодействия затем импортирует файл для низкоуровневой обработки с помощью выражения import module или from module import attribute.

Как только вы пустите в ход выражение import, вы начнете пользоваться модулями. Модули могут быть либо встроенными (вроде os и sys), либо сторонними пакетами, установленными в среде (вроде Requests или NumPy), либо внутренними модулями проекта.

Далее показан пример некоторых выражений import (подтверждается, что импортированный модуль является объектом Python со своим типом данных): >>> import sys # built-in module

>>> import matplotlib.pyplot as plt # сторонний модуль

>>>

>>> import mymodule as mod # внутренний модуль проекта

>>>

>>> print(type(sys), type(plt), type(mod))

<class 'module'> <class 'module'> <class 'module'>

В соответствии с руководством по стилю кода (https://www.python.org/dev/peps/pep-0008/) присваивайте модулям короткие имена, которые начинаются со строчной буквы. И убедитесь, что не использовали специальные символы вроде точки (.) или вопросительного знака (?), поскольку это может нарушить вид Python для модулей. Поэтому вам следует избегать имен файла вроде my.spam.py42 (Python попытается найти файл spam.py в каталоге с именем my, а это неверно). В документации Python (http://docs.python.org/tutorial/modules.html#packages) более подробно описывается нотация с точкой.

Импортирование модулей. Помимо следования некоторым ограничениям в именовании, для использования файла Python в качестве модуля не требуется больше ничего особенного. Однако понимать механизм импортирования будет нелишним. Во-первых, выражение import modu начнет искать определение modu в файле с именем modu.py в том же каталоге, где находится и вызывающая сторона, если такой файл существует. При неудаче интерпретатор Python будет рекурсивно искать файл modu.py в пути поиска Python (https://docs.python.org/2/library/sys.html#sys.path) и сгенерирует исключение ImportError, если не найдет. Путь поиска зависит от платформы и включает в себя определенные пользователем или системой каталоги, указанные в переменной среды $PYTHONPATH (или %PYTHONPATH% в Windows). Ее можно просмотреть или изменить в сессии Python: import sys

>>> sys.path

[ '', 'currentabsolute/path', 'etc']

# Реальный список содержит каждый путь, где выполняется поиск,

# когда вы импортируете библиотеки в Python в том порядке,

# в котором они проверяются.

Как только файл modu.py будет найден, интерпретатор Python запустит модуль в ограниченной области видимости. Любое выражение верхнего уровня в файле modu.py будет выполнено, включая другие выражения импорта, если таковые существуют. Определения функций и классов хранятся в словаре модуля. Наконец, переменные функции и классы модуля будут доступны вызывающей стороне с помощью пространства имен модуля — основной концепции программирования, которая особенно эффективна в Python. Пространства имен предоставляют область видимости, содержащую именованные атрибуты, которые видны друг другу, но к ним нельзя получить доступ из-за пределов пространства имен.

Во многих языках директива заставляет препроцессор, по сути, скопировать содержимое включаемого файла в код вызывающей стороны. В Python все происходит иначе: включаемый код изолируется в пространстве имен модуля. Результатом выполнения выражения import modu станет объект модуля с именем modu, который будет находиться в глобальном пространстве имен, его атрибуты будут доступны с помощью точечной нотации. Например modu.sqrt — это объект sqrt, определенный внутри файла modu.py. Это означает, что вам, как правило, не нужно волноваться о том, что включаемый код может делать что-то нежелательное, к примеру переопределять существующую функцию с тем же именем.

Инструменты для пространств имен



Поделиться книгой:

На главную
Назад