#16. PEP8. Imports

Code organization. PEP8. Imports

План

  1. PEP8
  2. Imports
  3. Література

PEP8

PEP - Python Enhanced Proposal(іноді пишеться PEP8 або PEP-8) - це документ, який містить вказівки (style guide) щодо написання коду на Python. Притримуватися обовʼязково!

Ключова ідея Гвідо ван Россума така: код читається значно частіше, ніж пишеться. Власне, рекомендації про стиль написання коду спрямовані на те, щоб покращити читабельність коду та зробити його узгодженим між великою кількістю проектів. В ідеалі, весь код має буде написаний в єдиному стилі, щоб будь-хто легко міг його прочитати. Як йдеться у PEP 20 «Readability counts.» (Читабельність має значення).

Це керівництво про узгодженість та єдність. Узгодженість із цим керівництвом дуже важлива. Узгодженість усередині одного проекту ще важливіша. А узгодженість усередині модуля чи функції – найважливіше. Але важливо пам’ятати, що іноді це керівництво не застосовується і розуміти, коли можна відійти від рекомендацій. Коли ви сумніваєтеся, просто подивіться інші приклади і вирішите, який виглядає краще.

Дві причини, щоб порушити правила:

  1. Коли застосування правила зробить код менш читабельним навіть для того, хто звик читати код, який слідує правилам.

  2. Щоб писати в єдиному стилі з кодом, який вже є у проекті і який порушує правила (можливо, з історичних причин) — проте, це можливість підчистити чужий код.

Зовнішній вигляд коду

Відступи


Використовуйте 4 пробіли на один рівень відступу.

Tabs vs spaces?

Ніколи не змішуйте символи табуляції та пробіли!

Найпоширеніший спосіб відступів – пробіли. На другому місці відступи тільки з використанням табуляції. Код, в якому використовуються і ті, і інші типи відступів, повинен бути виправлений так, щоб відступи в ньому були розставлені лише за допомогою пробілів.

У нових проектах для відступів ми рекомендуємо використовувати 4 пробіли. До того ж багато редакторів дозволяють легко робити.

Максимальна довжина рядка

Обмежте максимальну довжину рядка 79 символами.

Поки що існує чимало пристроїв, де довжина рядка дорівнює 80 символам; до того ж, обмеживши ширину вікна 80 символами, ми зможемо розташувати кілька вікон поруч один з одним. Автоматичне перенесення рядків на таких пристроях порушить форматування, і код буде важче зрозуміти. Так що, будь ласка, обмежте довжину рядка 79 символами та 72 символами у разі довгих блоків тексту (рядки документації або коментарі).

Переважний спосіб перенесення довгих рядків - використання продовження рядка між звичайними, квадратними і фігурними дужками. У разі потреби можна додати ще одну пару дужок навколо виразу, або використати зворотній слеш (). Намагайтеся зробити правильні відступи для перенесеного рядка. Переважно вставити перенос рядка після бінарного оператора, але не перед ним.

Ось кілька прикладів:

class Rectangle(Blob):
    def __init__(self, width, height,
                 color='black', emphasis=None, highlight=0):
        if width == 0 and height == 0 and \
           color == 'red' and emphasis == 'strong' or \
           highlight > 100:
            raise ValueError("sorry, you lose")
        if width == 0 and height == 0 and (color == 'red' or
                                           emphasis is None):
            raise ValueError("I don't think so - значення %s, %s" %
                             (width, height))
        Blob.__init__(self, width, height,
                      color, emphasis, highlight)

Порожні рядки

Відокремлюйте функції (верхнього рівня, не функції всередині функцій) та визначення класів двома порожніми рядками.

Визначення методів усередині класу відокремлюйте одним порожнім рядком.

Додаткові відступи рядками можуть бути зрідка використані виділення групи логічно пов’язаних функцій. Порожні рядки можуть бути пропущені між кількома виразами, записаними в один рядок, наприклад, «заглушки» функцій.

Використовуйте (без ентузіазму) порожні рядки у коді функцій, щоб відокремити один від одного логічні частини.

Кодування

Код ядра Python 2 завжди повинен використовувати ASCII або Latin-1 кодування (також відоме як ISO-8859-1).
Починаючи з Python 3.0, кращою є кодування UTF-8 (див. PEP 3120).

Використовуйте Latin-1 (або UTF-8), лише якщо це необхідно, щоб вказати в коментарі або рядку документації ім’я автора, що містить символ із Latin-1.

Починаючи з версії python 3.0, у стандартній бібліотеці діє наступна політика (дивіться PEP 3131): усі ідентифікатори повинні містити лише ASCII символи, і означати англійські слова скрізь, де це можливо (у багатьох випадках використовуються скорочення або неанглійські технічні терміни). Крім того, рядки та коментарі теж повинні містити лише ASCII символи.

