# Поширені запитання про оформлення та історію

Зміст

- Поширені запитання про оформлення та історію

  - Чому Python використовує відступи для групування операторів?

  - Чому я отримую дивні результати під час простих арифметичних операцій?

  - Чому обчислення з плаваючою комою настільки неточні?

  - Чому рядки Python немутабельні?

  - Чому «self» має використовуватися явно у визначеннях і викликах методів?

  - Чому я не можу використовувати присвоєння у виразі?

  - Чому Python використовує методи для одних функцій (наприклад, list.index()), а функції для інших (наприклад, len(list))?

  - Чому метод join() є методом рядків, а не методом списку чи кортежу?

  - Як швидко працюють винятки?

  - Чому в Python немає оператора switch або case?

  - Чи не можна емулювати потоки в інтерпретаторі замість того, щоб покладатися на реалізацію потоку, специфічного для ОС?

  - Чому лямбда-вирази не можуть містити оператори?

  - Чи можна Python скомпілювати до машинного коду, мови C чи іншої?

  - Як Python керує пам’яттю?

  - Чому CPython не використовує більш традиційну схему збирання сміття?

  - Чому не вся пам’ять звільняється, коли CPython завершує роботу?

  - Чому існують окремі типи даних кортежу та списку?

  - Як списки реалізовані в CPython?

  - Як реалізовані словники в CPython?

  - Чому ключі словника повинні бути незмінними?

  - Чому list.sort() не повертає відсортований список?

  - Як визначити та застосувати специфікацію інтерфейсу в Python?

  - Чому немає goto?

  - Чому необроблені рядки (r-рядки) не можуть закінчуватися зворотною косою рискою?

  - Чому в Python немає оператора «with» для призначення атрибутів?

  - Чому генератори не підтримують оператор with?

  - Чому в операторах if/while/def/class потрібні двокрапки?

  - Чому Python допускає коми в кінці списків і кортежів?

## Чому Python використовує відступи для групування операторів?

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

Оскільки немає початкових/кінцевих дужок, не може бути розбіжностей між групуванням, сприйнятим синтаксичним аналізатором і людиною, що читає код. Час від часу програмісти на C стикаються з таким фрагментом коду:

    if (x <= y)
            x++;
            y--;
    z++;

Тільки оператор `x++` виконується, якщо умова виконується, але відступ змушує багатьох вважати протилежне. Навіть досвідчені С-програмісти іноді довго дивляться на нього, дивуючись, чому `y` зменшується навіть для `x > y`.

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

Багато стилів кодування розміщують початкові/кінцеві дужки на рядку самостійно. Це робить програми значно довшими та витрачає дорогоцінний простір на екрані, що ускладнює гарний огляд програми. В ідеалі функція повинна міститися на одному екрані (скажімо, 20–30 рядків). 20 рядків Python можуть виконувати набагато більше роботи, ніж 20 рядків C. Це пов’язано не лише з відсутністю початкових/кінцевих дужок – відсутність декларацій і високорівневих типів даних також відповідає – але оснований на відступах синтаксис, звичайно, допомагає.

## Чому я отримую дивні результати під час простих арифметичних операцій?

Дивіться наступне запитання.

## Чому обчислення з плаваючою комою настільки неточні?

Користувачів часто дивують такі результати:

    >>> 1.2 - 1.0
    0.19999999999999996

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

Тип `float` у CPython використовує C `double` для зберігання. Значення об’єкта `float` зберігається у двійковій формі з плаваючою комою з фіксованою точністю (зазвичай 53 біти), і Python використовує операції C, які, у свою чергу, покладаються на апаратну реалізацію в процесорі, щоб виконувати операції з плаваючою комою. Це означає, що в операціях з плаваючою комою Python поводиться як багато популярних мов, включаючи C і Java.

Many numbers that can be written easily in decimal notation cannot be expressed exactly in binary floating-point. For example, after:

    >>> x = 1.2

значення, яке зберігається для `x`, є (дуже точним) наближенням до десяткового значення `1.2`, але не зовсім дорівнює йому. На типовій машині фактичне збережене значення:

    1.0011001100110011001100110011001100110011001100110011 (binary)

а саме:

    1.1999999999999999555910790149937383830547332763671875 (decimal)

Типова точність в 53 біти забезпечує числа з плаваючою точкою Python з точністю 15–16 десяткових цифр.

