JavaRush /Курси /Модуль 1: Python Core /Стандартні помилки

Стандартні помилки

Модуль 1: Python Core
Рівень 11 , Лекція 6
Відкрита

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

Ви вже багато чому навчилися, давайте розглянемо найпоширеніші помилки новачків – краще вчитися на чужих помилках, чи не так?

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


def foo(bar=[]):  # bar - це необов'язковий аргумент 
# і за замовчуванням дорівнює порожньому списку.
    bar.append("baz")  # цей рядок може стати проблемою...
    return bar

Поширена помилка в даному випадку – це думати, що значення необов'язкового аргументу буде встановлюватися в значення за замовчуванням кожного разу, як функція буде викликатися без значення для цього аргументу.

У наведеному вище коді, наприклад, можна припустити, що повторно викликаючи функцію foo() (тобто без вказання значення для аргументу bar), вона завжди буде повертати ["baz"], оскільки передбачається, що кожного разу, коли викликається foo() (без вказання аргументу bar), bar встановлюється в [] (тобто новий порожній список).

Але давайте подивимось, що ж буде відбуватися насправді:


result = foo()
print(result)  # ["baz"]
            
result = foo()
print(result)  # ["baz", "baz"]
            
result = foo()
print(result)  # ["baz", "baz", "baz"]

Чому ж функція продовжує додавати значення baz до існуючого списку кожного разу, коли викликається foo(), замість того щоб кожного разу створювати новий список?

Відповіддю на це питання буде більш глибоке розуміння того, що діється у Python «під капотом». А саме: значення за замовчуванням для функції ініціалізується тільки один раз, під час визначення функції.

Таким чином, аргумент bar ініціалізується за замовчуванням (тобто порожнім списком) тільки тоді, коли foo() визначений вперше, але подальші виклики foo() (тобто без вказання аргументу bar) продовжать використовувати той самий список, який було створено для аргументу bar в момент першого визначення функції.

Для довідки, поширеним «обхідним шляхом» для цієї помилки є наступне визначення:


def foo(bar=None):             
    if bar is None:
        bar = []                                          
    bar.append("baz")   
    return bar
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]
    
result = foo()
print(result)  # ["baz"]

7.2 Неправильне використання змінних класу

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


class A(object):
    x = 1
       
class B(A):
    pass
       
class C(A):
    pass
       
print(A.x, B.x, C.x) 
# 1 1 1

Нібито все гаразд.

Тепер давайте присвоїмо значення полю x класу B:


B.x = 2
print(A.x, B.x, C.x)
# 1 2 1

Все як і очікувалося.

А тепер змінимо змінну класу A:


A.x = 3
print(A.x, B.x, C.x)
# 3 2 3

Дивно, ми ж тільки змінили A.x. Чому ж C.x теж змінилося?

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

Іншими словами, C не має свого власного властивості x, незалежного від A. Таким чином, посилання на C.x фактично є посиланнями на A.x. Це може спричинити проблеми, якщо не враховувати такі випадки належним чином. При вивченні Python зверніть особливу увагу на атрибути класу та роботу з ними.

7.3 Неправильне зазначення параметрів для блоку винятків

Припустимо, що у вас є наступний шматок коду:


try:
    l = ["a", "b"]
    int(l[2])
except ValueError, IndexError:  # Щоб перехопити обидва винятки, так?
    pass
        
Traceback (most recent call last):
    File "<stdin>", line 3, in <module>
IndexError: list index out of range

Проблема тут полягає в тому, що вираз except не приймає список винятків, зазначених таким чином. У результаті у наведеному вище коді виняток IndexError не перехоплюється виразом except; натомість виняток завершується прив'язкою до параметра з ім'ям IndexError.

Важливо! Такий код ви можете зустріти в прикладах в інтернеті, так як він використовувався в Python 2.x.

Правильний спосіб перехоплення кількох винятків за допомогою виразу except — вказати перший параметр у вигляді кортежу, що містить усі винятки, які потрібно перехопити. Крім того, для кращої читабельності ви можете використовувати ключове слово as, наприклад ось так:


try:
    l = ["a", "b"]
    int(l[2])
except (ValueError, IndexError) as e:  
    pass
Коментарі (4)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Alex Stasyuk Рівень 25
23 грудня 2025
Що не відповідає умові задачі?
Alex Stasyuk Рівень 25
23 грудня 2025
Це просто жесть - у правильному розвязанні - вього навсього змінено порядок - оголошення функції foo - її 3 виклики, потом оголошення foo_correct і її виклики. Це жесть.
Матвій Рівень 28
12 серпня 2025
наприклад, з використанням кортежу воно написало я так зробив воно написало що неправильно треба два різних except
Денис Рівень 25
4 січня 2026
Не обовʼязково там два except:

try:
    number = int(input("Введіть число: "))
    result = 10 / number
except (ValueError, ZeroDivisionError) as e:
    print(f"Сталася помилка: {e}")