Почему в Python нет встроенного гибридного getattr + __getitem__? - PullRequest
9 голосов
/ 18 июля 2011

У меня есть методы, которые принимают диктовки или другие объекты и имена «полей» для извлечения из этих объектов. Если объект является dict, то метод использует __getitem__ для получения именованного ключа, или же он использует getattr для получения именованного атрибута. Это довольно часто встречается в языках веб-шаблонов. Например, в шаблоне Chameleon вы можете иметь:

<p tal:content="foo.keyname">Stuff goes here</p>

Если вы передадите foo как диктовку типа {'keyname':'bar'}, то foo.keyname выберет ключ 'keyname', чтобы получить 'bar'. Если foo является экземпляром такого класса, как:

class Foo(object):
    keyname = 'baz'

затем foo.keyname извлекает значение из атрибута keyname. Сам Хамелеон реализует эту функцию (в модуле chameleon.py26) следующим образом:

def lookup_attr(obj, key):
    try:
        return getattr(obj, key)
    except AttributeError as exc:
        try:
            get = obj.__getitem__
        except AttributeError:
            raise exc
        try:
            return get(key)
        except KeyError:
            raise exc

Я реализовал это в моем собственном пакете вроде:

try:
    value = obj[attribute]
except (KeyError, TypeError):
    value = getattr(obj, attribute)

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

Ответы [ 3 ]

15 голосов
/ 19 июля 2011

Я как бы наполовину прочитал твой вопрос, написал ниже, а затем перечитал твой вопрос и понял, что ответил на немного другой вопрос. Но я думаю, что ниже на самом деле все еще дает ответ после рода. Если вы так не думаете, сделайте вид, что задали этот более общий вопрос, который, я думаю, включает в себя ваш подвопрос:

"Почему Python не предоставляет какой-либо встроенный способ обработки атрибутов и элементов как взаимозаменяемых?"


Я задумался над этим вопросом, и я думаю, что ответ очень прост. Когда вы создаете тип контейнера, очень важно различать атрибуты и items . Любой достаточно хорошо разработанный контейнерный тип будет иметь ряд атрибутов - часто, хотя и не всегда, методов - которые позволяют ему изящно управлять своим содержимым. Так, например, у dict есть items, values, keys, iterkeys и так далее. Все эти атрибуты доступны с использованием нотации .. К элементам, с другой стороны, обращаются, используя обозначение []. Так что не может быть никаких столкновений.

Что происходит, когда вы включаете доступ к элементу, используя нотацию .? Внезапно у вас есть перекрывающиеся пространства имен . Как вы справляетесь со столкновениями сейчас? Если вы создаете подкласс и диктуете эту функциональность, либо вы, как правило, не можете использовать такие ключи, как items, либо вам необходимо создать какую-то иерархию пространства имен. Первый вариант создает правило, которое является обременительным, сложным для исполнения и неукоснительным. Второй вариант создает раздражающую степень сложности без полного решения проблемы коллизий, поскольку вам все еще нужно иметь альтернативный интерфейс, чтобы указать, хотите ли вы items элемент или items атрибут.

Теперь, для некоторых видов очень примитивных типов это приемлемо. Наверное, поэтому в стандартной библиотеке, например, namedtuple. (Но обратите внимание, что namedtuple подвержен этим самым проблемам, поэтому, вероятно, он был реализован как фабричная функция (предотвращает наследование) и использует странные, закрытые имена методов, такие как _asdict.)

Также очень, очень, очень легко создать подкласс object без (публичных) атрибутов и использовать на нем setattr. Даже довольно легко переопределить __getitem__, __setitem__ и __delitem__ для вызова __getattribute__, __setattr__ и __delattr__, так что доступ к элементу становится синтаксическим сахаром для getattr(), setattr() и т. Д. (Хотя это немного более сомнительно, так как вызывает несколько неожиданное поведение.)

Но для любого хорошо разработанного класса контейнеров, который вы хотите иметь возможность расширять и наследовать, добавляя новые полезные атрибуты, гибрид __getattr__ + __getitem__ был бы, честно говоря, огромным PITA.

5 голосов
/ 18 июля 2011

Самая близкая вещь в стандартной библиотеке python - namedtuple (), http://docs.python.org/dev/library/collections.html#collections.namedtuple

Foo = namedtuple('Foo', ['key', 'attribute'])
foo = Foo(5, attribute=13)
print foo[1]
print foo.key

Или вы можете легко определить свой собственный тип, который всегда на самом деле сохраняет его, но допускает появление установки атрибута и получение:

class MyDict(dict):
    def __getattr__(self, attr):
        return self[attr]
    def __setattr__(self, attr, value):
        self[attr] = value

d = MyDict()

d.a = 3
d[3] = 'a'
print(d['a']) # 3
print(d[3]) # 'a'
print(d['b']) # Returns a keyerror

Но не делайте d.3, потому что это синтаксическая ошибка. Конечно, существуют более сложные способы создания гибридного типа хранилища, например, поищите в Интернете множество примеров.

Что касается проверки того и другого, то путь Хамелеона выглядит основательно. Когда дело доходит до «почему нет способа сделать и то, и другое в стандартной библиотеке», это потому, что двусмысленность - ПЛОХАЯ. Да, у нас есть ducktyping и все другие виды маскировки в python, и классы на самом деле просто словари в любом случае, но в какой-то момент мы хотим получить другую функциональность из контейнера, такого как dict или list, чем мы хотим от класса, с порядком разрешения методов , переопределение и т. д.

4 голосов
/ 18 июля 2011

Вы можете довольно легко написать свой собственный подкласс dict, который ведет себя таким образом. Минимальная реализация, которую я люблю называть «кучей» атрибутов, выглядит так:

class Pile(dict):
    def __getattr__(self, key):
        return self[key]
    def __setattr__(self, key, value):
        self[key] = value

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

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

...