Модули и пакеты


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

Написание модулей

Модули в Python - это просто файлы Python с расширением .py. Имя модуля будет именем файла. В модуле Python может быть определен и реализован набор функций, классов или переменных. В приведенном выше примере у нас будет два файла:

mygame/ mygame/game.py mygame/draw.py
1
2
3
mygame /
mygame / game . ру
mygame / draw . ру
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Скрипт Python game.pyбудет реализовывать игру. Он будет использовать функцию draw_gameиз файла draw.pyили, другими словами, drawмодуль, реализующий логику рисования игры на экране.

Модули импортируются из других модулей с помощью importкоманды. В этом примере game.pyсценарий может выглядеть примерно так:

# game.py # import the draw module import draw def play_game(): ... def main(): result = play_game() draw.draw_game(result) # this means that if this script is executed, then # main() will be executed if __name__ == '__main__': main()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# game.py
# импортировать модуль рисования
импортный розыгрыш
def play_game ( ) :
...
def main ( ) :
результат = play_game ( )
рисовать . draw_game ( результат )
# это означает, что если этот скрипт выполняется, то
# main () будет выполнен
если __name__ == '__main__' :
main ( )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

drawМодуль может выглядеть следующим образом :

# draw.py def draw_game(): ... def clear_screen(screen): ...
1
2
3
4
5
6
7
# draw.py
def draw_game ( ) :
...
def clear_screen ( экран ) :
...
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

В этом примере gameмодуль импортирует drawмодуль, что позволяет ему использовать функции, реализованные в этом модуле. mainФункция будет использовать локальную функцию , play_gameчтобы запустить игру, а затем нарисовать результат игры с помощью функции реализованы в drawмодуле , называемом draw_game. Чтобы использовать функцию draw_gameиз drawмодуля, нам нужно указать, в каком модуле реализована функция, с помощью оператора точки. Чтобы ссылаться на draw_gameфункцию из gameмодуля, нам нужно будет импортировать drawмодуль и только затем вызвать draw.draw_game().

Когда import drawдиректива будет запущена, интерпретатор Python будет искать файл в каталоге, из которого был выполнен скрипт, по имени модуля с .pyсуффиксом, поэтому в нашем случае он попытается найти draw.py. Если он найдет его, он импортирует его. Если нет, он продолжит поиск встроенных модулей.

Вы могли заметить, что при импорте модуля появляется .pycфайл, который является скомпилированным файлом Python. Python компилирует файлы в байт-код Python, поэтому ему не придется анализировать файлы при каждой загрузке модулей. Если .pycфайл существует, он загружается вместо .pyфайла, но этот процесс прозрачен для пользователя.

Импорт объектов модуля в текущее пространство имен

Мы также можем импортировать функцию draw_gameнепосредственно в пространство имен основного скрипта, используя fromкоманду.

# game.py # import the draw module from draw import draw_game def main(): result = play_game() draw_game(result)
1
2
3
4
5
6
7
# game.py
# импортировать модуль рисования
из рисовать импорт draw_game
def main ( ) :
результат = play_game ( )
draw_game ( результат )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Вы могли заметить, что в этом примере draw_gameне предшествует имя модуля, из которого он импортируется, потому что мы указали имя модуля в importкоманде.

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

Импорт всех объектов из модуля

Мы также можем использовать import *команду для импорта всех объектов из определенного модуля, например:

# game.py # import the draw module from draw import * def main(): result = play_game() draw_game(result)
1
2
3
4
5
6
7
# game.py
# импортировать модуль рисования
из импорта из розыгрыша *
def main ( ) :
результат = play_game ( )
draw_game ( результат )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Это может быть немного рискованно, поскольку изменения в модуле могут повлиять на модуль, который его импортирует, но он короче и также не требует от вас указывать, какие объекты вы хотите импортировать из модуля.

Пользовательское имя импорта

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

Например, если у вас есть два drawмодуля с немного разными именами, вы можете сделать следующее:

# game.py # import the draw module if visual_mode: # in visual mode, we draw using graphics import draw_visual as draw else: # in textual mode, we print out text import draw_textual as draw def main(): result = play_game() # this can either be visual or textual depending on visual_mode draw.draw_game(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
# game.py
# импортировать модуль рисования
если visual_mode :
# в визуальном режиме мы рисуем с помощью графики
импортировать draw_visual как draw
еще :
# в текстовом режиме печатаем текст
импортировать draw_textual как рисовать
def main ( ) :
результат = play_game ( )
# это может быть визуальное или текстовое в зависимости от
на visual_mode
рисовать . draw_game ( результат )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Инициализация модуля

При первой загрузке модуля в работающий скрипт Python он инициализируется однократным выполнением кода в модуле. Если другой модуль в вашем коде снова импортирует тот же модуль, он не будет загружен дважды, а только один раз - поэтому локальные переменные внутри модуля действуют как «синглтон» - они инициализируются только один раз.

Это полезно знать, потому что это означает, что вы можете положиться на это поведение при инициализации объектов. Например:

# draw.py def draw_game(): # when clearing the screen we can use the main screen object initialized in this module clear_screen(main_screen) ... def clear_screen(screen): ... class Screen(): ... # initialize main_screen as a singleton main_screen = Screen()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# draw.py
def draw_game ( ) :
# при очистке экрана мы можем использовать основной
объект экрана, инициализированный в этом модуле
clear_screen ( главный_экран )
...
def clear_screen ( экран ) :
...
класс Screen ( ) :
...
# инициализировать main_screen как синглтон
main_screen = Экран ( )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Расширение пути загрузки модуля

