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:  # To catch both exceptions, right?
    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
2
Задача
Модуль 1: Python Core, 11 уровень, 6 лекция
Недоступна
Параметры по умолчанию.
Параметры по умолчанию.
2
Задача
Модуль 1: Python Core, 11 уровень, 6 лекция
Недоступна
Неправильное указание параметров
Неправильное указание параметров
Комментарии (13)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Ivan Уровень 40
13 мая 2025
Как же бесят на сайте горячие клавиши Ctrl→ и Ctrl← :( Толку от них ноль, а случайное нажатие во время набора комментария переключает на другую лекцию, а весь набранный комментарий пропадает. Накатал целую страницу с разбором первого примера про функцию с параметром по умолчанию и потерял таким идиотским способом :( Второй раз такое писать не хочу.
SWK Уровень 25
11 апреля 2025
А ко второй задаче слабо было написать пару предложений, объясняющих, почему приведённый в ней код перехватывает неправильно?
XoxoTyH Уровень 24
30 апреля 2025
А он там как раз правильный. Видимо, забыли написать неправильный перехват без кортежа
SWK Уровень 25
5 мая 2025
Видимо, правильность преподаваемого материала авторов не беспокоит.
SWK Уровень 25
11 апреля 2025
По поводу переменных класса было бы уместно объяснить, почему В.х не изменилась, когда изменялась А.х, в В же точно так же нет х, как и в С. И куда тогда двойка записалась, раз в классе В нет переменной х? И чего она, когда для С искалась, в В не нашлась? и т.д. и т.п. Голый пример без объяснений исключительно вредит.
Дмитрий Уровень 27
7 мая 2025
Когда мы принудительно присваиваем аттрибуту x класса B значение 2, мы создаём в классе B аттрибут x. Поэтому B.x - это аттрибут класса B, а C.x - фактически аттрибут класса A, поскольку в C нет аттрибута x. Когда мы обращаемся к аттрибуту класса, пайтон сперва ищет его в указанном классе, а потом - в родительских классах.
Japan_Dragon Уровень 32
17 февраля 2025
первый пример хоть и имеет место быть, но Pycharm. уже давно подсвечивает такие штуки и указывает на возможную ошибку
Максим Уровень 15
2 февраля 2025
В чём прикол второй задачи, так и не понял. Можно же ничего не менять))
Mr.Robot Уровень 21 Expert
20 марта 2025
Не проходит валидацию, если ничего не менять. Я разложил на 2 except - только тогда прошла.
SWK Уровень 25
11 апреля 2025
А ошибка-то где в том коде?
SWK Уровень 25
11 апреля 2025
Гы-гы. А я исключения местами поменял и валидатор принял. Начинаю подозревать, что мы подопытные у маньяков.
Максим Уровень 15
2 февраля 2025
Если скопировать блок с вызовом foo_correct для foo и не менять закомментированные пояснения, то задачу проверка не проходит
Екатерина Уровень 69
9 сентября 2024
Первая задача с foo и foo_correct. Не забудьте прописать запуск неисправленной foo()