For a fuller explanation, please see the floating point arithmetic chapter in the Python tutorial.

## Чому рядки Python немутабельні?

Є кілька переваг.

Одна з них — це продуктивність: знання того, що рядок є незмінним, означає, що ми можемо виділити для нього місце під час створення, а вимоги до пам’яті є фіксованими та незмінними. Це також одна з причин відмінності між кортежами та списками.

Ще одна перевага полягає в тому, що рядки в Python вважаються такими ж «елементарними», як і числа. Жодна активність не змінить значення 8 на щось інше, а в Python жодна активність не змінить рядок «вісім» на щось інше.

## Чому «self» має використовуватися явно у визначеннях і викликах методів?

Ідея була запозичена з Modula-3. Це виявляється дуже корисним з різних причин.

По-перше, більш очевидно, що ви використовуєте метод або атрибут екземпляра замість локальної змінної. Читання `self.x` або `self.meth()` робить абсолютно зрозумілим, що використовується змінна екземпляра або метод, навіть якщо ви не знаєте визначення класу напам’ять. У C++ це можна визначити за відсутністю оголошення локальної змінної (якщо глобальні значення рідкісні або легко впізнавані), але в Python немає оголошення локальної змінної, тому вам доведеться шукати визначення класу, щоб бути впевненим. Деякі стандарти кодування C++ і Java вимагають, щоб атрибути екземплярів мали префікс `m_`, тому ця чіткість все ще корисна в цих мовах.

Second, it means that no special syntax is necessary if you want to explicitly reference or call the method from a particular class. In C++, if you want to use a method from a base class which is overridden in a derived class, you have to use the `::` operator – in Python you can write `baseclass.methodname(self, <argument list>)`. This is particularly useful for `__init__()` methods, and in general in cases where a derived class method wants to extend the base class method of the same name and thus has to call the base class method somehow.

Нарешті, для змінних екземпляра, такий підхід вирішує синтаксичну проблему з призначенням: оскільки локальні змінні в Python — це (за визначенням!) ті змінні, яким присвоєно значення в тілі функції (і які явно не оголошені глобальними), необхідно бути якимось способом повідомити інтерпретатору, що це призначено для встановленния значення змінній екземпляра, а не локальній змінній, і бажано, щоб воно було синтаксичним (з причин ефективності). C++ робить це за допомогою декларацій, але Python не має декларацій, і було б шкода вводити їх лише для цієї мети. Використання явного `self.var` добре вирішує цю проблему. Подібним чином, для використання змінних екземпляра необхідність запису `self.var` означає, що посилання на некваліфіковані імена всередині методу не потребують пошуку в каталогах екземпляра. Іншими словами, локальні змінні та змінні екземплярів живуть у двох різних просторах імен, і вам потрібно сказати інтерпритатору Python, який простір імен використовувати.

## Чому я не можу використовувати присвоєння у виразі?

Починаючи з Python 3.8, ви можете!

Assignment expressions using the walrus operator `:=` assign a variable in an expression:

    while chunk := fp.read(200):
       print(chunk)

Перегляньте PEP 572 для отримання додаткової інформації.

## Чому Python використовує методи для одних функцій (наприклад, list.index()), а функції для інших (наприклад, len(list))?

Як сказав Гвідо:

> (a) Для деяких операцій префіксна нотація просто читається краще, ніж постфіксна — префіксні (та інфіксні!) операції мають давню традицію в математиці, яка любить нотації, де візуальні елементи допомагають математику думати про проблему. Порівняйте легкість, за допомогою якої ми переписуємо формулу на кшталт x*(a+b) на x*a + x*b, із незграбністю виконання того ж самого, використовуючи чисту нотацію OO.

> (b) Коли я читаю код, який каже len(x), я знаю, що він запитує довжину чогось. Це говорить мені про дві речі: результат є цілим числом, а аргумент є певним контейнером. Навпаки, коли я читаю x.len(), я вже маю знати, що x — це якийсь контейнер, який реалізує інтерфейс або успадковує від класу, який має стандартний len(). Подивіться, яка плутанина у нас іноді виникає, коли клас, який не реалізує відображення, має метод get() або keys(), або щось, що не є файлом, має метод write().

> —https://mail.python.org/pipermail/python-3000/2006-November/004643.html

## Чому метод join() є методом рядків, а не методом списку чи кортежу?

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

    ", ".join(['1', '2', '4', '8', '16'])

