В главе 2 мы изучили основы IPython shell и Jupyter notebook. В этой главе мы рассмотрим более продвинутые функции IPython, которые можно использовать как в командной строке, так и в Jupyter.
IPython поддерживает небольшую базу данных на диске, которая сохраняет каждую выполненную команду. Это полезно для:
Эти функции особенно полезны в shell, поскольку notebook по своей природе предназначен для размещения ввода и вывода кода в каждой ячейке.
С помощью IPython вы можете искать и выполнять ранее использованные команды или другие команды. Эта функция очень полезна, когда вам нужно повторно выполнить одну и ту же команду, например, команду %run или другой код. Предположим, вы должны выполнить:
In[7]: %run first/second/third/data_script.py
Если после успешного выполнения вы проверяете результат и обнаруживаете ошибку в расчётах, то после исправления проблемы и модификации data_script.py, вы можете ввести несколько команд %run, а затем нажать Ctrl+P или стрелку вверх. Это позволит вам искать команды в истории. Повторное нажатие Ctrl+P или стрелки вверх будет продолжать поиск команд. Если вы хотите выполнить нужную команду, не бойтесь. Вы можете нажать Ctrl-N или стрелку вниз, чтобы перейти к предыдущей команде в истории. После нескольких таких действий вы сможете легко нажимать эти клавиши!
Ctrl-R предоставляет функцию частичного поиска, аналогичную readline в Unix-подобных оболочках (например, bash). На Windows эта функция эмулируется IPython. Чтобы использовать эту функцию, сначала нажмите Ctrl-R, а затем введите символы, которые вы хотите найти в текущей строке ввода:
In [1]: a_command = foo(x, y, z)
(reverse-i-search)`com': a_command = foo(x, y, z)
Ctrl-R будет циклически проходить через историю, находя строки, соответствующие вашему запросу.
Забыть присвоить результат вызова функции переменной может быть очень раздражающим. В одной сессии IPython хранит ссылки на введённые и выведенные объекты Python в специальной переменной. Два предыдущих вывода будут сохранены в переменных _ (подчёркивание) и __ (два подчёркивания):
In [24]: 2 ** 27
Out[24]: 134217728
In [25]: _
Out[25]: 134217728
Переменные ввода хранятся в именах, подобных _iX, где X — номер строки ввода. Для каждой строки ввода существует соответствующая переменная вывода _X. Таким образом, после ввода строки 27 у вас появятся две новые переменные _27 (вывод) и _i27 (ввод):
In [26]: foo = 'bar'
In [27]: foo
Out[27]: 'bar'
In [28]: _i27
Out[28]: u'foo'
In [29]: _27
Out[29]: 'bar'
Поскольку входные переменные являются строками, их можно повторно выполнить с помощью ключевого слова exec Python:
In [30]: exec(_i27)
Здесь _i27 — это код, введённый в In [27].
Существует несколько магических функций, которые позволяют вам использовать историю ввода и вывода. %hist печатает всю или часть истории ввода, включая или исключая номера строк. %reset очищает интерактивное пространство имён или кэш ввода и вывода. Магическая функция %xdel позволяет удалить все ссылки IPython на конкретный объект. Дополнительную информацию об этих магических методах см. в документации.
Предупреждение: при работе с большими наборами данных помните, что история ввода и вывода IPython может привести к тому, что на объекты, на которые есть ссылки, не будет собираться мусор (освобождаться память), даже если вы используете ключевое слово del для удаления переменных из интерактивного пространства имён. В этом случае будьте осторожны с использованием xdel % и %reset, чтобы избежать проблем с памятью.
Ещё одна функция IPython — бесшовная интеграция с файловой системой и операционной системой. Это означает, что вы можете выполнять операции с командной строкой, такие как работа с файлами, изменение каталогов или сохранение результатов в виде объектов Python (списков или строк), одновременно выполняя другие задачи. Он также имеет простые функции псевдонимов команд и закладок каталогов.
Таблица B-1 обобщает магические функции и синтаксис для вызова команд оболочки. Я расскажу об этих функциях в следующих разделах.
Таблица B-1 Команды IPython, связанные с системой |
---|
Команда |
Выполнение команды оболочки |
Вывод команды оболочки в переменную |
Создание псевдонима для команды оболочки |
Просмотр списка псевдонимов |
Добавление закладки каталога |
Удаление закладки каталога |
Переключение на закладку каталога |
Список всех закладок |
Использование восклицательного знака перед строкой заставляет IPython выполнить всё, что следует за ним. Это означает, что вы можете удалять файлы (в зависимости от операционной системы, используя rm или del), изменять каталоги или выполнять любые другие команды.
Добавляя восклицательный знак перед переменной, вы можете сохранить вывод команды консоли в этой переменной. Например, на моём компьютере с Linux, подключённом к сети, я могу получить IP-адрес в переменной Python:
In [1]: ip_info = !ifconfig wlan0 | grep "inet "
In [2]: ip_info[0].strip()
Out[2]: 'inet addr:10.0.0.11 Bcast:10.0.0.255 Mask:255.255.255.0'
Возвращаемый объект Python ip_info на самом деле является настраиваемым списком, содержащим различные версии вывода консоли.
При использовании ! IPython также может заменять значения, определённые в текущем окружении Python. Для этого вы можете добавить символ $ перед именем переменной:
In [3]: foo = 'test*'
In !ls $foo
test4.py test.py test.xml
Магическая функция %alias позволяет создавать быстрые способы выполнения команд оболочки. Вот простой пример:
In [1]: %alias ll ls -l
In [2]: ll /usr
total 332
drwxr-xr-x 2 root root 69632 2012-01-29 20:36 bin/
drwxr-xr-x 2 root root 4096 2001-08-23 12:05 games/
...
Вы можете выполнять несколько команд, как в командной строке, просто разделяя их точкой с запятой:
In [558]: %alias test_alias (cd examples; ls; cd ..)
In [559]: test_alias
macrodata.csv spx.csv tips.csv
Определённые вами псевдонимы теряются при завершении сеанса. Чтобы создать постоянные псевдонимы, необходимо использовать конфигурацию.
У IPython есть простая система закладок для каталогов, которая позволяет сохранять псевдонимы для часто используемых каталогов, что может быть удобно при перемещении между ними. Например, предположим, что вы хотите создать закладку для дополнительного содержимого этой книги:
In [6]: %bookmark py4da /home/wesm/code/pydata-book
После этого, при использовании магической команды %cd, вы можете использовать определённую закладку:
In [7]: cd py4da
(bookmark:py4da) -> /home/wesm/code/pydata-book
/home/wesm/code/pydata-book
Если имя закладки совпадает с именем текущего рабочего каталога, вы можете использовать флаг -b для перезаписи, указав местоположение закладки. Используйте опцию -l %bookmark, чтобы вывести список всех закладок:
In [8]: %bookmark -l
Current bookmarks:
py4da -> /home/wesm/code/pydata-book-source
Закладки, в отличие от псевдонимов, сохраняются между сеансами. AssertionError
Как только вы попадаете в отладчик, вы можете выполнять любой код Python и проверять все объекты и данные в каждом стеке вызовов (интерпретатор будет поддерживать их активность). По умолчанию он начинается с самого нижнего уровня, где произошло исключение. Вы можете переключаться между различными уровнями стека вызовов, используя команды u (вверх) и d (вниз):
ipdb> u
> /home/wesm/code/pydata-book/examples/ipython_bug.py(13)calling_things()
12 works_fine()
---> 13 throws_an_exception()
14
Команда %pdb позволяет автоматически запускать отладчик при возникновении любого исключения, что может быть очень полезно для многих пользователей.
Отладка кода с помощью отладчика также проста, особенно когда вы хотите установить точки останова или перемещаться между функциями и скриптами для проверки состояния каждого этапа. Существует несколько способов сделать это. Первый — использовать %run и -d, которые вызовут отладчик перед выполнением любого кода переданного скрипта. Вам нужно сразу же нажать s (step), чтобы войти в скрипт:
In [5]: run -d examples/ipython_bug.py
Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:1
NOTE: Enter 'c' at the ipdb> prompt to start your script.
> <string>(1)<module>()
ipdb> s
--Call--
> /home/wesm/code/pydata-book/examples/ipython_bug.py(1)<module>()
1---> 1 def works_fine():
2 a = 5
3 b = 6
Затем вы можете решить, как действовать дальше. Например, в предыдущем исключении мы можем установить точку останова перед вызовом works_fine(), а затем запустить скрипт, нажав c (continue), когда достигнем точки останова:
ipdb> b 12
ipdb> c
> /home/wessm/code/pydata-book/examples/ipython_bug.py(12)calling_things()
11 def calling_things():
2--> 12 works_fine()
13 throws_an_exception()
Теперь вы можете шагнуть внутрь works_fine() или выполнить его, нажав n (next):
ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(13)calling_things()
2 12 works_fine()
---> 13 throws_an_exception()
14
Далее мы можем перейти к throws_an_exception, достичь строки, где произошла ошибка, и проверить переменные. Обратите внимание, что команды отладчика находятся перед именем переменной, а восклицательный знак ставится перед именем переменной! Вы можете просмотреть содержимое:
ipdb> s
--Call--
> /home/wesm/code/pydata-book/examples/ipython_bug.py(6)throws_an_exception()
5
----> 6 def throws_an_exception():
7 a = 5
ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(7)throws_an_exception()
6 def throws_an_exception():
----> 7 a = 5
8 b = 6
ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(8)throws_an_exception()
7 a = 5
----> 8 b = 6
9 assert(a + b == 10)
ipdb> n
> /home/wesm/code/pydata-book/examples/ipython_bug.py(9)throws_an_exception()
8 b = 6
----> 9 assert(a + b == 10)
10
ipdb> !a
5
ipdb> !b
6
Повышение уровня владения интерактивным отладчиком требует практики и опыта. В таблице B-2 перечислены все команды отладчика. Если вы привыкли к IDE, то поначалу отладчик терминала может показаться неудобным, но со временем он станет более удобным. Некоторые IDE Python имеют отличные графические интерфейсы отладки, поэтому выбирайте тот, который вам больше нравится.
Другие способы использования отладчика
Есть ещё несколько других функций, которые можно использовать с отладчиком. Первая — это использование специальной функции set_trace (названной в честь pdb.set_trace), которая представляет собой упрощённую точку останова. Есть ещё два метода, которые вы, возможно, захотите использовать (как и я, добавив их в конфигурацию IPython):
from IPython.core.debugger import Pdb
def set_trace():
Pdb(color_scheme='Linux').set_trace(sys._getframe().f_back)
def debug(f, *args, **kwargs):
pdb = Pdb(color_scheme='Linux')
return pdb.runcall(f, *args, **kwargs)
Первая функция set_trace очень проста. Если вы хотите временно остановиться и внимательно изучить ситуацию (например, перед возникновением исключения), вы можете использовать set_trace в любом месте кода:
In [7]: run examples/ipython_bug.py
> /home/wesm/code/pydata-book/examples/ipython_bug.py(16)calling_things()
15 set_trace()
---> 16 throws_an_exception()
17
Нажмите c (продолжить), чтобы продолжить выполнение кода.
Мы только что рассмотрели функцию debug, которая позволяет удобно использовать отладчик при вызове любой функции. Предположим, мы написали следующую функцию и хотим пошагово проанализировать её логику:
def f(x, y, z=1):
tmp = x + y
return tmp / z
При обычном использовании f будет выглядеть как f(1, 2, z=3). Чтобы войти в f, передайте f в качестве первого параметра в debug, а затем передайте позицию и аргументы ключевого слова в f:
In [6]: debug(f, 1, 2, z=3)
> <ipython-input>(2)f()
1 def f(x, y, z):
----> 2 tmp = x + y
3 return tmp / z
ipdb>
Эти два простых метода сэкономили мне много времени.
Наконец, отладчик можно использовать вместе с %run. Скрипт запускается через %run -d и сразу переходит в отладчик, позволяя устанавливать произвольные точки останова и запускать скрипт: ``` В [1]: %run -d examples/ipython_bug.py Breakpoint 1 at /home/wesm/code/pydata-book/examples/ipython_bug.py:1 NOTE: Enter 'c' at the ipdb> prompt to start your script.
(1)()
ipdb>
**Перевод:**
В [1]: %run -d examples/ipython_bug.py Точка останова 1 установлена в /home/wesm/code/pydata-book/examples/ipython_bug.py:1. ВНИМАНИЕ: для запуска скрипта введите «c» в командной строке ipdb>.
<строка>(1)<модуль>()
ipdb>:
Добавив -b и номер строки, можно задать точку останова:
**Перевод:**
Добавив -b и номер строки, можно установить точку останова:
Для больших и долго выполняющихся приложений анализа данных вы можете захотеть измерить время выполнения различных компонентов или отдельных вызовов функций. Возможно, вы захотите узнать, какая функция занимает больше всего времени. К счастью, IPython позволяет легко получить эту информацию при разработке и тестировании кода.
Можно вручную использовать модуль time и его функции time.clock и time.time для измерения кода, но это утомительно и повторяемо, поскольку необходимо писать некоторый шаблонный код:
import time
start = time.time()
for i in range(iterations):
# some code to run here
elapsed_per = (time.time() - start) / iterations
Поскольку это очень распространенная операция, IPython предоставляет две магические функции, %time и %timeit, которые могут автоматизировать этот процесс.
%time запускает оператор один раз и сообщает общее время выполнения. Предположим, у нас есть большой список строк, и мы хотим сравнить различные методы выбора строк, начинающихся с foo. Здесь у нас есть список из 600 000 строк и два метода, использующих x.startswith('foo') и x[:3] == 'foo':
# a very large list of strings
strings = ['foo', 'foobar', 'baz', 'qux',
'python', 'Guido Van Rossum'] * 100000
method1 = [x for x in strings if x.startswith('foo')]
method2 = [x for x in strings if x[:3] == 'foo']
Похоже, что их производительность должна быть на одном уровне, но так ли это? Используйте %time для измерения:
In [561]: %time method1 = [x for x in strings if x.startswith('foo')]
CPU times: user 0.19 s, sys: 0.00 s, total: 0.19 s
Wall time: 0.19 s
In [562]: %time method2 = [x for x in strings if x[:3] == 'foo']
CPU times: user 0.09 s, sys: 0.00 s, total: 0.09 s
Wall time: 0.09 s
Wall time (wall-clock time) — это то, на что следует обратить внимание. Первый метод в два раза длиннее второго, но этот метод измерения неточен. Если вы будете использовать %time несколько раз для измерения, вы обнаружите, что результаты меняются. Чтобы быть более точным, используйте магическую функцию %timeit. Предоставьте любой оператор, который будет запускать этот оператор несколько раз, чтобы получить более точное время:
In [563]: %timeit [x for x in strings if x.startswith('foo')]
10 loops, best of 3: 159 ms per loop
In [564]: %timeit [x for x in strings if x[:3] == 'foo']
10 loops, best of 3: 59.3 ms per loop
Этот пример показывает, насколько важно понимать производительность Python стандартной библиотеки, NumPy, pandas и других библиотек. В крупномасштабном анализе данных эти миллисекунды будут накапливаться!
%timeit особенно полезен для анализа коротких операторов и функций, даже если они микросекундные или наносекундные. Эти времена могут показаться незначительными, но функция, выполняющаяся 20 микросекунд 1 миллион раз, будет выполняться дольше, чем функция, которая выполняется 5 микросекунд, на 15 секунд. В предыдущем примере мы можем напрямую сравнить две строковые операции, чтобы понять их характеристики производительности:
In [565]: x = 'foobar'
In [566]: y = 'foo'
In [567]: %timeit x.startswith(y)
1000000 loops, best of 3: 267 ns per loop
In [568]: %timeit x[:3] == y
10000000 loops, best of 3: 147 ns per loop
Анализ кода тесно связан с измерением кода, за исключением того, что он фокусируется на том, «где тратится время». Основным инструментом анализа Python является модуль cProfile, который не ограничивается IPython. cProfile выполняет программу или произвольный блок кода и отслеживает время выполнения каждой функции.
Обычно cProfile используется путем запуска целого сценария из командной строки и вывода времени выполнения каждой функции. Предположим, у вас есть простой сценарий, который выполняет линейную алгебру в цикле (вычисляет серию 100×100 матриц максимального абсолютного собственного значения):
import numpy as np
from numpy.linalg import eigvals
def run_experiment(niter=100):
K = 100
results = []
for _ in xrange(niter):
mat = np.random.randn(K, K)
max_eigenvalue = np.abs(eigvals(mat)).max()
results.append(max_eigenvalue)
return results
some_results = run_experiment()
print 'Largest one we saw: %s' % np.max(some_results)
Вы можете запустить этот скрипт с помощью cProfile, используя следующую командную строку:
python -m cProfile cprof_example.py
После запуска вы увидите вывод, отсортированный по имени функции. Это немного сложно определить, кто тратит больше времени, поэтому лучше использовать -s для указания сортировки:
$ python -m cProfile -s cumulative cprof_example.py
Largest one we saw: 11.923204422
15116 function calls (14927 primitive calls) in 0.720 seconds
Ordered by: cumulative time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 0.721 0.721 cprof_example.py:1(<module>)
100 0.003 0.000 0.586 0.006 linalg.py:702(eigvals)
200 0.572 0.003 0.572 0.003 {numpy.linalg.lapack_lite.dgeev}
1 0.002 0.002 0.075 0.075 __init__.py:106(<module>)
100 0.059 0.001 0.059 0.001 {method 'randn')
1 0.000 0.000 0.044 0.044 add_newdocs.py:9(<module>)
2 0.001 0.001 0.037 0.019 __init__.py:1(<module>)
2 0.003 0.002 0.030 0.015 __init__.py:2(<module>)
1 0.000 0.000 0.030 0.030 type_check.py:3(<module>)
1 0.001 0.001 0.021 0.021 __init__.py:15(<module>)
1
``` 0.013 0.013 0.013 0.013 numeric.py:1(<module>)
1 0.000 0.000 0.009 0.009 __init__.py:6(<module>)
1 0.001 0.001 0.008 0.008 __init__.py:45(<module>)
262 0.005 0.000 0.007 0.000 function_base.py:3178(add_newdoc)
100 0.003 0.000 0.005 0.000 linalg.py:162(_assertFinite)
Только 15 строк. Просматривая столбец cumtime, можно легко увидеть, сколько времени заняла каждая функция. Если одна функция вызывает другую функцию, время не останавливается. cProfile записывает время начала и окончания каждой функции, используя их для измерения времени.
Помимо использования в командной строке, cProfile также можно использовать в программе для анализа любого блока кода без необходимости запуска нового процесса. Ipython предоставляет удобные интерфейсы %prun и %run -p для этой цели. %prun использует аналогичные параметры командной строки cProfile, но может анализировать любой оператор Python вместо всего файла py:
```python
In [4]: %prun -l 7 -s cumulative run_experiment()
4203 function calls in 0.643 seconds
Ordered by: cumulative time
List reduced from 32 to 7 due to restriction <7>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.643 0.643 <string>:1(<module>)
1 0.001 0.001 0.643 0.643 cprof_example.py:4(run_experiment)
100 0.003 0.000 0.583 0.006 linalg.py:702(eigvals)
200 0.569 0.003 0.569 0.003 {numpy.linalg.lapack_lite.dgeev}
100 0.058 0.001 0.058 0.001 {method 'randn'}
100 0.003 0.000 0.005 0.000 linalg.py:162(_assertFinite)
200 0.002 0.000 0.002 0.000 {method 'all' of 'numpy.ndarray'}
Аналогично, вызов ``%run -p -s cumulative cprof_example.py`` имеет тот же эффект, что и командная строка, только вам не нужно покидать Ipython.
В Jupyter notebook вы можете использовать %%prun магию (два %) для анализа целого блока кода. Это вызовет отдельное окно с анализом вывода. Удобно для быстрого ответа на вопросы, например, «почему этот блок кода занял так много времени»?
Используя IPython или Jupyter, есть и другие инструменты, которые могут сделать анализ более понятным. Одним из них является SnakeViz (https://github.com/jiffyclub/snakeviz/), который использует d3.js для создания интерактивной визуализации результатов анализа.
## Анализ функций по строкам
В некоторых случаях информация, полученная с помощью %prun (или других методов анализа на основе cProfile), не позволяет получить полное представление о времени выполнения функции или результаты слишком сложны, и вместе с именем функции трудно интерпретировать. Для таких случаев существует небольшая библиотека под названием line_profiler (доступна через PyPI или менеджер пакетов). Он включает в себя плагин IPython, который позволяет активировать новый магический метод %lprun для анализа одной или нескольких функций построчно. Вы можете включить этот плагин, добавив следующую строку в конфигурацию IPython (см. документацию IPython или раздел конфигурации в конце этой главы):
```python
# A list of dotted module names of IPython extensions to load.
c.TerminalIPythonApp.extensions = ['line_profiler']
Вы также можете запустить команду:
%load_ext line_profiler
line_profiler также можно использовать вне IPython (смотрите полную документацию), но он наиболее мощный при использовании в IPython. Предположим, у вас есть модуль prof_mod с кодом, выполняющим некоторые операции с массивами NumPy:
from numpy.random import randn
def add_and_sum(x, y):
added = x + y
summed = added.sum(axis=1)
return summed
def call_function():
x = randn(1000, 1000)
y = randn(1000, 1000)
return add_and_sum(x, y)
Если вы хотите узнать о производительности функции add_and_sum, %prun может предоставить следующее:
In [569]: %run prof_mod
In [570]: x = randn(3000, 3000)
In [571]: y = randn(3000, 3000)
In [572]: %prun add_and_sum(x, y)
4 function calls in 0.049 seconds
Ordered by: internal time
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.036 0.036 0.046 0.046 prof_mod.py:3(add_and_sum)
1 0.009 0.009 0.009 0.009 {method 'sum' of 'numpy.ndarray'}
1 0.003 0.003 0.049 0.049 <string>:1(<module>)
Этот подход не очень информативен. Активировав плагин IPython line_profiler, вы можете использовать новую команду %lprun. Разница в использовании заключается в том, что мы должны сообщить %lprun, какую функцию анализировать. Синтаксис следующий:
%lprun -f func1 -f func2 statement_to_profile
Мы хотим проанализировать add_and_sum и запускаем:
In [573]: %lprun -f add_and_sum add_and_sum(x, y)
Timer unit: 1e-06 s
File: prof_mod.py
Function: add_and_sum at line 3
Total time: 0.045936 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def add_and_sum(x,
4 1 36510 36510.0 79.5 added = x + y
5 1 9425 9425.0 20.5 summed = added.sum(axis=1)
6 1 1 1.0 0.0 return summed
Это упрощает интерпретацию. Мы проанализировали ту же функцию, что и в коде. Глядя на код модуля, мы можем вызвать call_function и проанализировать его вместе с add_and_sum для получения полного обзора производительности кода:
In [574]: %lprun -f add_and_sum -f call_function call_function()
Timer unit: 1e-06 s
File: prof_mod.py
Function: add_and_sum at line 3
Total time: 0.005526 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
3 def add_and_sum(x, **Перевод текста на русский язык:**
Мой опыт показывает, что для макроанализа используется %prun (cProfile), а для микроанализа — %lprun (line_profiler). Лучше всего хорошо понимать оба этих инструмента.
>Примечание: использование %lprun требует указания имени функции, поскольку отслеживание времени выполнения каждой строки является очень затратным. Отслеживание ненужных функций может существенно изменить результат.
**B.4 Использование эффективных методов разработки IPython**
Удобство и скорость написания кода, отладки и использования являются целью каждого человека. Помимо стиля кода, необходимо также внести некоторые коррективы в детали процесса (например, перегрузка кода).
Поэтому содержание этого раздела больше похоже на искусство, чем на науку, и требует постоянного экспериментирования для достижения эффективности. В конечном итоге вы должны уметь оптимизировать структуру кода и быстро и легко проверять результаты программы или функции. Я обнаружил, что программное обеспечение, разработанное с использованием IPython, лучше подходит для работы, чем командная строка, особенно когда возникает ошибка, и вам нужно проверить код, написанный вами или кем-то другим несколько месяцев или лет назад.
**Перегрузка зависимостей модулей**
В Python, когда вы вводите import some_lib, код внутри some_lib будет выполнен, и все переменные, функции и определения будут импортированы в новое созданное пространство имён модуля some_lib. Когда вы в следующий раз введёте some_lib, вы получите ссылку на уже существующее пространство имён модуля. Скрытая проблема заключается в том, что если вы %run скрипт, который зависит от другого модуля, и этот модуль был изменён, это вызовет проблемы. Предположим, у меня есть следующий код в test_script.py:
```python
import some_lib
x = 5
y = [1, 2, 3, 4]
result = some_lib.get_answer(x, y)
Если вы запустите %run test_script.py, а затем измените some_lib.py и снова выполните %run test_script.py, вы всё равно получите старую версию some_lib.py из-за механизма «однократной загрузки» системы модулей Python. Это отличает Python от других сред анализа данных, таких как MATLAB, которые автоматически распространяют изменения кода. Есть несколько способов решить эту проблему. Первый — использовать функцию reload в стандартном библиотечном модуле importlib:
import some_lib
import importlib
importlib.reload(some_lib)
Это гарантирует, что каждый раз при запуске test_script.py будет загружаться новейшая версия some_lib.py. Очевидно, что использование reload для более глубоких зависимостей становится очень обременительным. Для этой проблемы IPython имеет специальную функцию dreload (она не является магической функцией), которая перезагружает глубокие модули. Если я запустил some_lib.py, а затем введу dreload(some_lib), он попытается перезагрузить some_lib и его зависимости. Однако этот метод не применим ко всем сценариям, но он намного лучше, чем перезапуск IPython.
Методы проектирования кода
Для этого нет простого решения, но есть несколько принципов, которые я обнаружил полезными в своей работе.
Сохраняйте объекты и данные активными
Написание программы, подобной примеру ниже, для командной строки встречается редко:
from my_functions import g
def f(x, y):
return g(x + y)
def main():
x = 6
y = 7.5
result = x + y
if __name__ == '__main__':
main()
Запуск этой программы в IPython вызовет проблемы, вы заметили? После запуска любые определённые результаты и объекты в функции main нельзя будет получить в IPython. Лучший способ — выполнить код непосредственно в пространстве имён модуля (или в __name__ == '__main__':
, если вы хотите, чтобы этот модуль можно было вызывать). Таким образом, когда вы %rundiamante, вы можете просматривать все определённые переменные в main. Это эквивалентно определению переменных верхнего уровня в коде ячейки Jupyter notebook.
Плоское лучше вложенного
Код с глубокими вложениями всегда напоминает мне о луке. При тестировании или отладке функции сколько слоёв лука вам нужно снять, прежде чем добраться до целевого кода? «Плоское лучше вложенного» — часть философии Python, которая также применима к интерактивному коду разработки. По возможности избегайте связывания функций и классов и модулируйте их для тестирования (если вы пишете модульные тесты), отладки и интерактивного использования.
Преодоление страха перед большими файлами
Если раньше вы писали на JAVA (или аналогичном языке), возможно, вам говорили, что файлы должны быть короткими. В большинстве языков это разумный совет: слишком длинные файлы заставляют людей думать, что код плохой, и требуют рефакторинга и реорганизации. Но при разработке с использованием IPython выполнение 10 связанных небольших файлов (менее 100 строк) менее утомительно, чем двух или трёх больших файлов. Меньшее количество файлов означает меньше перезагрузки модулей и меньше переходов по файлам во время редактирования. Я обнаружил, что поддерживать большие модули, каждый из которых организован плотно, более полезно и соответствует стилю Python. Иногда большие файлы разбиваются на более мелкие.
Я не рекомендую доводить эту рекомендацию до крайности, иначе получится отдельный сверхбольшой файл. Создание разумной и интуитивно понятной библиотеки больших кодов и структуры упаковки часто требует некоторой работы, но это важно в командной работе. Каждый модуль должен быть плотно организован и должен позволять интуитивно находить функции и классы, отвечающие за каждую область функциональности.
B.5 Расширенные функции IPython
Чтобы полностью использовать систему IPython, требуется немного другой подход к написанию кода или более глубокое понимание конфигурации IPython.
Сделайте классы дружественными к IPython
IPython старается максимально красиво отображать каждую строку в консоли. Для многих объектов, таких как словари, списки и кортежи, встроенный модуль pprint можно использовать для улучшения форматирования. Однако в пользовательских классах вы сами генерируете строки. Предположим, есть простой класс:
class Message:
def __init__(self, msg):
self.msg = msg
При такой записи вы обнаружите, что вывод по умолчанию недостаточно красив:
In [576]: x = Message('I have a secret')
In [580]: x
Out[580]: <__main__.Message instance at 0x60ebbd8>
IPython принимает строку, возвращаемую методом repr(obj), и выводит её на консоль. Поэтому мы можем добавить простой метод repr в предыдущий класс, чтобы получить более полезный вывод:
class Message:
def __init__(self, msg):
self.msg = msg
def __repr__(self):
return 'Message: %s' % self.msg
In [579]: x = Message('I have a secret')
In [580]: x
Out[580]: Message: I have a secret
Файлы и конфигурация
Расширяя систему конфигурации, большая часть внешнего вида IPython и Jupyter notebook (цвета, подсказки, интервалы строк и т. д.) и действий могут быть настроены. С помощью конфигурации вы можете:
Конфигурация IPython хранится в специальном файле ipython_config.py, который обычно находится в папке .ipython/ в домашнем каталоге пользователя. Конфигурация осуществляется через специальный файл. Когда вы запускаете IPython, он по умолчанию загружает файл, хранящийся в папке profile_default. Таким образом, на моей Linux-системе полный путь к файлу конфигурации IPython выглядит следующим образом:
/home/wesm/.ipython/profile_default/ipython_config.py
Чтобы запустить этот файл, выполните следующую команду:
ipython profile create **Содержание этого файла читатель может изучить самостоятельно.** В файле есть комментарии, объясняющие назначение каждой опции конфигурации. Кроме того, можно иметь несколько конфигурационных файлов. Предположим, вы хотите создать ещё один файл конфигурации IPython специально для другого приложения или проекта. Создать новый файл конфигурации очень просто:
```python
ipython profile create secret_project
После этого в каталоге profile_secret_project будет создан новый конфигурационный файл. Затем запустите IPython следующим образом:
$ ipython --profile=secret_project
Python 3.5.1 | packaged by conda-forge | (default, May 20 2016, 05:22:56)
Type "copyright", "credits" or "license" for more information.
IPython 5.1.0 — улучшенный интерактивный Python.
? -> Введение и обзор возможностей IPython.
%quickref -> Быстрая справка.
help -> Собственная справочная система Python.
object? -> Подробная информация об «объекте», используйте «object??» для получения дополнительных сведений.
Профиль IPython: secret_project.
Как и раньше, документация IPython является отличным ресурсом для изучения конфигурационного файла.
Конфигурирование Jupyter немного отличается, поскольку вы можете использовать другие языки, кроме Python. Чтобы создать аналогичный файл конфигурации Jupyter, выполните:
jupyter notebook --generate-config
Это создаст файл конфигурации в ~/.jupyter/jupyter_notebook_config.py. После редактирования вы можете переименовать его:
$ mv ~/.jupyter/jupyter_notebook_config.py ~/.jupyter/my_custom_config.py
Открыв Jupyter, вы можете добавить параметр --config:
jupyter notebook --config=~/.jupyter/my_custom_confg.py
Изучив примеры кода из этой книги, ваши навыки работы с Python определённо улучшились. Я рекомендую вам продолжать изучать IPython и Jupyter. Поскольку цель этих проектов — повысить производительность, вы, возможно, обнаружите некоторые инструменты, которые позволят вам более эффективно использовать Python и библиотеки вычислений.
Вы можете найти больше интересных ноутбуков Jupyter на nbviewer (https://nbviewer.jupyter.org/).
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )