Как стать автором
Обновить

Комментарии 41

Как быть, если ключ совпадает с именем метода? Имхо, не нужно путать сущности: интерфейс доступа к данным должен быть один.
Такая ситуация не будет мешать нативным фунцкиям словаря, так что всегда остается возможность написать a["copy"].
Интерфейс доступа к данным остался один, только теперь его можно синтаксически написать по-разному: оба варианта работают эквивалентно.
Тут возникает неопределенность: через __getitem__ можно гарантированно получить хранимое значение, через __getattr__ — возможно нарваться на built-in метод или метод/свойство/атрибут класса наследника, что создаст двоякость. С __setattr__ и __setitem__ тоже самое — неопределенность:
>>> foo = Dict()
>>> foo.update = 'bar'
>>> foo.update
<built-in method update of Dict object at 0xb74e580c>
тогда как ожидалось все же 'bar'.

Получается, что безопасней пользоваться dict-like интерфейсом, чтобы гарантировать получение нужного значения. Не зачем усложнять себе жизнь.
не знаю как у вас, но у меня после ваших манипуляций foo.update возвращает 'bar'. Но это порождает другую проблему: перекрытие нативных методов с последующими трудноуловимыми багами там, где они используются.
Потому что я использовал реализацию 2. Вариант 3 не будет работать, если задан __slots__. В любом случае, в третьем варианте та жа проблема, только вид с другой стороны.
Мне кажется, что само задание __slots__ противоречит самой идее, что объект является словарем, в который можно добавлять/изменять/удалять свойства. Объясните, если я не прав.
рекурсивный вариант
У Вас при инициализации чего-то вида c = Dict({'a':1,'b':{'c':3,'d':4}}), судя по коду, не будут доступны через точку аттрибуты внутренних словарей
Да, рекурсивный вариант не предполагался, хотя можно написать так: Dict({'a':1,'b':Dict({'c':3,'d':4})}).
Кстати, рекурсивным вариантом вроде того, что вы предложили, очень удобно оборачивать какие-то распаршеные конфиги или другие данные, полученные из yaml или json для последующей работы с ними.
НЛО прилетело и опубликовало эту надпись здесь
В такой реализации атрибуты объекта и ЕСТЬ значениями в словаре (не считая нативных методов класа dict), так что они не могут «путаться». Хотя да, согласен проблемы могут вылезти, например, если отнаследоватся от Dict и добавить в него новые свойства, они не будут видны как значения(item) в словаре-экземпляре этого класа, хотя будут видны как атрибуты(attr). Но эту проблему можно решить с помощью метакласа, что, по секрету, и было сделанно в системе, где это используются.)
НЛО прилетело и опубликовало эту надпись здесь
Вы правы. Расширенную реализацию идеи, с использованием метакласса, которая частично решает проблему:

def validDictProperty(name, value):
	return not callable(value) and not isinstance(value, (property, classmethod, staticmethod))

class DictMetaclass(type):
	def __init__(cls, name, bases, namespace):
		cls.default = {}
		for base in bases:
			if hasattr(base, "default"):
				cls.default.update(base.default)
		cls.default.update((k, v) for k, v in namespace.items() if validDictProperty(k, v))

class Dict(dict):
	__metaclass__ = DictMetaclass
	def __new__(cls, *args, **kwargs):
		self = dict.__new__(cls)
		self.update(cls.default)
		self.__dict__ = self
		return self


Такая реализация во-первых позволяет объектам иметь методы, определенные в класах-наследниках Dict, которые при этом не будут элементами словаря, а во-вторых поддерживаются как атрибуты и как элементы свойства по-умолчанию, определенные в класах-наследниках.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Вообще динамически подтыкаемые аттрибуты и методы сами по себе бомба с часовым механизмом.
Возможно не «функционал словаря», а функциональность словаря?)
Поправил, чтоб не резать слух математикам)
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
обращение к несуществующему атрибуту поднимет KeyError, вместо ожидаемого AttributeError.