що дає результат:

    "1, 2, 4, 8, 16"

Існує два загальні аргументи проти такого використання.

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

Друге заперечення, як правило, формулюється так: «Я справді кажу послідовності об’єднати її члени за допомогою рядкової константи». На жаль, ні. З певних причин здається, що з використанням `split()` як рядкового методу набагато менше труднощів, оскільки в цьому випадку легко побачити, що:

    "1, 2, 4, 8, 16".split(", ")

це вказівка для рядкового літералу повертати підрядки, розділені заданим роздільником (або, за замовчуванням, пробілом).

`join()` — це рядковий метод, оскільки, використовуючи його, ви вказуєте рядку-роздільнику перебирати послідовність рядків і вставляти себе між суміжними елементами. Цей метод можна використовувати з будь-яким аргументом, який підкоряється правилам для об’єктів послідовності, включаючи будь-які нові класи, які ви можете визначити самостійно. Подібні методи існують для об’єктів bytes і bytearray.

## Як швидко працюють винятки?

A try/except block is extremely efficient if no exceptions are raised. Actually catching an exception is expensive. In versions of Python prior to 2.0 it was common to use this idiom:

    try:
        value = mydict[key]
    except KeyError:
        mydict[key] = getvalue(key)
        value = mydict[key]

Це мало сенс лише тоді, коли ви очікували, що dict матиме ключ майже весь час. Якщо це не так, ви закодували це так:

    if key in mydict:
        value = mydict[key]
    else:
        value = mydict[key] = getvalue(key)

Для цього конкретного випадку ви також можете використати `value = dict.setdefault(key, getvalue(key))`, але лише якщо виклик `getvalue()` досить дешевий, оскільки він оцінюється в усіх випадках.

## Чому в Python немає оператора switch або case?

You can do this easily enough with a sequence of `if... elif... elif... else`. For literal values, or constants within a namespace, you can also use a `match ... case` statement.

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

    functions = {'a': function_1,
                 'b': function_2,
                 'c': self.method_1}

    func = functions[value]
    func()

Для виклику методів об’єктів ви можете ще більше спростити, використовуючи вбудований `getattr()` для отримання методів із певним іменем:

    class MyVisitor:
        def visit_a(self):
            ...

        def dispatch(self, value):
            method_name = 'visit_' + str(value)
            method = getattr(self, method_name)
            method()

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

## Чи не можна емулювати потоки в інтерпретаторі замість того, щоб покладатися на реалізацію потоку, специфічного для ОС?

Відповідь 1: На жаль, інтерпретатор надсилає принаймні один кадр стека C для кожного кадру стека Python. Крім того, розширення можуть повертатися до Python у майже випадкові моменти. Таким чином, повна реалізація потоків вимагає підтримки потоків для C.

Відповідь 2: На щастя, є Stackless Python, який має повністю перероблений цикл інтерпретатора, який уникає стека C.

## Чому лямбда-вирази не можуть містити оператори?

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

Functions are already first class objects in Python, and can be declared in a local scope. Therefore the only advantage of using a lambda instead of a locally defined function is that you don’t need to invent a name for the function – but that’s just a local variable to which the function object (which is exactly the same type of object that a lambda expression yields) is assigned!

## Чи можна Python скомпілювати до машинного коду, мови C чи іншої?

Cython compiles a modified version of Python with optional annotations into C extensions. Nuitka is an up-and-coming compiler of Python into C++ code, aiming to support the full Python language.

## Як Python керує пам’яттю?

Деталі керування пам’яттю Python залежать від реалізації. Стандартна реалізація Python, CPython, використовує підрахунок посилань для виявлення недоступних об’єктів та інший механізм для збору посилальних циклів, періодично виконуючи алгоритм виявлення циклів, який шукає недоступні цикли та видаляє залучені об’єкти. Модуль `gc` надає функції для збирання сміття, отримання статистики налагодження та налаштування параметрів збирача.

Other implementations (such as Jython or PyPy), however, can rely on a different mechanism such as a full-blown garbage collector. This difference can cause some subtle porting problems if your Python code depends on the behavior of the reference counting implementation.

У деяких реалізаціях Python наступний код (який добре процює у CPython), ймовірно, не матиме файлових дескрипторів:

    for file in very_long_list_of_files:
        f = open(file)
        c = f.read(1)

Indeed, using CPython’s reference counting and destructor scheme, each new assignment to f closes the previous file. With a traditional GC, however, those file objects will only get collected (and closed) at varying and possibly long intervals.