Винятки становлять:

  • test case, що тестує не-ASCII особливості програми
  • імена авторів.
    Автори, літери в іменах яких не з латинського алфавіту, мають транслітерувати свої імена до латиниці.

Проекти з відкритим кодом для широкої аудиторії також рекомендують використовувати цю угоду.

Пробіли у виразах та інструкціях


Уникайте використання пробілів у таких ситуаціях:

  • Відразу після або перед дужками (звичайними, фігурними та квадратними)
# Correct:
spam(ham[1], {eggs: 2})

# Wrong:
spam (ham [1], {eggs: 2})
  • Відразу перед комою, крапкою з комою, двокрапкою:
# Correct:
if x == 4: print(x, y); x, y = y, x

# Wrong:
if x == 4 : print(x , y) ; x , y = y , x
  • Відразу перед дужкою, після якої починається список аргументів при виклику функції:
# Correct:
spam(1)

# Wrong:
spam (1)
  • Відразу перед відчиняючою дужкою, після якої слідує індекс або зріз:
# Correct:
dict['key'] = list[index]

# Wrong:
dict ['key'] = list [index]
  • Використання більше одного пробілу навколо оператора присвоєння (або будь-якого іншого) для того, щоб вирівняти його з іншим таким же оператором на сусідньому рядку:
# Correct:
x = 1
y = 2
long_variable = 3

# Wrong:
x             = 1
y             = 2
long_variable = 3

Інші рекомендації:

  • Завжди оточуйте ці бінарні оператори одним пропуском з кожного боку: присвоєння (=, +=, -= та інші), порівняння (==, <, >, != , <>, <=, >=, in, not in, is, is not), логічні оператори (and, or, not) .

  • Ставте пробіли навколо арифметичних операцій.

# Correct:
i = i + 1
submitted += 1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)

# Wrong:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a+b) * (ab)
  • Не використовуйте пробіли для відділення знака =, якщо він використовується для позначення аргумента-ключа (keyword argument) чи значення параметра за замовченням.
# Correct:
def complex(real, imag=0.0):
    return magic(r=real, i=imag)

# Wrong:
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)
  • Не використовуйте складові інструкції (кілька команд в одному рядку).
# Correct:
if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()

# Wrong:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()
  • Іноді можна писати тіло циклів while, for або гілку if у тому рядку, якщо команда коротка, але якщо команд кілька, ніколи так не пишіть.
# Wrong:
if foo == 'blah': do_blah_thing()
for x in lst: total += x
while t < 10: t = delay()

# Wrong:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()
try: something()
finally: cleanup()
do_one(); do_two(); do_three(long, argument,
                                             list, like, this)
if foo == 'blah': one(); two(); three()

Import

Імпортування різних модулів має бути на різних рядках, наприклад:

правильно:

# Correct:
import os
import sys

# Wrong:
import os, sys

У той же час, можна писати так:

from subprocess import Popen, PIPE

Імпортування завжди потрібно робити відразу після коментарів до модуля та рядків документації, перед оголошенням глобальних змінних та констант.

Групуйте імпорти у такому порядку:

  1. імпорти стандартної бібліотеки
  2. імпорти сторонніх бібліотек
  3. імпорти модулів поточного проекту

Вставляйте порожній рядок між групою імпортів.

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

Коли ви імпортуєте клас із модуля, цілком можна писати так:

from myclass import MyClass 
from foo.bar.yourclass import YourClass

Якщо таке написання викликає конфлікт імен, тоді пишіть:

import myclass 
import foo.bar.yourclass

І використовуйте myclass.MyClass і foo.bar.yourclass.Yourclass.

Коментарі

Коментарі, які суперечать коду, гірші, ніж відсутність коментарів. Завжди виправляйте коментарі, якщо ви змінюєте код!

Коментарі мають бути закінченими пропозиціями. Якщо коментар — фраза або речення, перше слово має бути написане з великої літери, якщо це не ім’я змінної, яка починається з маленької літери (до речі, ніколи не відступайте від цього правила для імен змінних).

Якщо коментар короткий, можна опустити крапку наприкінці речення. Блок коментарів зазвичай складається з одного або більше абзаців, складених із повноцінних речень, тому кожна пропозиція має закінчуватися точкою.

Ставте два пробіли після точки наприкінці пропозиції.

Програмісти, які не говорять англійською мовою, будь ласка, пишіть коментарі англійською, якщо тільки ви не впевнені на 120 відсотків, що ваш код ніколи не читатимуть люди, які не знають вашої рідної мови.

Блок коментарів

Блок коментарів зазвичай пояснює код (весь, або лише деяку частину), що йде після блоку, і повинен мати той самий відступ, що і сам код. Кожен рядок такого блоку повинен починатися з символу # і однієї пробілу після нього (якщо сам текст коментаря немає відступу).

Абзаци всередині блоку коментарів краще відокремлювати рядком, що складається з одного символу #.

