Модули и классы в Python являются пространствами имен. И эти пространства имен открыты для изменения. Во время работы программы вы можете добавить в модуль или класс новые атрибуты:
import module
module.new_attribute = 42
module.new_attribute # 42
from classes import C
C.foo = 'bar'
C.foo # 'bar'
Также вы можете хранить в атрибутах изменяемые сущности — например, списки и словари — и время от времени вносить в них изменения.
class D:
L = []
D.L.append(100)
D.L # [100]
import module
module.X # {}
module.X['k'] = 'v'
module.X # {'k': 'v'}
Вы могли слышать, что пользоваться глобальными переменными плохо. Так говорят именно из-за того, что очень сложно следить за изменениями в одном месте кода и предсказывать влияние этих изменений на остальные части программы. Так вот, изменяемые (и добавляемые) атрибуты классов и модулей — это те самые глобальные переменные!
Кому-то эти возможности могут показаться очень удобными, но не нужно заблуждаться: кажущееся удобство практически всегда приводит к тому, что вы тратите много времени на поиск мест, которые внесли подобные изменения, и что-то в итоге сломалось. Ошибиться очень легко, а затронута будет большая часть программы. Ведь изменения, вносимые в класс, влияют на все экземпляры этого класса — не только на будущие, но и на уже созданные!
С модулями все становится еще опаснее: объект модуля всегда один на всю программу, потому что Python запоминает все модули, которые куда-то импортировались хотя бы раз, и при последующих импортах модуля возвращает ссылку на уже загруженный модуль. Соответственно, изменения, вносимые в модуль, затрагивают все модули, которые его импортируют! Представьте, что у вас есть код, который строит логику, исходя из значения константы в атрибуте модуля, и вдруг в совершенно другом месте программы происходит изменение этой константы — такие ситуации очень тяжело отлаживать.
Не используйте изменяемые объекты-одиночки, каковыми являются большинство классов и все модули! Исключения возможны, но они обычно предполагают, что вы знаете, что делаете, и готовы к последствиям.
Одиночка или singleton — название приема, который позволяет во всех местах программы работать с неким объектом, который всегда существует в единственном экземпляре, но при этом объект не передается явно между местами в коде, которые его используют. Пример одиночки: объект, хранящий конфигурацию программы. Она загружается из внешнего источника (например, файла) при первом обращении к объекту, а затем все последующие обращения к конфигурации сразу же получают доступ к уже загруженному объекту.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.