Вообще-то поднимается как раз AttributeError.
В третьем варианте — AttributeError, во втором и первом — KeyError.
Как я понял, третий вариант в статье был окончательным. Поэтому я только его и тестировал.
в 3 варианте циклическая ссылка, со всеми вытекающими…
это неправильный «синтаксический сахар», такая практика не приветствуется!
Словарь — очень эффективная структура, с точки зрения времени доступа к данным. Логично предположить, что в словари (и уникальные наборы) собирают часто запрашиваемую информацию. Поэтому очень странно видеть подобную реализацию словаря, когда даже в официальных рекомендациях по оптимизации кода советуют избегать обращения через точку и пользоваться малочитаемыми хаками, если алгоритм часто повторяет это обращение.
НЛО прилетело и опубликовало эту надпись здесь
Уважаемый tanenn очень хорошо описал отношение к этой «фишке» в комментарие выше: habrahabr.ru/blogs/python/129201/#comment_4276323
Конечно, если вам нужен словарь, как «очень эффективная структура, с точки зрения времени доступа к данным», то никакой синтаксический сахар и вообще любые слои над стандартной библиотекой ни к чему.
Слои над стандартной библиотекой решают прикладные задачи — унификация интерфейсов и отражение предметной области в коде. Они полезны как раз потому, что не являются простым синтаксическим сахаром.

А вот слой «структуры данных как в JavaScript» поверх отдельно взятой структуры данных, которая сама по себе не отражает никакой семантики из предметной области, а служит лишь «строительным блоком» — настоящий overengineering. С печальными последствиями на производительности.

Кстати, хороший «синтаксический сахар» — это тот, который вводится на уровне конструкций языка, либо способен компилироваться в код без side-эффектов и деградаций в производительности.
Спасибо за ссылку. Многие вещи по отдельности встречал в разных статьях, но здесь они очень хорошо собраны и описаны.
В осуждаемом многими за некошерность фреймворке web2py есть качественная реализация первого варианта, описанного в статье — класс Storage

class Storage(dict):

    """
    A Storage object is like a dictionary except `obj.foo` can be used
    in addition to `obj['foo']`.

        >>> o = Storage(a=1)
        >>> print o.a
        1

        >>> o['a']
        1

        >>> o.a = 2
        >>> print o['a']
        2

        >>> del o.a
        >>> print o.a
        None

    """

    def __getattr__(self, key):
        if key in self:
            return self[key]
        else:
            return None

    def __setattr__(self, key, value):
        if value == None:
            if key in self:
                del self[key]
        else:
            self[key] = value

    def __delattr__(self, key):
        if key in self:
            del self[key]
        else:
            raise AttributeError, "missing key=%s" % key

    def __repr__(self):
        return '<Storage ' + dict.__repr__(self) + '>'

    def __getstate__(self):
        return dict(self)

    def __setstate__(self, value):
        for (k, v) in value.items():
            self[k] = v
Интересно будет ли работать такой код под PyPy 1.6+ Может пробовали?
Это то что я хотел при работе со словарями, тоже думал о такой реализаций, но без проблем этого не сделать, поэтому желания поубавилось. Но если приходится часто работать с какой либо структурой, которая статична, то пытаюсь использовать именованные кортежи, но это лишь покрывает малую долю желаемого.
¿а чем вас не устаивает просто в лоб:
class D(object):
  pass

d = D()
d.foo = "foo"
Тоже делал схожую штуку (она делала разбор очень сложного конфига в DOM, состоящий из подобны оюъектов), из нее хочу добавить полезный сниппет метода:


def get_public_attrs(self):
return filter(lambda (a,b): a[0] != '_', self.__dict__.items())


Правда в итоге я пришел к выводу, что pythonic-pythonic-ом, но подобное нестандартное использование очень сильно мешает созданию reusable кода и вообще скорость и корректность его восприятия.

На питоне можно сделать миллион очень необычных и извилистых конструкций, но чем дальше, тем реже я пользуюсь даже генераторами с лямбдами.
В топку такое решение! Не используйте его — это создатель циклических ссылок. То есть у вас приложение будет неконтролируемо жрать память на пустом месте, если вы будете надеятся на короткую живучесть этих словарей (и их будет много притом). То есть вместо дешевой сборки объектов с более менее детерминированной деструкцией вы получити сборку мусора циклических ссылок, с недетерминированной деструкцией (RAII — против такого).
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации