JavaRush /Курсы /Модуль 1: Python Core /Стандартные ошибки, часть 3

Стандартные ошибки, часть 3

Модуль 1: Python Core
11 уровень , 8 лекция
Открыта

9.1 Создание циклических зависимостей модуля

Допустим, у вас есть два файла, a.py и b.py, каждый из которых импортирует другой следующим образом:

В a.py:


import b

def f():
    return b.x

print(f())

В b.py:


import a

x = 1

def g():
    print(a.f())

Сначала попробуем импортировать a.py:


import a
# 1

Сработало просто отлично. Возможно, это вас удивляет. В конце концов, модули циклически импортируют друг друга, и это, вероятно, должно быть проблемой, не так ли?

Ответ заключается в том, что простое наличие циклического импорта модулей само по себе не является проблемой в Python. Если модуль уже был импортирован, Python достаточно умен, чтобы не пытаться повторно импортировать его. Однако, в зависимости от того, в какой момент каждый модуль пытается получить доступ к функциям или переменным, определенным в другом, вы действительно можете столкнуться с проблемами.

Итак, возвращаясь к нашему примеру, когда мы импортировали a.py, у него не было проблем с импортом b.py, поскольку b.py не требует, чтобы что-либо из a.py было определено во время его импорта. Единственная ссылка в b.py на a — это вызов a.f(). Но этот вызов в g(), и ничего в a.py или b.py не вызывает g(). Так что все работает прекрасно.

Но что произойдет, если мы попытаемся импортировать b.py (без предварительного импорта a.py, то есть):


import b
Traceback (most recent call last):
    File "<stdin>", line 1, in 
      
  
      
  
      
  
    File "b.py", line 1, in 
   
     import a File "a.py", line 6, in 
    
      print(f()) File "a.py", line 4, in f return b.x AttributeError: 'module' object has no attribute 'x' 
     
    
  

Проблема здесь в том, что в процессе импорта b.py он пытается импортировать a.py, который, в свою очередь, вызывает f(), который пытается получить доступ к b.x. Но b.x еще не было определено. Отсюда исключение AttributeError.

По крайней мере, одно из решений этой проблемы довольно тривиально. Просто измените b.py, чтобы импортировать a.py в g():


x = 1

def g():
    import a  # This will be evaluated only when g() is called
    print(a.f())

Теперь, когда мы его импортируем, все нормально:


import b
b.g()
# 1   Printed a first time since module 'a' calls 'print(f())' at the end
# 1   Printed a second time, this one is our call to 'g()'

9.2 Пересечение имен с именами модулей стандартной библиотеки Python

Одна из прелестей Python — это множество модулей, которые поставляются «из коробки». Но в результате, если вы сознательно не будете за этим следить, можно столкнуться с тем, что имя вашего модуля может совпасть с именем модуля из стандартной библиотеки, поставляемой с Python (например, в вашем коде может быть модуль с именем email.py, который будет конфликтовать с модулем стандартной библиотеки с таким же именем).

Это может привести к серьезным проблемам. Например, если какой-нибудь из модулей будет пытаться импортировать версию модуля из стандартной библиотеки Python, а у вас в проекте будет модуль с таким же именем, он по ошибке импортирует ваш модуль вместо модуля из стандартной библиотеки.

Поэтому следует проявлять осторожность, чтобы не использовать те же имена, что и в модулях стандартной библиотеки Python. Гораздо проще изменить название модуля в своем проекте, нежели подавать запрос на изменение имени модуля в стандартной библиотеке и ждать его одобрения.

9.3 Видимость исключений

Рассмотрим следующий файл main.py:


import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)
        
def bad():
    e = None
    try:
        bar(int("1"))
    except KeyError as e:
        print('key error')
    except ValueError as e:
        print('value error')
    print(e)

bad()

Вроде все верно, код должен работать, давайте посмотрим, что он нам выведет:


$ python main.py 1
Traceback (most recent call last):
    File "C:\Projects\Python\TinderBolt\main.py", line 19, in <module>
        bad()
    File "C:\Projects\Python\TinderBolt\main.py", line 17, in bad
        print(e)
          ^
UnboundLocalError: cannot access local variable 'e' where it is not associated with a value

Что здесь только что произошло? Проблема в том, что в Python объект в блоке исключения недоступен за его пределами (причина этого заключается в том, что в противном случае объекты в этом блоке сохранялись бы в памяти до тех пор, пока сборщик мусора не запустится и не удалит ссылки на них).

Один из способов избежать этой проблемы — сохранить ссылку на объект блока исключения за пределами этого блока, чтобы он оставался доступным. Вот версия предыдущего примера, которая использует эту технику, тем самым делая код рабочим:


import sys

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)
            
def good():
    exception = None
    try:
        bar(int("1"))    
    except KeyError as e:
        exception = e
        print('key error')
    except ValueError as e:
        exception = e
        print('value error')
    print(exception)
            
good()

9.4 Неправильное использование метода __del__

Когда интерпретатор удаляет объект, он проверяет — есть ли у этого объекта функция __del__, и если есть, то вызывает ее перед удалением объекта. Это очень удобно, когда вы хотите, чтобы ваш объект подчистил за собой какие-нибудь внешние ресурсы или кэш.

