полка питона: одни и те же объекты становятся разными объектами после открытия полки - PullRequest
0 голосов
/ 06 ноября 2018

Я вижу это поведение при использовании полки:

import shelve

my_shelve = shelve.open('/tmp/shelve', writeback=True)
my_shelve['a'] = {'foo': 'bar'}
my_shelve['b'] = my_shelve['a']
id(my_shelve['a'])  # 140421814419392
id(my_shelve['b'])  # 140421814419392
my_shelve['a']['foo'] = 'Hello'
my_shelve['a']['foo']  # 'Hello'
my_shelve['b']['foo']  # 'Hello'
my_shelve.close()

my_shelve = shelve.open('/tmp/shelve', writeback=True)
id(my_shelve['a'])  # 140421774309128
id(my_shelve['b'])  # 140421774307832 -> This is weird.
my_shelve['a']['foo']  # 'Hello'
my_shelve['b']['foo']  # 'Hello'
my_shelve['a']['foo'] = 'foo'
my_shelve['a']['foo']  # 'foo'
my_shelve['b']['foo']  # 'Hello'
my_shelve.close()

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

  1. Кто-нибудь знает, что здесь происходит?
  2. Кто-нибудь знает, как избежать этого поведения?

Я использую Python 3.7.0

Ответы [ 3 ]

0 голосов
/ 06 ноября 2018

shelve сохраняет выбранные представления объектов в файле полки. Когда вы сохраняете тот же объект, что и my_shelf['a'] и my_shelf['b'], shelve записывает метку объекта для клавиши 'a' и другую метку объекта для клавиши 'b'. Одна ключевая вещь, которую нужно отметить, - это то, что она выбирает все значения отдельно.

Когда вы снова откроете полку, shelve использует маринованные представления для восстановления объектов. Он использует рассол для 'a', чтобы восстановить сохраненный вами диктат, и использует рассол для 'b', чтобы восстановить сохраненный вами диктат снова .

Соленья не взаимодействуют друг с другом и не имеют возможности вернуть один и тот же объект друг другу, когда они не выбраны. В представлении на диске нет никаких указаний на то, что my_shelf['a'] и my_shelf['b'] когда-либо были одним и тем же объектом; полка, изготовленная с использованием отдельных предметов для my_shelf['a'] и my_shelf['b'], может выглядеть идентично.


Если вы хотите сохранить тот факт, что эти объекты были идентичны, вы не должны хранить их в отдельных ключах полки. Подумайте о том, чтобы использовать один и тот же диктовку с помощью клавиш 'a' и 'b' вместо использования shelve.

0 голосов
/ 06 ноября 2018

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

У меня есть быстрый пример использования __reduce__ ниже. Но вы, вероятно, должны знать, что это не самая лучшая идея.

Может быть проще использовать библиотеку copyreg, но вы должны знать, что все, что вы делаете с этой библиотекой, будет влиять на все, что вы маринете все время. Метод __reduce__ будет чище и безопаснее, поскольку вы явно указываете pickle, какие классы вы ожидаете, чтобы иметь такое поведение, вместо того, чтобы неявно применять их ко всему.

В этой системе есть худшие предостережения. Идентификатор всегда будет меняться между экземплярами Python, поэтому вам нужно сохранить исходный идентификатор во время __init__ (или __new__, как бы вы это ни делали) и убедиться, что теперь несуществующее значение сохраняется, когда оно вынимается из полки позже , Уникальность идентификатора даже не гарантируется в сеансе Python из-за сборки мусора. Я уверен, что появятся другие причины не делать этого. (Я постараюсь обратиться к ним со своим классом, но я не даю обещаний.)

import uuid

class UniquelyPickledDictionary(dict):
    _created_instances = {}

    def __init__(self, *args, _uid=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.uid = _uid
        if _uid is None:
            self.uid = uuid.uuid4()
        UniquelyPickledDictionary._created_instances[self.uid] = self

    def __reduce__(self):
        return UniquelyPickledDictionary.create, (self.uid,), None, None, list(self.items())

    @staticmethod
    def create(uid):
        if uid in UniquelyPickledDictionary._created_instances:
            return UniquelyPickledDictionary._created_instances[uid]
        return UniquelyPickledDictionary(_uid=uid)

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

Эквивалентная версия, использующая copyreg, может быть сделана для засолки любого класса, но потребует специальной обработки при расщеплении, чтобы гарантировать точки повторения для того же объекта. Чтобы сделать его наиболее общим, необходимо проверить «уже созданный» словарь, чтобы сравнить его со всеми экземплярами. Чтобы сделать его наиболее удобным, к экземпляру необходимо добавить новое значение, что может быть невозможно, если объект использует __slots__ (или в некоторых других случаях).

Я использую 3.6, но я думаю, что это должно работать для любой все еще поддерживаемой версии Python. Он сохранил объект в моем тестировании, с рекурсией (но pickle уже делает это) и множественными расщеплениями.

0 голосов
/ 06 ноября 2018

Кто-нибудь знает, что здесь происходит?

Переменные Python являются ссылками на объекты. Когда вы набираете

a = 123

негласно, Python создает новый объект int(123) и затем указывает a на него. Если потом пишешь

a = 456

Затем Python создает другой объект int(456) и обновляет a, чтобы он стал ссылкой на новый объект. Он не переписывает то, что хранится в блоке с именем a, так, как присваивается переменная в языке Си. Так как id() возвращает адрес памяти объекта (ну, в любом случае эталонная реализация CPython), он будет иметь другое значение каждый раз, когда вы указываете a на другой объект.

Кто-нибудь знает, как избежать такого поведения?

Вы не можете, потому что это свойство того, как работает присваивание.

...