Якщо ви хочете написати код, який працюватиме з будь-якою реалізацією Python, вам слід явно закрити файл або використати оператор `with`; це працюватиме незалежно від схеми керування пам’яттю:

    for file in very_long_list_of_files:
        with open(file) as f:
            c = f.read(1)

## Чому CPython не використовує більш традиційну схему збирання сміття?

По-перше, це не є стандартною функцією C, а отже, вона не переносна. (Так, ми знаємо про бібліотеку Boehm GC. Вона містить фрагменти коду асемблера для найбільш поширених платформ, не для всіх, і хоча вона здебільшого прозора, але всеж таки не зовсім прозора; для Python потрібні патчи щоб працювати з нею.)

Traditional GC also becomes a problem when Python is embedded into other applications. While in a standalone Python it’s fine to replace the standard malloc() and free() with versions provided by the GC library, an application embedding Python may want to have its own substitute for malloc() and free(), and may not want Python’s. Right now, CPython works with anything that implements malloc() and free() properly.

## Чому не вся пам’ять звільняється, коли CPython завершує роботу?

Об’єкти, на які посилаються глобальні простори імен модулів Python, не завжди звільняються, коли Python завершує роботу. Це може статися, якщо є циклічні посилання. Є також певні частини пам’яті, виділені бібліотекою C, які неможливо звільнити (наприклад, такий інструмент, як Purify, буде скаржитися на це). Однак Python агресивно очищає пам’ять під час виходу та намагається знищити кожен окремий об’єкт.

Якщо ви хочете змусити Python видалити певні речі під час звільнення, скористайтеся модулем `atexit`, щоб запустити функцію, яка примусово прискорить ці видалення.

## Чому існують окремі типи даних кортежу та списку?

Lists and tuples, while similar in many respects, are generally used in fundamentally different ways. Tuples can be thought of as being similar to Pascal records or C structs; they’re small collections of related data which may be of different types which are operated on as a group. For example, a Cartesian coordinate is appropriately represented as a tuple of two or three numbers.

Lists, on the other hand, are more like arrays in other languages. They tend to hold a varying number of objects all of which have the same type and which are operated on one-by-one. For example, `os.listdir('.')` returns a list of strings representing the files in the current directory. Functions which operate on this output would generally not break if you added another file or two to the directory.

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

## Як списки реалізовані в CPython?

Списки CPython насправді є масивами змінної довжини, а не пов’язаними списками як у стилі Lisp. Реалізація використовує безперервний масив посилань на інші об’єкти та зберігає вказівник на цей масив і довжину масиву в структурі заголовка списку.

Це робить індексацію списку `a[i]` операцією, вартість якої не залежить від розміру списку або значення індексу.

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

## Як реалізовані словники в CPython?

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

Dictionaries work by computing a hash code for each key stored in the dictionary using the `hash()` built-in function. The hash code varies widely depending on the key and a per-process seed; for example, «Python» could hash to -539294296 while «python», a string that differs by a single bit, could hash to 1142331976. The hash code is then used to calculate a location in an internal array where the value will be stored. Assuming that you’re storing keys that all have different hash values, this means that dictionaries take constant time – O(1), in Big-O notation – to retrieve a key.

## Чому ключі словника повинні бути незмінними?

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

Якщо ви хочете, щоб словник був індексований списком, просто спочатку перетворіть список на кортеж; функція `tuple(L)` створює кортеж з тими самими записами, що і список `L`. Кортежі є незмінними, тому їх можна використовувати як ключі словника.

Деякі неприйнятні рішення, які були запропоновані:

- Хешувати списки за їхньою адресою (ID об’єкта). Це не працює, тому що якщо ви створите новий список із тим самим значенням, його не буде знайдено; наприклад:

      mydict = {[1, 2]: '12'}
      print(mydict[[1, 2]])

  викличе виняток `KeyError`, оскільки ідентифікатор `[1, 2]`, який використовується у другому рядку, відрізняється від ідентифікатора в першому рядку. Іншими словами, ключі словника слід порівнювати за допомогою `==`, а не за допомогою `is`.

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

- Дозволити списки як ключі, але сказати користувачеві не змінювати їх. Це дозволило б створити клас помилок, які важко відстежити в програмах, коли ви випадково забули або змінили список. Це також робить недійсним важливий інваріант словників: кожне значення в `d.keys()` можна використовувати як ключ словника.

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

