Причина разрешения специальных символов в атрибутах Python

Я случайно обнаружил, что вы можете установить "незаконные" атрибуты для объекта, используя setattr, Под незаконным я подразумеваю атрибуты с именами, которые нельзя получить с помощью __getattr__ интерфейс с традиционным . Операторские ссылки. Их можно получить только через getattr метод.

Это мне кажется довольно удивительным, и мне интересно, есть ли причина для этого, или это просто что-то упущено, и т. Д. Так как существует оператор для получения атрибутов и стандартная реализация setattribute интерфейс, я бы ожидал, что он позволяет только имена атрибутов, которые могут быть получены в обычном режиме. И если бы у вас была какая-то причудливая причина хотеть атрибутов с недопустимыми именами, вам пришлось бы реализовать для них собственный интерфейс.

Я один удивлен таким поведением?

class Foo:
    "stores attrs"

foo = Foo()
setattr(foo, "bar.baz", "this can't be reached")
dir(foo)

Это возвращает нечто странное и немного вводящее в заблуждение:[...'__weakref__', 'bar.baz']

И если я хочу получить доступ к foo.bar.baz "стандартным" способом, я не могу. Невозможность извлечь его имеет смысл, но способность установить его вызывает удивление.

foo.bar.baz
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'

Это просто предполагается, что, если вы должны использовать setattr чтобы установить переменную, вы будете ссылаться на нее через getattr? Потому что во время выполнения это не всегда может быть правдой, особенно с интерактивным интерпретатором Python, отражением и т. Д. По-прежнему кажется очень странным, что это будет разрешено по умолчанию.

РЕДАКТИРОВАТЬ: (очень грубый) пример того, что я ожидаю увидеть в качестве реализации по умолчанию setattr:

import re

class Safe:
    "stores attrs"

    def __setattr__(self, attr, value):
        if not re.match(r"^\w[\w\d\-]+$", attr):
            raise AttributeError("Invalid characters in attribute name")
        else:
            super().__setattr__(attr, value)

Это не позволит мне использовать недопустимые символы в именах моих атрибутов. Очевидно, что super() не может использоваться в базовом классе Object, но это только пример.

2 ответа

Решение

Я думаю, что ваше предположение, что атрибуты должны быть "идентификаторами", неверно. Как вы заметили, объекты Python поддерживают произвольные атрибуты (не только идентификаторы), потому что для большинства объектов атрибуты хранятся в экземпляре __dict__ (который является dict и поэтому поддерживает произвольные строковые ключи). Однако для того, чтобы вообще иметь оператор доступа к атрибуту, необходимо ограничить набор имен, к которым можно получить доступ таким образом, чтобы можно было генерировать синтаксис, который может его анализировать.

Просто предполагается, что, если вам нужно использовать setattr для установки переменной, вы будете ссылаться на нее через getattr?

Нет, я не думаю, что это предполагается. Я думаю, что предполагается, что если вы ссылаетесь на атрибуты, используя . оператор, то вы знаете, что это за атрибуты. И если у вас есть возможность узнать, что это за атрибуты, то вы, вероятно, можете контролировать, как они называются. И если у вас есть контроль над тем, как они называются, тогда вы можете назвать им то, что парсер знает, как с этим справиться;-).

Я вижу эту особенность языка как непреднамеренный побочный эффект от того, как язык реализован.

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

Во-первых, из "Zen of Python":

Должен быть один - и желательно только один - очевидный способ сделать это.

Для меня очевидным способом доступа к атрибуту является . оператор. Таким образом, я считаю имена, несовместимые с оператором, незаконными, поскольку для их использования требуются "взломы".

Во-вторых, несмотря на то, что мы можем иметь целочисленный ключ в экземпляре __dict__ (как указал Марк Рэнсом) я не считаю int быть действительным именем атрибута. Тем более что это нарушает поведение объекта:

>>> a.__dict__[12] = 42
>>> dir(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unorderable types: int() < str()

В-третьих, не совсем верно то, что в документации Python говорится о . оператор и getattr() встроенная эквивалентность. Разница заключается в результирующем байт-коде. Первый компилируется в LOAD_ATTR байт-код, а последний - до CALL_FUNCTION:

>>> dis.dis(lambda x: x.a)
  1           0 LOAD_FAST                0 (x)
              3 LOAD_ATTR                0 (a)
              6 RETURN_VALUE
>>> dis.dis(lambda x: getattr(x, 'a'))
  1           0 LOAD_GLOBAL              0 (getattr)
              3 LOAD_FAST                0 (x)
              6 LOAD_CONST               1 ('a')
              9 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
         12 RETURN_VALUE

То же относится и к setattr() встроенный. Таким образом, я вижу встроенные функции как разновидность обхода, введенную для облегчения динамического доступа к атрибутам (встроенная функция отсутствовала в Python 0.9.1).

Наконец, следующий код (объявив __slots__ атрибуты) не удается:

>>> class A(object):
...     __slots__ = ['a.b']
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __slots__ must be identifiers

что предполагает, что имена атрибутов должны быть идентификаторами.

Тем не менее, поскольку я не могу найти какой-либо формальный синтаксис для разрешенных имен атрибутов, я также считаю, что аргумент @mgilson действителен.

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