Рубрики
Без рубрики

Пожалуйста, поделитесь, чтобы поддержать, если вам нравится моя работа.

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

Автор оригинала: Sheena.

Что?! Еще один учебник по импорту?

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

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

Что такое импорт?

Чтобы использовать любой пакет в своем коде, вы должны сначала сделать его доступным. Вы должны импортировать его. Вы не можете использовать что-либо в Python до того, как оно будет определено. Некоторые вещи встроены, например, базовые типы (например, int , float и т. Д.) Можно использовать в любое время. Но для большинства вещей, которые вы захотите сделать, потребуется немного больше. Попробуйте ввести следующее в консоль Python:

oTime = datetime.datetime.now()

Вы получите NameError . Очевидно, Python не знает, что означает date time – |/date time не определено. Вы получите аналогичный результат для этого:

print my_undefined_variable

Для того, чтобы datetime (или что-либо действительно) считалось определенным, он должен быть доступен из текущей области видимости. Чтобы это было правдой, он должен удовлетворять одному из следующих условий:

  • это часть среды Python по умолчанию. Например, int , list , __name__ и object . Попробуйте ввести их в интерпретаторе и посмотрите, что произойдет
  • он был определен в текущем потоке программы (например, вы написали def или class или просто простой оператор присваивания, чтобы он что-то значил. Это утверждение немного упрощено, и я довольно скоро расширю его
  • он существует как отдельный пакет, и вы импортировали этот пакет, выполнив соответствующую инструкцию импорта.

Попробуйте это:

import datetime
oTime = datetime.datetime.now()
print oTime.isoformat()

Это сработало намного лучше. Сначала мы импортируем datetime . Затем мы используем функцию now для создания объекта и назначаем его переменной time . Затем мы можем получить доступ к функциям и атрибутам, прикрепленным к time .

Импорт пакета datetime сделал его доступным в текущей области.

Хорошо… но что такое сфера охвата?

Слово scope , кажется, вызывает много страха у новых разработчиков, поэтому я немного расскажу об этом здесь. Основная идея заключается в том, что если вы хотите использовать какой-либо объект в любой точке программы, этот объект должен быть определен до его использования. Некоторые вещи всегда определяются независимо от того, где вы находитесь в своей программе (например: int ), они, как говорят, существуют в глобальной области.

Другие вещи должны быть определены явным образом. Это подразумевает фактическое выполнение оператора или операторов для определения объекта – чтобы дать ему имя в текущей области. Если у вас есть куча доступных объектов, то эти объекты доступны с помощью их имен, объект существует в области, если ему было дано имя в этой области. И присвоение чему-то имени всегда подразумевает фактическое выполнение оператора. Теперь, когда я перефразировал себя миллион раз, давайте перейдем к некоторым примерам.

print x   # NameError
x = 3     # associates the name `x` with 3
print x   # works fine this time

Пакеты, классы и функции работают одинаково. Взгляните на примеры date time выше.

Так что это довольно просто, не так ли? Неправильно! Все еще остается вопрос о том, чтобы охватить области.

Попробуйте это…

y = 3                                   # first we associate the name 'y' with the number 3

def print_stuff():              # then we associate the name print_stuff with this function
    print "calling print_stuff"          #  (***)
    print y                                                                              
    z = 4     
    print z                
    print "exiting print_stuff"
                                                                                                             
print_stuff()                      # we call print_stuff and the program execution goes to (***)
print y                               # works fine
print z                               # NameError!!!

Таким образом, y был определен снаружи print_stuff и был доступен как внутри, так и снаружи print_stuff . И z был определен внутри print_stuff и был доступен только в print_stuff. Здесь есть два отдельных прицела!

Итак, давайте расширим наше понимание до: объект существует в области, если ему было дано имя в этой области ИЛИ заключающей области .

Теперь о чем-то немного другом… если вы используете интерпретатор, поместите его в новый (выйдите и откройте новый).

 # no more y here...

def print_stuff():    
    print "calling print_stuff"  
    print y                
    z = 4      
    print z     
    print "exiting print_stuff"                                                                 

print_stuff()     # NameError. this shouldn't surprise you since we haven't yet set y 
y = 3             # only now do we associate the name 'y' with the number 3
print_stuff()     # the rest works fine
print y                              

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

program_scope = {
        'y'  : 3,        # y was defined outside of print_stuff,
                          #         and is accessible inside and outside
        'print_stuff' : {
            'z' : 4               # z was defined inside of print_stuff, 
                                   #           and is ONLY accessible inside
        },
}

Подготовьтесь к последнему примеру для этого раздела…

a = [1,2,3]             # first we give some names to some stuff, 
b = [4,5,6]             #     thus making it accessible in this scope and enclosing scopes
c = [8,9,10]

def do_things():
    a = "new a"
    b.append(7)
    c = "new c"
    print a
    print b
    print c

do_things()            #nothing surprising in the output...

print a                # [1,2,3]
print b                # [4, 5, 6, 7]
print c                # [8,9,10]   

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

Рисуя иерархию областей в виде словаря, как и раньше, у нас есть что-то вроде этого:

program_scope = {
    'a' : [1,2,3],             
    'b' : [4,5,6,7], 
    'c' : [8,9,10],
    do_things: {
       'a' : 'new_a',    #the enclosed scope has its own a and c but no b!!!
       'c' : 'new c'
    }
}

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

Пакет-это просто дерево каталогов с некоторыми файлами Python в нем. Ничего волшебного. Если вы хотите сообщить Python, что определенный каталог является пакетом, создайте файл с именем __init__.py и просто засунь его туда. Серьезно, это все, что нужно. Чтобы сделать пакет, который действительно полезен, вам нужно будет сделать несколько больше. Пакет может содержать другие пакеты, но для того, чтобы быть полезным, он должен содержать модули где-то в своей иерархии. Модуль-это просто скрипт, содержащий кучу определений и объявлений (вы определяете классы и функции, а также объявляете переменные). Весь смысл этой системы заключается в том, чтобы иметь возможность организовать код таким образом, чтобы было легко использовать существующий код в новых проектах без чрезмерного использования копипаста.

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

В этом разделе мы рассмотрим основные этапы создания и использования пакетов и модулей.

Для начала создайте себе каталог для работы (я буду называть его вашим рабочим каталогом до конца этого текста) и следуйте приведенным ниже инструкциям. Теперь создайте файл с именем my_funcs.py это выглядит немного похоже на:

def print_a():
    print "a"
    
def print_b():
    print "b"

Теперь откройте консоль и cd в недавно созданном рабочем каталоге и запустите интерпретатор Python. Введите следующие инструкции:

print my_funcs         # NameError
import my_funcs        # from this point on my_funcs is in the scope
print my_funcs         # prints information about the module
my_funcs.print_a()     # call the function print_a that is a member of the my_funcs module
my_funcs.print_b()

Разве это не волнительно? Чтобы добраться до чего-то, содержащегося непосредственно в модуле, вы можете просто импортировать модуль и использовать точку. Но это выглядит немного многословно, выйдите из интерпретатора, запустите новый и попробуйте это:

from my_funcs import print_a,print_b      # this time we add the two functions to the scope      
print_a()         # it works!
print_b()
print my_funcs    # this raises a NameError since we didn't make 
                  #              my_funcs accessable, only its members

Теперь создайте каталог в своем рабочем каталоге с именем my_pkg и переместите его my_funcs.py в my_pkg. Создайте пустой файл с именем __init__.py и засуньте его туда же. Теперь у вас есть такая структура каталогов:

    working_dir\
             my_pkg\
                 __init__.py
                 my_funcs.py

Закройте и перезапустите интерпретатор и введите следующее:

print my_pkg             # NameError
print my_funcs           # NameError

import my_pkg            # my_pkg is not defined in the scope

print my_pkg             # isn't that nice
print my_funcs           # NameError
print my_pkg.my_funcs    # module information

my_pkg.my_funcs.print_a() #works fine
my_pkg.my_funcs.print_b()

Опять же, все точки немного многословны. Мы можем сделать все немного по-другому. Поиграй с ними:

from my_pkg import my_funcs

from my_pkg.my_funcs import print_a, print_b

from my_pkg import my_funcs.print_a, my_funcs.print_b

from my_pkg.my_funcs import print_a as the_coolest_function_ever, print_b as not_the_brightest_crayon

Сообщение take home здесь заключается в том, что вы можете очень точно определить, какие части пакета или модуля вы хотите импортировать, и даже можете решить, какие имена им будут даны в области действия.

Теперь выйдите из интерпретатора и cd из вашего рабочего каталога и запустите новый интерпретатор. Попробуйте импортировать из my_pkg . Ты не можешь. И это отстой. Но мы можем импортировать datetime . Так что же это дает?

sys.path дает

Python очень хорошо осведомлен о том, что известно как текущий рабочий каталог . Если вы пытаетесь что-то импортировать, то текущий рабочий каталог находится там, где он выглядит первым, где он выглядит вторым, третьим и четвертым, немного зависит от вашей установки python. Попробуйте это:

import sys
print sys.path

Это выводит список каталогов, когда вы говорите Python импортировать что-то, то он выглядит в каждом из перечисленных местоположений по порядку. Первый элемент в списке-пустая строка, указывающая текущий рабочий каталог. Если бы вы создали пакет или модуль в своем текущем каталоге и назвали его date time , то стандартная функциональность Python datetime была бы недоступна.

Попробуйте это:

sys.path.append('/path/to/your/working/directory') #the directory that contains my_pkg
import my_pkg

Блестяще! Теперь мы можем снова использовать my_pkg .

Но было бы немного раздражающе добавлять новые записи в sys.path для каждого пакета, который мы хотим использовать, было бы лучше просто хранить пакеты Python в стандартных местах и указывать путь туда. К счастью, механизмы установки пакетов Python справляются с такими вещами. Однако это немного выходит за рамки этого текста. Дело в том, что вы можете сделать любое количество пакетов доступными для вашего скрипта с помощью sys.path , и это ужасно удобно. За исключением тех случаев, когда это не так…

Конфликты версий

Давайте на мгновение предположим, что конфликты версий-это ужасные вещи. Что делать, если вы начали работать над двумя разными проектами (A и B), для которых требуются два разных набора пакетов, скажем, проект A требует, чтобы вы установили C1 и C2, а проект B требует, чтобы вы установили пакеты D1 и D2. Красиво и опрятно. Там нет никаких конфликтов.

Но что, если C1 требует E версии 2, а проект B требует E версии 3? Вы можете просто постоянно устанавливать и удалять различные версии E по мере необходимости, когда вам нужно запускать сценарии… но это звучит действительно ужасно.

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

Учитывая ваши новые знания о пути импорта, мы могли бы поместить каждую из версий E в разные места, а затем изменить sys.path , чтобы включить местоположение correrct. Например, в A нам нужно было бы сделать что-то вроде:

import sys
sys.path.append('path/to/E_version_2')

Поначалу это может показаться умным, но на самом деле это не так. Такой подход сделал бы установку наших пакетов утомительной и хрупкой, поскольку все должно быть помещено в нужное место. И в любом случае, это не решает проблему будущих проблем.

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

Виртуальные среды

Виртуальная среда-это группа файлов и конфигураций, которые можно активировать (и деактивировать). Виртуальная среда связана с собственным исполняемым файлом python и собственным sys.path, и, следовательно, с собственной комбинацией установленных пакетов.

Вот как я обычно начинаю новый проект (это bash, а не Python):

virtualenv venv                # 1. creates the virtual environment
source venv/bin/activate       # 2. activates the virtual environment
# whatever you want
deactivate  # 3. deactivates the virtual environment  (you can also just close the terminal)

Строка 1 создает виртуальную среду с именем venv . Это просто структура каталогов, содержащая кучу материалов Python и некоторую конфигурацию (включая новый sys.path ). Эта команда имеет множество опций, вы даже можете выбрать, какой Python вы хотите включить в среду (если в вашей системе установлено несколько версий Python)

Строка 2 активирует окружающую среду. Если среда активна и вы запускаете Python, то вы запускаете интерпретатор Python, который живет в виртуальной среде. Если вы устанавливаете пакет, пока среда активна, то пакет не будет отправлен в то место, куда отправляются общесистемные пакеты, он скорее будет установлен внутри среды (структура каталогов).

Строка 3 отключает окружающую среду и снова делает все нормальным. Все, что вы установили в своей среде, будет доступно при следующей активации.

Опрятно, а?

Вывод

Мы здесь довольно много рассмотрели. Мы начали с основ области действия, затем перешли к пакетам и механизму импорта. Затем мы рассмотрели, как можно использовать виртуальные среды для преодоления конфликтов версий. Но кроличья нора уходит гораздо глубже. Если вы заинтересованы в дальнейшем использовании системы импорта, то стоит проверить встроенную функцию __import__ и import importlib . Кроме того, есть больше, чем просто обычные инструкции импорта (вы можете двигаться вверх по дереву пакетов, а не вниз, это иногда бывает очень полезно). Возможно, вы заметили появление файлов .pyc во время наших экспериментов, и они сами по себе довольно круты. Мы также коснулись того факта, что Python имеет стандартные механизмы установки пакетов, о которых действительно стоит знать, если вы собираетесь развернуть или отдать какой-либо значительный фрагмент кода.