There is a trick to get around this if you need to, but use it at your own risk: You can wrap a mutable structure inside a class instance which has both a `__eq__()` and a `__hash__()` method. You must then make sure that the hash value for all such wrapper objects that reside in a dictionary (or other hash based structure), remain fixed while the object is in the dictionary (or other structure).

    class ListWrapper:
        def __init__(self, the_list):
            self.the_list = the_list

        def __eq__(self, other):
            return self.the_list == other.the_list

        def __hash__(self):
            l = self.the_list
            result = 98767 - len(l)*555
            for i, el in enumerate(l):
                try:
                    result = result + (hash(el) % 9999999) * 1001 + i
                except Exception:
                    result = (result % 7777777) + i * 333
            return result

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

Крім того, завжди має бути так, що якщо `o1 == o2` (тобто `o1.__eq__(o2) має значення True`), тоді `hash(o1) == hash(o2)` (тобто, `o1.__hash__() == o2.__hash__()`), незалежно від того, чи є об’єкт у словнику чи ні. Якщо ви не впораєтеся з цими обмеженнями, словники та інші хеш-структури працюватимуть неправильно.

In the case of ListWrapper, whenever the wrapper object is in a dictionary the wrapped list must not change to avoid anomalies. Don’t do this unless you are prepared to think hard about the requirements and the consequences of not meeting them correctly. Consider yourself warned.

## Чому list.sort() не повертає відсортований список?

У ситуаціях, коли продуктивність має значення, створення копії списку лише для його сортування було б марним. Тому `list.sort()` сортує список за місцем. Щоб нагадати вам про цей факт, він не повертає відсортований список. Таким чином, вас не введуть в оману випадковим перезаписом списку, коли вам потрібна відсортована копія, але також потрібно зберегти невідсортовану версію.

Якщо ви хочете повернути новий список, скористайтеся вбудованою функцією `sorted()`. Ця функція створює новий список із наданого ітератора, сортує його та повертає. Наприклад, ось як виконати ітерацію по ключах словника у відсортованому порядку:

    for key in sorted(mydict):
        ...  # do whatever with mydict[key]...

## Як визначити та застосувати специфікацію інтерфейсу в Python?

Специфікація інтерфейсу для модуля, яка надається такими мовами, як C++ і Java, описує прототипи методів і функцій модуля. Багато хто вважає, що примусове виконання специфікацій інтерфейсу під час компіляції допомагає створювати великі програми.

Python 2.6 додає модуль `abc`, який дозволяє визначати абстрактні базові класи (ABC - Abstract Base Classes ). Потім ви можете використовувати `isinstance()` і `issubclass()`, щоб перевірити, чи екземпляр або клас певного ABC їх реалізує. Модуль `collections.abc` визначає набір корисних ABC, таких як `Iterable`, `Container` і `MutableMapping`.

Для Python багато переваг специфікацій інтерфейсу можна отримати за допомогою відповідного порядку тестування компонентів.

Хороший набір тестів для модуля може як забезпечити регресійний тест, так і служити специфікацією інтерфейсу модуля та набором прикладів. Багато модулів Python можна запускати як сценарій для забезпечення простого «самотестування». Навіть модулі, які використовують складні зовнішні інтерфейси, часто можуть бути протестовані ізольовано за допомогою тривіальних «заглушок» емуляції зовнішнього інтерфейсу. Модулі `doctest` і `unittest` або сторонні фреймворки тестування можна використовувати для створення вичерпних наборів тестів, які перевіряють кожен рядок коду в модулі.

An appropriate testing discipline can help build large complex applications in Python as well as having interface specifications would. In fact, it can be better because an interface specification cannot test certain properties of a program. For example, the `append()` method is expected to add new elements to the end of some internal list; an interface specification cannot test that your `append()` implementation will actually do this correctly, but it’s trivial to check this property in a test suite.

Написання наборів тестів дуже корисно, і ви можете розробити свій код, щоб його було легко перевірити. Одна з технік, що стає все більш популярною, — розробка на основі тестування (TDD - test-driven development)— вимагає спочатку написати частини набору тестів, перш ніж писати будь-який фактичний код. Звичайно, Python дозволяє вам бути неохайними і взагалі не писати тести.

## Чому немає goto?