Коментарі у рядку з кодом

Намагайтеся рідше використовувати такі коментарі.

Такий коментар знаходиться у тому ж рядку, що й інструкція. «Зустрічні» коментарі мають відокремлюватися хоча б двома пробілами від інструкції. Вони повинні починатися з символу # та одного пробілу.

Коментарі в рядку з кодом не потрібні і лише відволікають від читання, якщо пояснюють очевидне. Не пишіть ось так:

x = x + 1  # Increment x

Втім, іноді такі кометарі корисні:

x = x + 1  # Compensate for border

Рядки документації

Угоди про написання хорошої документації (docstrings) увічнені (так, забавно, але автор використовує саме таке слово) у PEP 257.

Пишіть документацію для всіх модулів, функцій, класів, методів, оголошених як public. Рядки документації необов’язкові для не public методів, але краще написати, що робить метод. Коментар потрібно писати після рядка з def.

PEP 257 пояснює, як правильно та добре документувати. Зауважте, дуже важливо, щоб закриваючі лапки стояли на окремому рядку, а ще краще, якщо перед ними буде ще й порожній рядок, наприклад:

"""Return a foobang

Optional plotz says to frobnicate the bizbaz first.
"""

Для однорядкової документації можна залишити “”" на тому ж рядку.

"""Return an ex-parrot."""

Імена

Угоди про імена змінних у Python трохи розмиті, тому їх список ніколи не буде повним — проте нижче є список рекомендацій, що діють на даний момент. Нові модулі та пакети мають бути написані згідно з цими стандартами, але якщо в будь-якій уже існуючій бібліотеці ці правила порушуються, краще писати в єдиному з нею стилі.

Стилі іменування

Існує багато різних стилів. Допоможемо вам розпізнати, який стиль іменування використовується незалежно від того, для чого він використовується.

Зазвичай розрізняють такі стилі:

  • b (поодинока маленька буква)
  • B (поодинока заголовна буква)
  • lowercase (слово в нижньому регістрі)
  • lower_case_with_underscores (слова з маленьких букв із підкресленнями, snake_case)
  • UPPERCASE (великі літери)
  • UPPERCASE_WITH_UNDERSCORES (слова із великих літер із підкресленнями)
  • CapitalizedWords (слова з великими літерами, або CapWords, або CamelCase. Іноді називається StudlyCaps). Примітка: коли ви використовуєте абревіатури в такому стилі, пишіть усі літери абревіатури великими - HTTPServerError краще, ніж HttpServerError.
  • mixedCase (відрізняється від CapitalizedWords тим, що перше слово починається з маленької літери)
  • Capitalized_Words_With_Underscores (слова з великими літерами та підкресленнями - жах!)

Ще існує стиль, у якому імена, що належать одній логічній групі, мають короткий префікс. Цей стиль рідко використовується в Python, але ми згадуємо його для повноти. Наприклад, функція os.stat() повертає кортеж, імена в якому традиційно мають вигляд st_mode, st_size, st_mtime і таке інше. (Так зроблено, щоб підкреслити відповідність цих полів структурі системних викликів POSIX, що допомагає знайомим із нею програмістам).

У бібліотеці X11 використовується префікс Х для всіх public-функцій. У Python цей стиль вважається зайвим, тому що перед полями та іменами методів стоїть ім’я об’єкта, а перед іменами функцій стоїть ім’я модуля.

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

  • _single_leading_underscore: слабкий індикатор того, що ім’я використовується для внутрішніх потреб. Наприклад, from M import * не буде імпортувати об’єкти, імена яких починаються з символу підкреслення.

  • single_trailing_underscore_: використовується за згодою для уникнення конфліктів з ключовими словами мови python, наприклад:

Tkinter.Toplevel(master, class_='ClassName')
  • __double_leading_underscore: змінює ім’я класу атрибута, тобто. у class FooBar поле __boo стає _FooBar__boo.

  • __double_leading_and_trailing_underscore__ (подвійне підкреслення на початку та в кінці імені): «магічні» об’єкти або атрибути, які «живуть» у просторах імен, керованих користувачем (user-controlled namespaces). Наприклад, __init__, __import__ або __file__. Не вигадуйте такі імена, використовуйте їх лише так, як написано у документації.

Стилі імен

Імена, яких слід уникати

Ніколи не використовуйте символи l (l, маленька латинська літера «ель»), O (O, заголовна латинська літера «о») або I (I, заголовна латинська літера «ай») як однолітерні ідентифікатори.

У деяких шрифтах ці символи не відрізняються від цифри один і нуля (і символу вертикальної палички). Якщо дуже потрібно використовувати l імена, пишіть замість неї заголовну L.

Імена модулів та пакетів

Модулі повинні мати короткі імена, що складаються з маленьких літер. Можна використовувати символи підкреслення, якщо це покращує читабельність. Те ж саме, за винятком символів підкреслення, відноситься і до імен пакетів.

