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  # Це буде оцінено лише тоді, коли g() буде викликано
print(a.f())

Тепер, коли ми його імпортуємо, все добре:


import b
b.g()
# 1   Виведено вперше, оскільки модуль 'a' викликає 'print(f())' в кінці
# 1   Виведено вдруге, це наш виклик до '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, але, думаю, ідею ви зрозуміли.

1
Опитування
Ітератори, рівень 11, лекція 8
Недоступний
Ітератори
Ітератори
Коментарі (10)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Денис Рівень 25
4 січня 2026
Друге завдання...

def bar(i):
    if i == 1:
        raise KeyError(1)
    if i == 2:
        raise ValueError(2)

def bad():
    e = None
    try:
        bar(1)
    except KeyError as err:
        print('key error')
        e = err
    except ValueError as err:
        print('value error')
        e = err
    print(e)

bad()
Alex Stasyuk Рівень 25
23 грудня 2025
правильне рішення task18 не проходить перевірку 😁😁😁
Alex Stasyuk Рівень 25
23 грудня 2025
e = exception для того щоб print(e) - інакше не проходить тестування. І це приклад як треба програмувати?????
Дмитро Рівень 30
17 листопада 2025
import importlib.util import os import math module_name = "math" file_path = os.path.join(os.path.dirname(__file__), "math.py") print(file_path) spec = importlib.util.spec_from_file_location(module_name, file_path) my_path = importlib.util.module_from_spec(spec) spec.loader.exec_module(my_path) print(my_path.sqrt(2)) print(math.sqrt(2))
Олена Рівень 22
14 квітня 2025
"Правильне рішення" не працює
vova_yu Рівень 64
10 травня 2025
вирішити задачу - це не тільки скопіювати з розв'язку в основну частину. Інколи її треба трішечки підправити під вашу локальну машину. Особливо допомагає подивитись логічний шлях до каталогу з модулем.
Дмитро Рівень 21
11 квітня 2025
task17 написана в стилі 'it worked on my machine' так от - на моїй машині воно не працює
Василь Рівень 56
15 квітня 2025
З допомогою чата GPT пройшов дане завдання.
Nataliia Fedyshyn Рівень 1
27 січня 2025
Супер, зробили один абзац абсолютно без пояснення що з цим робити і вліпили його в задачу. Ні про importlib не сказано нічого, ні про те, як з цим справлятись коли хочемо залишити назву модуля такою, яка перетинається. І так, гугл не допомагає, бо взагалі незрозуміло що має ця задача робити і дати, крім страждань і головного болю :) Останні три лекції просто навалена інфа з використанням незрозумілих прикладів і нерозкритими темами. Попередні теми хоч були більш структуровані і можна було нагуглити додаткові матеріали і пояснення, щоб зрозуміти, а тут взагалі не ясно в яку сторону копати. Не те щоб це дуже допомагало тримати себе в строю під час і так не дуже легкого навчання..
negoda Рівень 31
20 серпня 2025
+, чем дальше тем больше лекции похожи на бред сумашедшего