In the 1970s people realized that unrestricted goto could lead to messy «spaghetti» code that was hard to understand and revise. In a high-level language, it is also unneeded as long as there are ways to branch (in Python, with `if` statements and `or`, `and`, and `if-else` expressions) and loop (with `while` and `for` statements, possibly containing `continue` and `break`).

One can also use exceptions to provide a «structured goto» that works even across function calls. Many feel that exceptions can conveniently emulate all reasonable uses of the «go» or «goto» constructs of C, Fortran, and other languages. For example:

    class label(Exception): pass  # declare a label

    try:
        ...
        if condition: raise label()  # goto label
        ...
    except label:  # where to goto
        pass
    ...

This doesn’t allow you to jump into the middle of a loop, but that’s usually considered an abuse of goto anyway. Use sparingly.

## Чому необроблені рядки (r-рядки) не можуть закінчуватися зворотною косою рискою?

Точніше, вони не можуть закінчуватися непарною кількістю зворотних скісних рисок: непарна зворотна коса риска в кінці виходить із символу закриваючої лапки, залишаючи незакінчений рядок.

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

Якщо ви намагаєтеся створити імена шляхів Windows, зверніть увагу, що всі системні виклики Windows також приймають косу риску:

    f = open("/mydir/file.txt")  # works fine!

Якщо ви намагаєтеся створити шлях для команди DOS, спробуйте, наприклад. один з

    dir = r"\this\is\my\dos\dir" "\\"
    dir = r"\this\is\my\dos\dir\ "[:-1]
    dir = "\\this\\is\\my\\dos\\dir\\"

## Чому в Python немає оператора «with» для призначення атрибутів?

Python has a „with“ statement that wraps the execution of a block, calling code on the entrance and exit from the block. Some languages have a construct that looks like this:

    with obj:
        a = 1               # equivalent to obj.a = 1
        total = total + 1   # obj.total = obj.total + 1

У Python така конструкція була б неоднозначною.

Інші мови, такі як Object Pascal, Delphi та C++, використовують статичні типи, тому можна однозначно знати, якому члену призначено. Це головний момент статичної типізації – компілятор завжди знає область кожної змінної під час компіляції.

Python використовує динамічні типи. Неможливо знати заздалегідь, на який атрибут буде посилатися під час виконання. Атрибути учасників можна додавати або видаляти з об’єктів на льоту. Це робить неможливим з простого читання дізнатися, на який атрибут посилається: локальний, глобальний чи атрибут-член?

Наприклад, візьмемо наступний неповний фрагмент:

    def foo(a):
        with a:
            print(x)

The snippet assumes that «a» must have a member attribute called «x». However, there is nothing in Python that tells the interpreter this. What should happen if «a» is, let us say, an integer? If there is a global variable named «x», will it be used inside the with block? As you see, the dynamic nature of Python makes such choices much harder.

The primary benefit of «with» and similar language features (reduction of code volume) can, however, easily be achieved in Python by assignment. Instead of:

    function(args).mydict[index][index].a = 21
    function(args).mydict[index][index].b = 42
    function(args).mydict[index][index].c = 63

напиши це:

    ref = function(args).mydict[index][index]
    ref.a = 21
    ref.b = 42
    ref.c = 63

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

## Чому генератори не підтримують оператор with?

For technical reasons, a generator used directly as a context manager would not work correctly. When, as is most common, a generator is used as an iterator run to completion, no closing is needed. When it is, wrap it as «contextlib.closing(generator)» in the „with“ statement.

## Чому в операторах if/while/def/class потрібні двокрапки?

Двокрапка потрібна насамперед для покращення читабельності (один із результатів експериментальної мови ABC). Розглянемо це:

    if a == b
        print(a)

проти

    if a == b:
        print(a)

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

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

## Чому Python допускає коми в кінці списків і кортежів?

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

    [1, 2, 3,]
    ('a', 'b', 'c',)
    d = {
        "A": [1, 5],
        "B": [6, 7],  # last trailing comma is optional but good style
    }

Є кілька причин дозволити це.

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

Випадковий пропуск коми може призвести до помилок, які важко діагностувати. Наприклад:

    x = [
      "fee",
      "fie"
      "foo",
      "fum"
    ]

Цей список виглядає так, ніби він складається з чотирьох елементів, але насправді він містить три: «fee», «fiefoo» і «fum». Постійне додавання коми дозволяє уникнути цього джерела помилки.

Дозвіл кінцевої коми також може полегшити генерацію програмного коду.