Так як імена модулів відображаються в імена файлів, а деякі файлові системи є нечутливими до регістру символів і обрізають довгі імена, дуже важливо використовувати досить короткі імена модулів - це не проблема в Unix, але, можливо, код виявиться таким, що не підтримується у старій версії Windows або Mac , або DOS. Коли модуль розширення, написаний на С або C++, має супутній python-модуль (що містить інтерфейс високого рівня), С/С++ модуль починається з символу підкреслення, наприклад, _socket.

Імена класів

Усі імена класів повинні виконувати угоду CapitalizedWords майже без винятків. Класи внутрішнього використання можуть починатися з символу підкреслення.

Імена винятків (Exceptions)

Оскільки винятки є класами, до винятками застосовується іменування класів. Однак, ви можете додати Error в кінці імені (якщо звичайно виняток дійсно є помилкою).

Імена глобальних змінних

Сподіватимемося, що такі імена використовуються тільки всередині одного модуля. Керуйтеся тими самими угодами, що й імен функцій.

Додайте в модулі, які написані так, щоб їх використовували за допомогою from M import *, механізм __all__ щоб запобігти експорту глобальних змінних. Або, використовуйте стару угоду, додаючи перед іменами таких глобальних змінних один символ підкреслення (яким ви можете позначити ті глобальні змінні, які використовуються лише всередині модуля).

Імена функцій

Імена функцій повинні складатися з маленьких літер, а слова розділятися символами підкреслення – це необхідно, щоб збільшити читабельність.

Стиль mixedCase допускається в тих місцях, де вже переважає такий стиль, наприклад, у threading.py, для збереження зворотної сумісності.

Аргументи функцій та методів

Завжди використовуйте self як перший аргумент методу екземпляра об’єкта (instance method).

Завжди використовуйте cls як перший аргумент методу класу (class method).

Якщо ім’я аргументу конфліктує із зарезервованим ключовим словом Python, зазвичай краще додати до кінця імені символ підкреслення, ніж спотворити написання слова або використовувати абревіатуру. Таким чином, print_ краще, ніж prnt. (Можливо, хорошим варіантом буде підібрати синонім).

Імена методів та змінні екземплярів класів

Використовуйте той самий стиль, що і для імен функцій: імена повинні складатися з маленьких літер, а слова поділяються символами підкреслення.

Щоб уникнути конфлікту імен із підкласами, додайте два символи підкреслення, щоб увімкнути механізм зміни імен. Якщо клас Foo має назву атрибут з ім’ям __foo, до нього не можна звернутися, написавши Foo.__a. (Наполегливий користувач все одно може отримати доступ, написавши Foo._Foo__a). Взагалі подвійне підкреслення в іменах має використовуватися, щоб уникнути конфлікту імен з атрибутами класів, спроектованих так, щоб від них успадковували підкласи.

Константи

Константи зазвичай оголошуються лише на рівні модуля і записуються лише великими літерами, а слова поділяються символами підкреслення.
Наприклад: MAX_OVERFLOW, TOTAL.

Проектування уcпадкування

Обов’язково вирішіть, яким має бути метод класу чи змінна екземпляра класу (загалом атрибут) — public чи не-public. Якщо ви маєте сумнів, виберіть закритий, не-public атрибут. Потім буде простіше зробити їх public, ніж навпаки.

Відкриті атрибути – це ті, які використовуватимуть споживачі ваших класів, і ви повинні бути впевнені у відсутності зворотної несумісності. Не-public атрибути, у свою чергу, не призначені для використання третіми особами, тому ви можете не гарантувати, що не зміните або видаліть ці атрибути.

Ми не використовуємо термін private, тому що насправді Python таких атрибутів немає.

Інший тип атрибутів класів належить так званому API підкласам (в інших мовах вони часто називаються protected). Деякі класи проектуються так, щоб від них успадковували інші класи, які розширюють чи модифікують поведінку базового класу. Коли ви проектуєте такий клас, вирішіть і явно вкажіть, які атрибути є відкритими (public), які належать підкласів API (subclass API), а які використовуються тільки базовим класом.