Допустим, у вас есть вот такой файл mod.py:


import foo

class Bar(object):
        ...

def __del__(self):
    foo.cleanup(self.myhandle)

И вы пытаетесь сделать вот такое из другого файла another_mod.py:


import mod
mybar = mod.Bar()

И получаете ужасный AttributeError.

Почему? Потому что, как сообщается здесь, когда интерпретатор завершает работу, все глобальные переменные модуля имеют значение None. В результате в приведенном выше примере, в момент вызова __del__, имя foo уже было установлено в None.

Решением этой «задачи со звездочкой» будет использование специального метода atexit.register(). Таким образом, когда ваша программа завершает выполнение (то есть при нормальном выходе из нее), ваши handle'ы удаляются до того, как интерпретатор завершит работу.

С учетом этого исправление для приведенного выше кода mod.py может выглядеть примерно так:


import foo
import atexit
        
def cleanup(handle):
    foo.cleanup(handle)
        
class Bar(object):
    def __init__(self):
      ...

atexit.register(cleanup, self.myhandle)

Подобная реализация обеспечивает простой и надежный способ вызова любой необходимой очистки после обычного завершения программы. Очевидно, что решение о том, как поступить с объектом, который связан с именем self.myhandle, остается за foo.cleanup, но, думаю, идею вы поняли.

2
Задача
Модуль 1: Python Core, 11 уровень, 8 лекция
Недоступна
Пересечение имен.
Пересечение имен.
2
Задача
Модуль 1: Python Core, 11 уровень, 8 лекция
Недоступна
Зона видимости переменной.
Зона видимости переменной.
1
Опрос
Итераторы, 11 уровень, 8 лекция
Недоступен
Итераторы
Итераторы
Комментарии (11)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Дима Белый Уровень 22
11 ноября 2025
Ну это верх работы некорректного валидатора)) браво, это прекрасно)) *сильно сарказм*
Nadia Уровень 77
6 мая 2025
Поправьте: 1. должен быть отступ, когда мы пишем метод __del__() в классе Bar. import foo class Bar(object): def __del__(self): foo.cleanup(self.myhandle) 2. Переместите строку atexit.register(...) внутрь __init__(), чтобы self.myhandle был доступен: import foo import atexit def cleanup(handle): foo.cleanup(handle) class Bar(object): def __init__(self): self.myhandle = "some-resource" atexit.register(cleanup, self.myhandle)
SWK Уровень 26
14 апреля 2025
Давайте дружно намекнём авторам, что пример про atexit.register() никто не понял!
Дмитрий Уровень 27
7 мая 2025
Ну, в issue5099, на которую ссылаются авторы JavaRush, вообще сказано: "the real fix would be to avoid defining __del__ at all". Но в реальной жизни наверняка будут тимлиды, которые будут буквально настаивать на ручной зачистке с использованием __del__. Поэтому, ну вот подробное описание At Exit: https://docs.python.org/3/library/atexit.html
Елена Шорохова Уровень 64
25 мая 2025
Нет, не будут.
Дмитрий/MrJonson Уровень 65
13 марта 2025
# Пересечение имен. # Вызовите функцию sqrt вашего модуля math. # Вызовите функцию sqrt встроенного модуля math. # Напишите тут ваш код import importlib.util import sys from math import sqrt print(sqrt(10)) path = 'C:\\Users\\flash\\javarush\\3543133\\javarush-project\\src\\ru\\javarush\\python\\core\\level11\\task17\\math.py' spec = importlib.util.spec_from_file_location('math', path) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) from math import sqrt sqrt(5) написал запускаю все работает. запускаю проверку в IDE пишет не вызвана функция из стандартного модуля Ничего ни делая запускаю еще раз все проходит, вот только попытка № 2
Mr.Robot Уровень 21 Expert
20 марта 2025
11 попыток с этим же кодом ))) путь, естественно, свой подставлял добился положительного результата проверки просто переставляя места вызова sqrt
Slevin Уровень 64
7 июля 2025
Валидатор просто поломан. Принял решение которое не работает, но не принимал решение которое работает.
Олексій Єрмак Уровень 25
2 марта 2025
В опросе по итераторам ошибка в 3 вопросе, там написано: "Какой исключение генерирует метод", а должно быть: "Какое исключение генерирует метод" Исправьте, пожалуйста.
29 января 2025
3 вопрос: Какой метод должен реализовать объект, чтобы быть итератором? Думал , что нужно __Iter__() и __next__() реализовать - или я что-то не так понимаю ? "Итерируемый объект (Iterable) Чтобы по объекту можно было пройтись с помощью цикла for, он должен быть итерируемым – Iterable. Это значит, что наш объект должен реализовать метод __iter__(), который возвращает объект-итератор. Объект-итератор (Iterator) Это специальный объект, который имеет функцию __next__() для отдачи следующего элемента последовательности. Когда элементы заканчиваются, метод __next__() вызывает исключение StopIteration как знак прекращения итерации. Итератор также должен реализовать метод __iter__(), который возвращает сам итератор."
SWK Уровень 26
14 апреля 2025
Нужно оба. По определению. Но, авторы вопросов об этом не знают.