Pull to refresh

Comments 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 для последующей работы с ними.
UFO just landed and posted this here
В такой реализации атрибуты объекта и ЕСТЬ значениями в словаре (не считая нативных методов класа dict), так что они не могут «путаться». Хотя да, согласен проблемы могут вылезти, например, если отнаследоватся от Dict и добавить в него новые свойства, они не будут видны как значения(item) в словаре-экземпляре этого класа, хотя будут видны как атрибуты(attr). Но эту проблему можно решить с помощью метакласа, что, по секрету, и было сделанно в системе, где это используются.)
UFO just landed and posted this here
Вы правы. Расширенную реализацию идеи, с использованием метакласса, которая частично решает проблему:

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, которые при этом не будут элементами словаря, а во-вторых поддерживаются как атрибуты и как элементы свойства по-умолчанию, определенные в класах-наследниках.
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
Вообще динамически подтыкаемые аттрибуты и методы сами по себе бомба с часовым механизмом.
Возможно не «функционал словаря», а функциональность словаря?)
Поправил, чтоб не резать слух математикам)
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
обращение к несуществующему атрибуту поднимет KeyError, вместо ожидаемого AttributeError.

Вообще-то поднимается как раз AttributeError.
В третьем варианте — AttributeError, во втором и первом — KeyError.
Как я понял, третий вариант в статье был окончательным. Поэтому я только его и тестировал.
в 3 варианте циклическая ссылка, со всеми вытекающими…
это неправильный «синтаксический сахар», такая практика не приветствуется!
Словарь — очень эффективная структура, с точки зрения времени доступа к данным. Логично предположить, что в словари (и уникальные наборы) собирают часто запрашиваемую информацию. Поэтому очень странно видеть подобную реализацию словаря, когда даже в официальных рекомендациях по оптимизации кода советуют избегать обращения через точку и пользоваться малочитаемыми хаками, если алгоритм часто повторяет это обращение.
UFO just landed and posted this here
Уважаемый 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 — против такого).
Sign up to leave a comment.

Articles

Change theme settings