Тепер сформулюємо рекомендації:

  • Відкриті атрибути не повинні мати на початку імені символу підкреслення

  • Якщо ім’я відкритого атрибута конфліктує з ключовим словом мови, додайте один символ підкреслення в кінець імені. Це краще, ніж абревіатура чи спотворення написання (проте, це правило є виняток — аргументу який означає клас, і особливо перший аргумент методу класу (class method) повинен мати ім’я cls).

  • Назвіть прості відкриті атрибути зрозумілими іменами і не пишіть складні методи доступу та зміни (accessor/mutator, get/set) Пам’ятайте, що в Python дуже легко додати їх потім, якщо потрібно. У цьому випадку використовуйте властивості (properties), щоб приховати функціональну реалізацію синтаксису доступу до атрибутів.

    • Постарайтеся позбутися побічних ефектів, пов’язаних із функціональною поведінкою; втім, такі речі, як кешування цілком допустимі.

    • Уникайте використання обчислювально-затратних операцій, тому що через запис за допомогою атрибутів створюється враження, що доступ відбувається (відносно) швидко.

  • Якщо ви плануєте клас таким чином, щоб від нього успадковувалися інші класи, але не хочете, щоб підкласи успадкували деякі атрибути, додайте в імена два символи підкреслення на початок і жодного в кінець. Механізм зміни імен у Python (name mangling) спрацює так, що ім’я класу додасться до імені такого атрибуту, що дозволить уникнути конфлікту імен з атрибутами підкласів.

    • Будьте уважні: якщо підклас матиме те саме ім’я класу та ім’я атрибута, то знову виникне конфлікт імен.

    • Механізм зміни імен може ускладнити налагодження або роботу з __getattr__(), однак він добре документований і легко реалізується вручну.

    • Не всім подобається цей механізм, тому намагайтеся досягти компромісу між необхідністю уникнути конфлікту імен та можливістю доступу до цих атрибутів.

Загальні рекомендації

Код повинен бути написаний так, щоб не залежати від різних реалізація мови (PyPy, Jython, IronPython, Pyrex, Psyco та ін.). Наприклад, не покладайтеся на ефективну реалізацію в CPython конкатенації рядків у виразах типу a+=b або a=a+b. Такі інструкції виконуються значно повільніше у Jython. У критичних часах програми використовуйте ''.join() — таким чином склеювання рядків буде виконано за лінійний час незалежно від реалізації python.

Порівняння з None повинні обов’язково виконуватися з допомогою операторів is чи is not, а чи не з допомогою операторів рівності чи нерівності. Крім того, не пишіть if x, якщо маєте на увазі if x is not None - якщо, наприклад, при тестуванні така змінна або аргумент набуде значення іншого типу, то при приведенні до булевського типу вийде false.

Створюйте винятки на основі класів. Втім, починаючи з версії Python 2.6, ми вже не можемо використовувати рядки як винятки. У модулях або пакетах створюйте свої базові класи винятків, успадковуючи їх від вбудованого класу Exception та обов’язково їх документуйте:

class MessageError(Exception):
"""Base class for errors in the email package."""

Тут застосовні самі правила, як і назви класів. Якщо виняток є помилкою, ви можете додати в кінці імені Error.

Коли ви генеруєте виняток, пишіть raise ValueError('message') замість старого синтаксису raise ValueError, message. Таке використання краще, тому що через дужки не потрібно використовувати символи для продовження перенесених рядків, якщо ці рядки довгі або якщо використовується форматування. Стара форма запису заборонена у Python 3.

Коли код перехоплює винятки, ловіть конкретні помилки замість простого вираження except:. Наприклад, пишіть ось так:

try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

Просте написання except: також перехопить і SystemExit, і KeyboardInterrupt, що приведе до проблем, наприклад, складніше завершити програму натисканням control+C. Якщо ви дійсно маєте намір перехопити всі винятки, пишіть except Exception.

Обмежтеся використанням чистого except у двох випадках:

  1. Якщо обробник вийнятку виводить користувачу все про помилку, що трапилася (наприклад, traceback)
  2. Якщо потрібно виконати деякий код після перехоплення виключення, а потім знову “кинути” його для обробки десь в іншому місці. Зазвичай краще користуватися конструкцією try...finally.
    Постарайтеся укладати в кожну конструкцію try...except мінімум коду, щоб легше відловлювати помилки.
try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)

try:
    # Тут багато дій!
    return handle_value(collection[key])
except KeyError:
    # Тут також перехопиться KeyError, згенерований handle_value()
    return key_not_found(key)

Використовуйте рядкові методи замість модуля string - вони завжди швидше і мають той же API для unicode-рядків. Можна відмовитись від цього правила, якщо необхідна сумісність з версіями Python молодше 2.0.

Користуйтесь .startswith() та .endswith() замість обробки частин рядків (string slicing) для перевірки суфіксів або префіксів. startswith() і endswith() виглядають чистішими і породжують менше помилок. Наприклад:

# Correct:
if foo.startswith('bar'):

# Wrong:
if foo[:3] == 'bar':

Порівняння типів об’єктів потрібно робити за допомогою isinstance(), а не прямим порівнянням типів:

# Correct:
if isinstance(obj, int):

# Wrong:
if type(obj) is type(1):

Для послідовностей (рядків, списків, кортежів) можна використовувати той факт, що порожня послідовність є False:

# Correct:
if not seq:
if seq:

# Wrong:
if len(seq):
if not len(seq):

Не користуйтеся рядковими константами, які мають важливі пробіли в кінці — вони невидимі, а багато редакторів обрізають їх.

