Метод класса обращается к имени, объявленному в теле того же класса: в какой области видимости Python его ищет?
Метод не ищет свободное имя в пространстве имён класса. Для доступа к атрибуту класса нужно использовать квалифицированное обращение, например через self или имя самого класса; иначе Python проверит локальную область метода, внешние области объемлющих функций, глобальную область модуля и встроенные имена.
Тело класса выполняется как отдельный блок кода, формирующий пространство имён будущего класса. Это позволяет вычислять атрибуты, создавать методы и передавать собранное пространство имён механизму построения класса.
При этом класс не является обычной объемлющей функцией областью видимости. Методы становятся функциями, определёнными в теле класса, но их лексическое разрешение имён не включает пространство имён класса.
Если разработчик обращается к атрибуту класса как к обычной переменной, код может завершиться NameError, хотя имя визуально находится несколькими строками выше. Даже если в модуле случайно существует глобальное имя с таким же названием, метод может прочитать уже его, что создаёт трудно обнаружимую ошибку.
Неправильный выбор формы доступа также влияет на наследование и возможность переопределять настройки экземпляра. Поэтому важно отличать имя переменной от атрибута объекта.
Без квалификатора Python разрешает имена внутри функции по цепочке Local → Enclosing → Global → Builtins. Пространство имён класса в эту цепочку не входит, поэтому имя, записанное в теле класса, не захватывается методом как обычная свободная переменная.
Для доступа через экземпляр используют self.name. Такой поиск учитывает атрибуты экземпляра, атрибуты класса и классов-предков, а также правила дескрипторов. Для обращения именно к классу можно использовать ClassName.name; это делает зависимость от конкретного класса явной.
broken не находит timeout в пространстве имён класса и получает NameError, если глобального имени timeout нет. correct выполняет атрибутивный доступ и получает значение Settings.timeout.
self.timeout допускает переопределение атрибута в конкретном экземпляре. Если это нежелательно, можно использовать type(self).timeout, сохранив поддержку переопределения в подклассе, либо Settings.timeout, жёстко привязав обращение к базовому классу.
Особое исключение связано с __class__: компилятор может создать специальную ячейку замыкания для конструкций вроде super(). Это не означает, что все имена из пространства имён класса становятся доступными внутри метода.
В классе клиента хранится значение тайм-аута, которое по умолчанию одинаково для всех экземпляров, но должно изменяться для отдельных клиентов. Вариант с голым именем timeout неверен: метод не видит атрибут класса. Глобальная переменная формально решает проблему, но создаёт скрытую зависимость, мешает тестированию и смешивает настройки разных объектов.
Обращение через Settings.timeout надёжно получает значение, но не учитывает специализированные подклассы. Обращение через self.timeout позволяет задать индивидуальный тайм-аут экземпляру и автоматически учитывать значение, переопределённое в подклассе.
Поэтому выбран self.timeout: он соответствует модели объекта и поддерживает полиморфизм. Если настройка должна быть неизменяемой общей константой без возможности переопределения экземпляром, лучше явно использовать обращение к классу или отдельную константу модуля.
Вопрос: Может ли метод получить атрибут класса без self или имени класса?
Ответ: Да, но не как обычное имя из пространства имён класса. Возможны другие механизмы: обращение через globals() к глобальному объекту, явное получение класса через type(self) или использование специального __class__. Однако это уже квалифицированный доступ либо специальное правило, а не включение класса в стандартную цепочку лексического разрешения имён.
Вопрос: Почему обращение к __class__ внутри метода работает, если пространство имён класса не является замыканием?
Ответ: Для методов, которым нужен __class__, компилятор создаёт специальную ячейку __class__. Она используется, в частности, механизмом нулевого аргумента super(). Это специально поддержанный компилятором канал доступа к текущему классу, а не общее правило видимости всех атрибутов класса.
Вопрос: Почему имя атрибута класса может быть недоступно внутри включённого в тело класса выражения-генератора?
Ответ: Генераторные выражения и comprehensions создают собственную область видимости. Пространство имён класса не становится для неё обычной объемлющей областью, поэтому ранее присвоенный атрибут класса не обязан находиться по голому имени внутри такого выражения. Надёжный способ — явно обратиться к уже доступному объекту класса после его создания или передать нужное значение в выражение явно; при проектировании такого кода нужно учитывать момент создания класса.