Есть несколько способов указать интерпретатору Python, где искать модули, помимо значения по умолчанию, которое представляет собой локальный каталог и встроенные модули. Вы можете использовать переменную среды, PYTHONPATHчтобы указать дополнительные каталоги для поиска модулей, например:

PYTHONPATH=/foo python game.py
1
PYTHONPATH = / foo python game . ру
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Это будет выполнено game.pyи позволит сценарию загружать модули из fooкаталога, а также из локального каталога.

Другой метод - это sys.path.appendфункция. Вы можете выполнить его перед запуском importкоманды:

sys.path.append("/foo")
1
sys . путь . добавить ( "/ foo" )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Это добавит fooкаталог в список путей для поиска модулей.

Изучение встроенных модулей

Ознакомьтесь с полным списком встроенных модулей в стандартной библиотеке Python здесь .

Две очень важные функции, пригодится при изучении модулей в Python - с dirи helpфункции.

Если мы хотим импортировать модуль urllib, который позволяет нам создавать данные для чтения из URL-адресов, мы просто importиспользуем модуль:

# import the library import urllib # use it urllib.urlopen(...)
1
2
3
4
5
# импортировать библиотеку
импортировать urllib
# используй это
urllib . urlopen ( ... )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Мы можем посмотреть, какие функции реализованы в каждом модуле, используя dirфункцию:

>>> import urllib >>> dir(urllib) ['ContentTooShortError', 'FancyURLopener', 'MAXFTPCACHE', 'URLopener', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '__package__', '__version__', '_ftperrors', '_get_proxies', '_get_proxy_settings', '_have_ssl', '_hexdig', '_hextochr', '_hostprog', '_is_unicode', '_localhost', '_noheaders', '_nportprog', '_passwdprog', '_portprog', '_queryprog', '_safe_map', '_safe_quoters', '_tagprog', '_thishost', '_typeprog', '_urlopener', '_userprog', '_valueprog', 'addbase', 'addclosehook', 'addinfo', 'addinfourl', 'always_safe', 'basejoin', 'c', 'ftpcache', 'ftperrors', 'ftpwrapper', 'getproxies', 'getproxies_environment', 'getproxies_macosx_sysconf', 'i', 'localhost', 'main', 'noheaders', 'os', 'pathname2url', 'proxy_bypass', 'proxy_bypass_environment', 'proxy_bypass_macosx_sysconf', 'quote', 'quote_plus', 'reporthook', 'socket', 'splitattr', 'splithost', 'splitnport', 'splitpasswd', 'splitport', 'splitquery', 'splittag', 'splittype', 'splituser', 'splitvalue', 'ssl', 'string', 'sys', 'test', 'test1', 'thishost', 'time', 'toBytes', 'unquote', 'unquote_plus', 'unwrap', 'url2pathname', 'urlcleanup', 'urlencode', 'urlopen', 'urlretrieve']
1
2
3
4
5
6
7
8
>>> импортировать urllib
>>> DIR ( URLLIB )
[ 'ContentTooShortError' , 'FancyURLopener' ,
'MAXFTPCACHE' , 'URLopener' , '__all__' , '__builtins__'
,
'__doc__' , '__file__' , '__name__' , '__package__' ,
'__version__' , '_ftperrors' , '_get_proxies' ,
'_get_proxy_settings' , '_have_ssl' , '_hexdig' ,
_hextochr , _hostprog , _is_unicode , _localhost
,
_noheaders , _nportprog , _passwdprog ,
_portprog , _queryprog , _safe_map ,
'_safe_quoters' ,
'_tagprog' , '_thishost' , '_typeprog' , '_urlopener' ,
'_userprog' , '_valueprog' , 'addbase' , 'addclosehook' ,
'addinfo' , 'addinfourl' , 'always_safe' , 'basejoin' ,
c , ftpcache , ftperrors , ftpwrapper ,
'getproxies' ,
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Когда мы находим функцию в модуле, который хотим использовать, мы можем узнать о ней больше, используя helpфункцию внутри интерпретатора Python:

help(urllib.urlopen)
1
помощь ( urllib . urlopen )
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Написание пакетов

Пакеты - это пространства имен, которые содержат несколько пакетов и самих модулей. Это просто каталоги, но с изюминкой.

Каждый пакет в Python - это каталог, который ДОЛЖЕН содержать специальный файл с именем __init__.py. Этот файл может быть пустым и означает, что каталог, который он содержит, является пакетом Python, поэтому его можно импортировать так же, как и модуль.

Если мы создадим каталог с именем foo, который помечает имя пакета, мы сможем создать модуль внутри этого пакета с именем bar. Мы также не должны забывать добавить __init__.pyфайл в fooкаталог.

Чтобы использовать модуль bar, мы можем импортировать его двумя способами:

import foo.bar
1
import foo . бар
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

или:

from foo import bar
1
из панели импорта foo
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

В первом методе мы должны использовать fooпрефикс всякий раз, когда получаем доступ к модулю bar. Во втором методе мы этого не делаем, потому что мы импортируем модуль в пространство имен нашего модуля.

__init__.pyФайл может также решить , какие модули экспорта пакета как API, сохраняя при этом другие модули внутренней, с помощью переопределения __all__переменной, например , так:

__init__.py: __all__ = ["bar"]
1
2
3
__init__ . ру :
__all__ = [ "бар" ]
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
В 1]:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1/0 0/0

Упражнение

В этом упражнении вам нужно будет распечатать отсортированный по алфавиту список всех функций reмодуля, которые содержат это слово find.