Не порівнюйте логічні типи з True та False за допомогою ==:

# Correct:
if greeting:

# Wrong:
if greeting == True:

# Wrong:
if greeting is True:

Imports

Основні визначення

  • Модуль: будь-який файл *.py. Ім’я модуля – ім’я цього файлу.
  • Вбудований модуль: модуль, який був написаний на С, скомпільований і вбудований в інтерпретатор Python, і тому немає файлу *.py.
  • Пакет: будь-яка папка, яка містить файл __init__.py.
  • Ім’я пакета – ім’я папки. З версії Python 3.3 будь-яка папка (навіть без __init__.py) вважається пакетом.
  • Об’єкт: у Python майже все є об’єктом - функції, класи, змінні і т.д.

Приклад структури директорій

test/ # Коренева папка
    packA/ # Пакет packA
        subA/ # Підпакет subA
            __init__.py
            sa1.py
            sa2.py
        __init__.py
        a1.py
        a2.py
    packB/ # Пакет packB (неявний пакет простору імен)
        b1.py
        b2.py
    math.py
    random.py
    other.py
    start.py

Зверніть увагу, що в кореневій папці test/ немає файлу __init__.py.

Що робить import

При імпорті модуля Python виконує весь код у ньому. При імпорті пакета Python виконує код у файлі пакета __init__.py, якщо такий є. Усі об’єкти, визначені в модулі або __init__.py, стають доступними для імпортуючого.

Основи import та sys.path

import spam

Ось як оператор import здійснює пошук потрібного модуля або пакета згідно з документацією Python:

При імпорті модуля spam, інтерпретатор спочатку шукає вбудований модуль з таким ім’ям. Якщо такого модуля немає, то йде пошук spam.py у списку директорій, визначених у змінній sys.path. sys.path ініціалізується з наступних місць:

  • директорії, що містить вихідний скрипт (або поточної директорії, якщо файл не вказано);
  • директорії за замовчуванням, що залежить від дистрибутива Python;
  • PYTHONPATH (список імен директорій; має синтаксис, аналогічний змінній оточенню PATH).

Програми можуть змінювати змінну sys.path після її ініціалізації. Директорія, що містить скрипт, що запускається, поміщається в початок пошуку перед шляхом до стандартної бібліотеки. Це означає, що скрипти в цій директорії будуть імпортовані замість модулів із такими ж іменами у стандартній бібліотеці.

Технічно документація не зовсім повна. Інтерпретатор шукатиме не лише файл (модуль) spam.py, а й папку (пакет) spam.

Зверніть увагу, що Python спочатку здійснює пошук серед вбудованих модулів - тих, які вбудовані безпосередньо в інтерпретатор. Список вбудованих модулів залежить від дистрибутива Python, а знайти цей список можна в sys.builtin_module_names (Python 2 та Python 3). Зазвичай у дистрибутивах є модулі sys (завжди включений у дистрибутив), math, itertools, time та інші.

На відміну від вбудованих модулів, які під час пошуку перевіряються першими, інші (не вбудовані) модулі стандартної бібліотеки перевіряються після директорії запущеного скрипта. Це призводить до поведінці, що збиває з пантелику: можливо «замінити» деякі, але не всі модулі стандартної бібліотеки. Допустимо, модуль math є вбудованим модулем, а random - ні. Таким чином, import math у start.py імпортує модуль із стандартної бібліотеки, а не наш файл math.py з тієї ж директорії. У той же час, import random у start.py імпортує наш файл random.py.

Крім того, імпорти в Python реєстрозалежні: import Spam та import spam - різні речі.

Функцію pkgutil.iter_modules() (Python 2 та Python 3) можна використовувати, щоб отримати список усіх модулів, які можна імпортувати із заданого шляху:

import pkgutil
search_path = ['.'] # Використовуйте None, щоб побачити всі модулі, що імпортуються з sys.path
all_modules = [x[1] for x in pkgutil.iter_modules(path=search_path)]
print(all_modules)

Трохи докладніше про sys.path

Щоб побачити вміст sys.path, запустіть цей код:

import sys
print(sys.path)

Документація Python описує sys.path так:

Список рядків, що вказують шляхи пошуку модулів. Ініціалізується із змінної оточення PYTHONPATH та директорії за умовчанням, яка залежить від дистрибутива Python.

При запуску програми після ініціалізації першим елементом цього списку, path[0], буде директорія, що містить скрипт, який був використаний для виклику інтерпретатора Python. Якщо директорія скрипта недоступна (наприклад, якщо інтерпретатор був викликаний в інтерактивному режимі або скрипт зчитується зі стандартного введення), то path[0] є порожнім рядком. Через це Python спочатку шукає модулі у поточній директорії. Зверніть увагу, що директорія скрипта вставляється перед шляхами, взятими з PYTHONPATH.

