Область имен, определенных в блоке класса, не распространяется на блоки методов. Это почему?

Читая документацию, я наткнулся на следующий абзац:

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

Я решил попробовать получить доступ к переменной класса из метода сам:

>>> class A():
    i = 1
    def f(self):
        print(i)            

>>> a = A()

>>> a.i
1

>>> a.f()
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    a.f()
  File "<pyshell#4>", line 4, in f
    print(i)
NameError: global name 'i' is not defined

Я знаю что переменная i можно получить доступ, явно указав на имя класса A.i:

>>> a = A()
>>> class A():
    i = 1
    def f(self):
        print(A.i)          
>>> a = A()
>>> a.f()
1

Вопрос в том, почему разработчики языка сделали переменные класса невидимыми из методов? Что за этим стоит?

2 ответа

Решение

Похоже, это связано с использованием явного self параметр и требование, чтобы все вызовы метода и доступ к атрибуту экземпляра явно использовали self, Было бы по меньшей мере странно, если бы необычный случай доступа к функции области класса как к обычной функции был бы намного проще, чем обычный случай доступа к ней как к методу через self, К переменным класса обычно также обращаются через экземпляр в Python.

В C++, напротив, область видимости видна во всех методах, но вызов метода неявно проходит this, Это, кажется, другой разумный выбор.

Блок класса является синтаксическим сахаром для построения словаря, который затем передается метаклассу (обычно type) построить объект класса.

class A:
    i = 1
    def f(self):
        print(i)

Примерно эквивалентно:

def f(self):
    print(i)
attributes = {'f': f, 'i': 1)
A = type('A', (object,) attributes)

С этой точки зрения, нет внешней i имя, чтобы прийти. Однако, очевидно, существует временная область для выполнения операторов в блоке класса. Для этого блока класса было бы возможно desugar к чему-то более похожему:

def attributes():
    i = 1
    def f(self):
        print(i)
    return locals()
A = type('A', (object,), attributes())

В этом случае внешняя ссылка на i должно сработать. Тем не менее, это будет идти "вразрез" философии объектной системы Python.

У Python есть объекты, которые содержат атрибуты. На самом деле в функциях нет понятия "переменные", кроме локальных переменных (которые могут быть вложены для создания цепочки областей видимости). Голое имя рассматривается как локальная переменная, а затем во внешних областях (которые поступают из функций). Атрибуты ищутся с использованием синтаксиса точечного имени в других объектах, и вы всегда указываете, в каком объекте искать.

Существует протокол для разрешения ссылок на атрибуты, который говорит, что когда attribute не найден на obj, obj.attribute можно решить, посмотрев в классе obj (и его базовые классы, используя порядок разрешения метода). Это на самом деле, как методы найдены; когда в вашем примере вы казнили a.f(), a объект не содержит атрибута fтак что класс a (который A), и определение метода найдено.

Было бы странно иметь атрибуты класса, автоматически доступные во внешней области видимости для всех методов, потому что никакой другой атрибут не работает таким образом. Это также будет иметь следующие недостатки:

  1. Функции, определенные вне класса и назначенные ему позже, должны будут использовать другой синтаксис для ссылки на атрибут класса, чем функции, определенные как часть класса.
  2. Поскольку он короче, он будет поощрять ссылку на атрибуты класса, включая staticmethods и classmethods, как голые имена: thing вместо того, чтобы использовать Class.thing или же self.thing, Это делает их похожими на глобальные переменные модуля, когда их нет (определения методов обычно достаточно короткие, чтобы можно было легко увидеть, что они не определены локально).
  3. Обратите внимание, что поиск атрибутов на self позволяет им лучше играть с подклассами, так как позволяет подклассам переопределять атрибут. Это, вероятно, не так уж важно для "констант классов", но это очень важно для статических методов и методов классов.

Это основные причины, которые я вижу, но в конечном итоге это просто выбор, который сделали дизайнеры Python. Вы находите странным, что у вас нет такой неявной возможности ссылаться на переменные класса, но я нахожу странным доступ к переменным класса и экземпляра в таких языках, как C++ и Java. У разных людей разные мнения.

Другие вопросы по тегам