Прагнете покращити свої навички програмування на Python? Дізнайтеся, як “Дзен Python” може стати вашим надійним провідником у цьому процесі.
Python відомий своєю легкістю у вивченні. Проте, створення коду, що відповідає стандартам Python та є легким у підтримці, може бути непростим завданням, особливо для новачків. PEP-20 представив “Дзен Python” – вірш авторства Тіма Пітерса, що підкреслює важливість написання коду на Python, який відповідає найкращим практикам.
Для ознайомлення з “Дзен Python”, запустіть інтерпретатор Python та введіть команду:
>>> import this
The Zen of Python, by Tim Peters Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. Complex is better than complicated. Flat is better than nested. Sparse is better than dense. Readability counts. Special cases aren't special enough to break the rules. Although practicality beats purity. Errors should never pass silently. Unless explicitly silenced. In the face of ambiguity, refuse the temptation to guess. There should be one-- and preferably only one --obvious way to do it. Although that way may not be obvious at first unless you're Dutch. Now is better than never. Although never is often better than *right* now. If the implementation is hard to explain, it's a bad idea. If the implementation is easy to explain, it may be a good idea. Namespaces are one honking great idea -- let's do more of those!
Як можна побачити, багато з висловлювань в “Дзен Python” не супроводжуються поясненнями. Деякі з них слід розглядати в поєднанні з наступними, в той час як інші можуть суперечити попередньому. Незважаючи на це, “Дзен Python” є цікавим, захоплюючим та практичним для вивчення!
Інтерпретація “Дзен Python”
Вважається, що “Дзен Python” містить 20 основоположних принципів програмування на Python. Проте, наразі відомо лише 19 висловлювань. Розгляньмо їх детальніше.
Красиве краще, ніж потворне.
Цей принцип підкреслює важливість написання елегантного коду, який відповідає філософії Python.
Наведений нижче фрагмент коду має ознаки неефективного стилю:
def square(num): squares = [] for i in range(num): squares.append(i*i) return squares
Ця функція:
- Створює порожній список.
- Використовує цикл для додавання елементів в кінець списку.
- Повертає створений список.
Хоча функція виконує свою задачу, вона не є “pythonic” і її важко підтримувати.
Цю ж функціональність можна реалізувати набагато елегантніше, використовуючи генератори. Ось варіант з генератором:
def square(num): for i in range(num): yield i*i
Або ще краще, за допомогою генераторного виразу:
num = ... squares = (i*i for i in range(num))
Явне краще, ніж неявне.
При написанні коду, не залишайте іншим розробникам та користувачам можливості здогадуватися про неявну поведінку коду. Будьте явними. Розглянемо приклад використання символу підстановки при імпорті:
from some_module import * # імпорт з використанням підстановки from some_other_module import * result = some_function() # звідки ця функція?
Настільки, наскільки це можливо, уникайте використання імпорту з підстановкою, оскільки він не є явним та неефективним. Замість цього, імпортуйте конкретні функції та класи з інших модулів:
from some_module import this_function # явний імпорт result = this_function() # тепер ми знаємо.
Просте краще, ніж складне.
Цей принцип наголошує на необхідності створення простого коду та уникнення зайвої складності. Наприклад, якщо вам потрібно перевернути рядок, ви можете реалізувати рекурсивне рішення:
def reverse_string(my_string): if my_string == "": return my_string else: return reverse_string(my_string[1:]) + my_string[:1]
Хоча це працює, це, ймовірно, надмірно складне рішення, оскільки є простіші та “pythonic” способи досягти того ж результату.
Ось варіант з використанням нарізки рядків:
>>> rev_string = my_string[::-1] >>> rev_string 'nohtyP'
А ось спосіб з використанням вбудованих методів та функцій:
>>> rev_string = ''.join(reversed(my_string)) >>> rev_string 'nohtyP'
Складне краще, ніж заплутане.
Який сенс цього наступного принципу “Дзен Python”?
Перевертання рядка в Python – це досить проста операція. Однак, в реальних умовах, нам може знадобитися складніша логіка. Розглянемо наступний приклад:
Припустимо, вам потрібно з’єднатися з базою даних:
- Спочатку потрібно проаналізувати конфігураційний файл toml, щоб отримати інформацію про з’єднання з базою даних.
- Потрібно встановити з’єднання з базою даних.
- Далі, потрібно створити функцію для з’єднання з базою даних, обробки можливих помилок, тощо.
- І, нарешті, після з’єднання, можна виконувати запити до бази даних.
Хоча це все ще досить просто, це потребує складнішої логіки, ніж просте перевертання рядка. Але це не означає, що все повинно бути заплутаним. Ви все ще можете ефективно використовувати функціонал вбудованих модулів та структурувати свій код так, щоб іншим розробникам було легко його читати, розуміти та вносити зміни.
Плоске краще, ніж вкладене.
Просту структуру легше аналізувати та розуміти, ніж вкладену. Працюючи над проєктом, може виникнути спокуса розділити функціональність, створивши окремі модулі. Однак, занадто велике розбиття може бути надмірним.
З іншого боку, часто потрібно виходити за рамки простої структури. Але навіть якщо вам потрібно використовувати вкладення, зведіть його до мінімуму.
Приклад:
from db_info.config.actions.parse.parse_config import parse_toml # занадто складно розібратися! ... from db_config.parse_config import parse_toml # набагато краще! ...
Розріджене краще, ніж щільне.
Якщо ви тільки починаєте свій шлях розробника, у вас може виникнути спокуса надмірно використовувати певні можливості мови. Наприклад, генератори списків є “pythonic”, але лише тоді, коли ви використовуєте їх доречно.
Розгляньте наступний генератор списків:
prices_dict = {'melons':40,'apples':70,'berries':55} items = [(fruit,price) for fruit in prices_dict.keys() if fruit.startswith('m') for price in prices_dict.values() if price < 50] print(items) # Output: [('melons', 40)]
Цей генератор списків занадто щільний і його важко розібрати. В цьому випадку, використання звичайного циклу `for` з умовними виразами буде більш читабельним. Це означає, що цей генератор важко зрозуміти. 🙂
Читабельність має значення.
Ви завжди повинні писати читабельний код. Ось декілька простих способів покращити читабельність вашого коду:
- Використовуйте описові імена змінних.
- Додавайте рядки документації для функцій та класів.
- Коментуйте код, де це необхідно.
- Додавайте підказки типів для аргументів та типів повернення функцій.
Особливі випадки не настільки особливі, щоб порушувати правила.
Ви повинні, наскільки це можливо, дотримуватися правил мови та рекомендованих найкращих практик.
Але чи завжди це можливо? Ні, і тому в нас є наступний принцип.
Хоча практичність переважає чистоту.
Це продовження попереднього принципу. Хоча рекомендується дотримуватися правил мови, в деяких випадках допустимо відступати від деяких з них.
Помилки ніколи не повинні проходити мовчки.
В програмах на Python помилки досить поширені. За хорошою практикою, ви завжди повинні обробляти помилки, а не замовчувати їх, як швидке рішення.
Ви можете передбачити та реалізувати відповідну обробку помилок для різних типів помилок:
try: # виконання певних дій except ErrorType1: # обробка помилки типу 1 except ErrorType2: # обробка помилки типу 2 ...
Варто уникати простих та загальних винятків. Новіші версії Python (починаючи з Python 3.11) підтримують ланцюжки та групи винятків для більш складної обробки.
Якщо явно не замовчувати.
Це слідує за попереднім принципом. Якщо архітектура вимагає або дозволяє замовчувати помилку, то це має бути зроблено явно.
Наприклад, при з’єднанні з базою даних, ви можете зіткнутися з помилкою `OperationalError` через неправильну конфігурацію. Спробуйте з’єднатися з базою даних використовуючи кастомну конфігурацію. Якщо виникає `OperationalError`, тоді використовуйте конфігурацію за замовчуванням та спробуйте з’єднатися з базою даних знову.
try: # підключення з використанням кастомної конфігурації except OperationalError: # підключення з використанням стандартної конфігурації
Зіткнувшись з двозначністю, відмовтеся від спокуси вгадати.
Цей принцип “Дзен Python” є самоочевидним. Якщо є сумніви, не гадайте. Запустіть код та перевірте результат. Далі, залежно від того, чи відповідає результат вашим очікуванням, покращте читабельність або змініть логіку за потреби.
Розглянемо простий приклад з кортежем булевих значень:
>>> True, True == (True, True) (True, False) >>> True, (True == (True, True)) (True, False) >>> (True, True) == (True, True) True
Має бути один — і бажано тільки один — очевидний спосіб зробити це.
Для виконання конкретного завдання має існувати один і тільки один рекомендований “pythonic” спосіб зробити це. Однак, для будь-якої проблеми може бути декілька рішень.
Навіть у простому прикладі перевертання рядка, ми розглянули рекурсивне рішення, з використанням нарізки рядка та метод `join()`.
Це також є внутрішнім жартом, враховуючи непослідовне використання тире. Зазвичай, ми використовуємо тире без пробілів на початку та в кінці. Або ми використовуємо його з пробілами на початку та в кінці.
Отже, ось до якого висновку ми можемо дійти. Принцип, який наголошує на тому, що має бути один — і тільки один — “pythonic” спосіб робити речі, сам по собі може бути написаний більш ніж двома способами.
Хоча спочатку цей шлях може бути неочевидним, якщо ви не голландець.
Цей жартівливий вислів відноситься до Гвідо ван Россума, творця Python (який є голландцем). (Найбільш) “pythonic” спосіб виконання конкретного завдання є природним лише для творців Python.
Тому, розробникам потрібен досвід — та навчання на власному досвіді — для кращого використання особливостей мови.
Зараз краще, ніж ніколи.
Як і декілька інших принципів “Дзен Python”, цей також можна інтерпретувати декількома різними способами.
Одна з інтерпретацій полягає в тому, що для розробника досить часто затягувати початок кодування проєкту. Замість того, щоб чекати, щоб спланувати всі найдрібніші деталі проєкту, краще почати зараз.
Інша можлива інтерпретація: код, що виконується за кінцеву кількість кроків та завершується, часто кращий за код, який має помилки та застрягає в нескінченному циклі.
Хоча ніколи не буває краще, ніж зараз.
Цей принцип ніби суперечить попередньому. Хоча краще не зволікати, ми все ж повинні обміркувати проблему та розробити код відповідним чином.
Кодувати модуль без належного обмірковування, наповнюючи його кодовими “запахами” та антишаблонами, є поганою ідеєю. Тому що такий код важко рефакторизувати та вносити виправлення.
Якщо реалізацію важко пояснити, це погана ідея.
Будь-яку логіку, якою б складною вона не була, завжди можна реалізувати у формі, яку легко пояснити та зрозуміти.
Якщо реалізацію важко пояснити, ймовірно, в коді є непотрібна складність. Код можна змінити або переробити так, щоб за ним було легше слідкувати.
Якщо реалізацію легко пояснити, це може бути хорошою ідеєю.
Цей принцип пов’язаний з попереднім і є самоочевидним. Якщо реалізацію можна пояснити простими словами, то це, скоріш за все, гарна ідея.
Тому що такий код, реалізацію якого можна описати простими словами, найімовірніше буде читабельним та легким у використанні з мінімальною складністю.
Простори імен — це одна чудова ідея — давайте робити більше таких!
В Python доступ до об’єктів у певній області можна отримати, використовуючи їхні імена в просторі імен. Наприклад, ви можете створити клас і використовувати його як шаблон для створення екземплярів класу. Тепер усі змінні екземпляра будуть в просторі імен екземпляра.
Це дозволяє використовувати об’єкти з однаковими іменами без конфліктів, оскільки вони знаходяться в різних просторах імен. Проте, ви повинні використовувати їх лише за потреби та слідкувати, щоб простота та читабельність коду не була порушена.
Висновок
На цьому наш посібник завершено! Сподіваємося, що ця стаття допомогла вам зрозуміти, як “Дзен Python” підкреслює важливість стилю коду та хороших практик кодування на Python. Чим більше ви кодуєте, тим краще у вас це виходить.
Якщо ви хочете навчитися писати стислий та читабельний код, прочитайте цю статтю про однорядковий код в Python.