Документація до інтерфейсу командного рядка Python додає інформацію про запуск скриптів із командного рядка. Зокрема, під час запуску python <script>.py.

Якщо ім’я скрипта посилається безпосередньо на файл Python, то директорія, що містить цей файл, додається на початок sys.path, а файл виконується як модуль main.

Отже, повторимо порядок, за яким Python шукає імпортовані модулі:

  • Модулі стандартної бібліотеки (наприклад, math, os).
  • Модулі або пакети, зазначені в sys.path:

Якщо інтерпретатор Python запущено в інтерактивному режимі:

sys.path[0] - порожній рядок ‘’. Це означає, що Python шукатиме в поточній робочій директорії, з якої ви запустили інтерпретатор. У Unix-системах цю директорію можна дізнатися з допомогою команди pwd.

Якщо ми запускаємо скрипт командою python <script>.py:

sys.path[0] - це шлях до <script>.py.

Директорії, зазначені у змінному середовищі PYTHONPATH.

Директорія за замовчуванням залежить від дистрибутива Python.

Зверніть увагу, що при запуску скрипта для sys.path важлива не директорія, в якій ви знаходитесь, а шлях до самого скрипта. Наприклад, якщо в командному рядку ми знаходимося в test/folder і запускаємо команду python ./packA/subA/subA1.py, то sys.path буде включати в себе test/packA/subA/, але не test/.

Крім того, sys.path загальний для всіх імпортованих модулів. Допустимо, ми викликали python start.py. Нехай start.py імпортує packA.a1, а a1.py виводить на екран sys.path. У такому разі sys.path буде включати test/ (шлях до start.py), але не test/packA (шлях до a1.py). Це означає, що a1.py може викликати import other, тому що other.py знаходиться в test/.

Все про __init__.py

Файл __init__.py має дві функції:

  • Перетворити папку зі скриптами на імпортований пакет модулів (до Python 3.3).
  • Виконати код ініціалізації пакета.

Перетворення папки зі скриптами на імпортований пакет модулів

Щоб імпортувати модуль (або пакет) з директорії, яка знаходиться не в директорії нашого скрипта (або не в директорії, з якої ми запускаємо інтерактивний інтерпретатор), цей модуль має бути в пакеті.

Як було зазначено раніше, будь-яка директорія, що містить файл __init__.py, є пакетом. Наприклад, при роботі з Python 2.7 start.py може імпортувати пакет packA, але не packB, тому що в директорії test/packB/ немає файлу __init__.py.

Це не стосується Python 3.3 і вище завдяки появі неявних пакетів просторів імен. Простіше кажучи, у Python 3.3+ усі папки вважаються пакетами, тому порожні файли __init__.py більше не потрібні.

Допустимо, packB - пакет простору імен, тому що в ньому немає __init__.py. Якщо запустити інтерактивну оболонку Python 3 в директорії test/, ми побачимо наступне:

>>> import packB
>>> packB
<module 'packB' (namespace)>

Виконання коду ініціалізації пакету

У той час, коли пакет або один з його модулів імпортується вперше, Python виконує __init__.py в корені пакета, якщо такий файл існує. Усі об’єкти та функції, визначені в __init__.py, вважаються частиною простору імен пакета.

Розглянемо наступний приклад:
test/packA/a1.py

def a1_func():
    print("Run a1_func()")

test/packA/init.py

## Цей імпорт робить функцію a1_func() доступною безпосередньо з packA.a1_func
from packA.a1 import a1_func

def packA_func():
    print("Run packA_func()")

test/start.py

import packA # «import packA.a1» спрацює так само

packA.packA_func()
packA.a1_func()
packA.a1.a1_func()

Висновок після запуску python start.py:

Run packA_func()
Run a1_func()
Run a1_func()

  • Примітка:
    Якщо a1.py викликає import a2, і ми запустимо python a1.py, то test/packA/__init__.py не буде викликаний, незважаючи на те, що a2 як би є частиною пакета packA. Це пов’язано з тим, що коли Python виконує скрипт (в даному випадку a1.py), папка, що містить його, не вважається пакетом.

Використання об’єктів з імпортованого модуля або пакета

Є 4 різні види імпортів:

- import <пакет>
- import <модуль>
- from <пакет> import <модуль чи підпакет чи об'єкт>
- from <модуль> import <об'єкт>

Нехай X - ім’я того, що йде після import:

Якщо X - ім’я модуля або пакета, то для того, щоб використовувати об’єкти, визначені в X, доведеться писати X.об’єкт.

Якщо X — ім’я змінної, її можна використовувати напряму.

Якщо X — ім’я функції, її можна викликати з допомогою X().

Опціонально після будь-якого вираження import X можна додати as Y. Це перейменує X на Y в межах скрипта. Зверніть увагу, що ім’я X з цього моменту стає недійсним. Частим прикладом такої конструкції є

import numpy as np

Аргументом для import може бути як одне ім’я, так і їхній список. Кожне з назв можна перейменувати за допомогою as. Наприклад, наступне вираз буде дійсно в start.py:

import packA as pA, packA.a1, packA.subA.sa1 as sa1`.

Приклад: потрібно в start.py імпортувати функцію helloWorld() з sa1.py.

Рішення 1:

from packA.subA.sa1 import helloWorld

Ми можемо викликати функцію безпосередньо на ім’я:
x = helloWorld()

Рішення 2:

from packA.subA import sa1

або те ж саме

import packA.subA.sa1 as sa1

Для використання функції потрібно додати перед її ім’ям ім’я модуля:
x = sa1.helloWorld()
Іноді такий підхід краще першого, оскільки стає ясно, з якого модуля взялася та чи інша функція.

Рішення 3:

import packA.subA.sa1

Для використання функції перед її ім’ям потрібно додати повний шлях:
x = packA.subA.sa1.helloWorld().

Використовуємо dir() для дослідження вмісту імпортованого модуля

Після імпортування модуля можна використовувати функцію dir() для отримання списку доступних у модулі імен. Допустимо, ми імпортуємо sa1. Якщо sa1.py є функція helloWorld(), то dir(sa1) буде включати helloWorld:

>>> from packA.subA import sa1
>>> dir(sa1)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'helloWorld']

Імпортування пакетів

Імпортування пакета по суті рівноцінно імпорту його __init__.py. Ось як Python насправді бачить пакет:

>>> import packA
>>> packA
<module 'packA' from 'packA/__init__.py'>

Після імпорту стають доступними лише ті об’єкти, що визначені в __init__.py пакеті. Оскільки в packB немає такого файлу, від import packB (у Python 3.3.+) буде мало користі, тому що ніякі об’єкти з цього пакета не стають доступними. Наступний виклик модуля packB.b1 призведе до помилки, оскільки він ще не був імпортований.

Абсолютний та відносний імпорт

При абсолютному імпорті використовується повний шлях (від початку кореневої папки проекту) до бажаного модуля.

При відносному імпорті використовується відносний шлях (починаючи з поточного модуля) до бажаного модуля. Є два типи відносних імпортів:

При явному імпорті використовується формат from .<модуль/пакет> import X, де символи крапки . показують, на скільки директорій «нагору» треба піднятися. Одна крапка. показує поточну директорію, дві крапки .. - на одну директорію вище і т.д.

Неявний відносний імпорт пишеться так, ніби поточна директорія була частиною sys.path. Такий тип імпорту підтримується тільки в Python 2.

У документації Python про відносні імпорти в Python 3 написано:

Єдиний прийнятний синтаксис для відносних імпортів from .[модуль] import [ім'я]. Усі імпорти, які починаються не з крапки, вважаються абсолютними.

Як приклад припустимо, що ми запускаємо start.py, який імпортує a1, який імпортує other, a2 та sa1. Тоді імпорти в a1.py виглядатимуть так:

Абсолютні імпорти:

import other
import packA.a2
import packA.subA.sa1

Явні відносні імпорти:

import other
from . import a2
from .subA import sa1

Неявні відносні імпорти (не підтримуються в Python 3):

import other
import a2
import subA.sa1

Зверніть увагу, що у відносних імпортах за допомогою крапок . можна дійти лише до директорії, що містить запущений із командного рядка скрипт (не включно). Таким чином, from .. import other не спрацює в a1.py. В результаті ми отримаємо помилку ValueError: attempted relative import beyond top-level package.

Як правило, абсолютні імпорти краще відносних. Вони дозволяють уникнути плутанини між явними та неявними імпортами. Крім того, будь-який скрипт із явними відносними імпортами не можна запустити безпосередньо.

Майте на увазі, що відносні імпорти базуються на імені поточного модуля. Оскільки ім’я головного модуля завжди є __main__, модулі, які повинні використовуватися як головний модуль програми, повинні завжди використовувати абсолютні імпорти.

Практика і домашнє завдання:

Продовжуємо завдання з попереднього уроку.

  1. Створити метод check_salary(self, days), який розраховує ЗП за передану кількість днів.
  2. ** Зробити можливим, щоб метод check_salary рахував ЗП з початку місяця до поточного дня, не враховуючи вихідні дні.
  3. Додати в конструктор класу Developer атрибут tech_stack (список з назвами технологій).
  4. Для класу Developer зробити порівняння за кількістю технологій.
  5. Зробити можливим операцію додавання об’єктів класу Developer. Результатом має бути новий об’єкт, в якому name = name1 + ‘ ’ + name2, a tech_stack - список з технологій двох об’єктів (тільки унікальні значення), ЗП - більша з двох.

Література

  1. PEP 8 – Style Guide for Python Code
  2. How to Write Beautiful Python Code With PEP 8