Как правильно выбрать экземпляр namedtuple

Я учусь использовать рассол. Я создал объект namedtuple, добавил его в список и попытался выбрать этот список. Однако я получаю следующую ошибку:

pickle.PicklingError: Can't pickle <class '__main__.P'>: it's not found as __main__.P

Я обнаружил, что если я запускаю код, не заключая его в функцию, он работает отлично. Требуется ли дополнительный шаг для обработки объекта при его обертывании внутри функции?

Вот мой код:

from collections import namedtuple
import pickle

def pickle_test():
    P = namedtuple("P", "one two three four")
    my_list = []
    abe = P("abraham", "lincoln", "vampire", "hunter")
    my_list.append(abe)
    f = open('abe.pickle', 'w')
    pickle.dump(abe, f)
    f.close()

pickle_test()

person Dirty Penguin    schedule 04.05.2013    source источник
comment
К сожалению, pickle не очень хорошо работает с namedtuples.   -  person Antimony    schedule 04.05.2013
comment
@Antimony: pickle прекрасно обрабатывает классы namedtuple; классов, определенных в локальном пространстве имен функций, не так много.   -  person Martijn Pieters    schedule 05.05.2013
comment
@AirThomas Этот вопрос был задан/отвечен год назад :)   -  person Dirty Penguin    schedule 18.06.2014
comment
Это не влияет на то, является ли это дубликатом, и теперь вопросы связаны друг с другом на боковой панели, что очень удобно. Комментарий не предназначен для критики, он генерируется автоматически при пометке.   -  person Air    schedule 18.06.2014
comment
Ни один не взят. Я просто подумал, что это смешно. Ссылка на вопрос действительно очень полезна :)   -  person Dirty Penguin    schedule 18.06.2014
comment
Есть аналогичная проблема. Если переменная типа и строка типа, используемая в конструкторе, не совпадают, то pickle также завершится ошибкой. например P = namedtuple("Q", "one two three four")   -  person Andrew Hoos    schedule 05.02.2015


Ответы (5)


Создайте именованный кортеж вне функции:

from collections import namedtuple
import pickle

P = namedtuple("P", "one two three four")

def pickle_test():
    my_list = []
    abe = P("abraham", "lincoln", "vampire", "hunter")
    my_list.append(abe)
    f = open('abe.pickle', 'w')
    pickle.dump(abe, f)
    f.close()

pickle_test()

Теперь pickle может его найти; теперь это глобальный модуль. При распаковке все, что нужно сделать модулю pickle, — это снова найти __main__.P. В вашей версии P является локальным для функции pickle_test(), и это не может быть проанализировано или импортировано.

Важно помнить, что namedtuple() — это фабрика классов; вы даете ему параметры, и он возвращает объект класса, из которого вы можете создавать экземпляры. pickle сохраняет только данные, содержащиеся в экземплярах, а также строковую ссылку на исходный класс для повторного восстановления экземпляров.

person Martijn Pieters    schedule 04.05.2013
comment
Итак, что, если я создаю namedtuple динамически, потому что я не знаю полей до времени выполнения? Есть ли еще способ обойти эту проблему? Я попытался создать другой метод вне класса, но это не сработало. - person Chuim; 26.06.2013
comment
@Chuim: назначьте его глобальным переменным вашего модуля (используйте globals(), чтобы получить сопоставление) под тем же именем, и pickle все равно сможет его найти. - person Martijn Pieters; 27.06.2013
comment
Если вы создаете несколько именованных кортежей с динамическими полями, расшифровка не будет работать после переопределения P в globals. - person Davit Tovmasyan; 01.08.2020
comment
@DavitTovmasyan: Нет, каждому классу нужно отдельное имя. - person Martijn Pieters; 01.08.2020

После того, как я добавил свой вопрос в качестве комментария к основному ответу, я нашел способ решить проблему создания динамически созданного namedtuple рассола. Это необходимо в моем случае, потому что я выясняю его поля только во время выполнения (после запроса к БД).

Все, что я делаю, это monkey patch namedtuple, эффективно перемещая его в модуль __main__:

def _CreateNamedOnMain(*args):
    import __main__
    namedtupleClass = collections.namedtuple(*args)
    setattr(__main__, namedtupleClass.__name__, namedtupleClass)
    namedtupleClass.__module__ = "__main__"
    return namedtupleClass

Имейте в виду, что имя namedtuple (которое предоставляется args) может перезаписать другого члена в __main__, если вы не будете осторожны.

person Chuim    schedule 26.06.2013
comment
Просто установите его на globals() вместо этого: globals()[namedtupleClass.__name__] = namedtupleClass. Тогда нет необходимости устанавливать __module__. - person Martijn Pieters; 27.06.2013
comment
Когда я попробовал globals()[namedtupleClass.__name__] = namedtupleClass, он действительно позволил мне замариновать мой объект, но когда я попытался разблокировать, у него не было namedtupleClass, в котором он нуждался. Мой совет: просто используйте словарь, пока они не сделают pickle достаточно умным для этого. - person Teque5; 21.01.2017

Я нашел этот ответ в другом потоке. Это все об именовании именованного кортежа. Это сработало для меня:

group_t =            namedtuple('group_t', 'field1, field2')  # this will work
mismatched_group_t = namedtuple('group_t', 'field1, field2')  # this will throw the error
person Ruvalcaba    schedule 12.10.2017

В качестве альтернативы вы можете использовать cloudpickle или dill для сериализации:

from collections import namedtuple

import cloudpickle
import dill



def dill_test(dynamic_names):
    P = namedtuple('P', dynamic_names)
    my_list = []
    abe = P("abraham", "lincoln", "vampire", "hunter")
    my_list.append(abe)
    with open('deleteme.cloudpickle', 'wb') as f:
        cloudpickle.dump(abe, f)
    with open('deleteme.dill', 'wb') as f:
        dill.dump(abe, f)


dill_test("one two three four")
person Peque    schedule 12.06.2017

Проблема здесь в том, что дочерние процессы не могут импортировать класс объекта - в данном случае класс P-, в случае проекта с несколькими моделями класс P должен быть импортируемым везде, где используется дочерний процесс.

быстрый обходной путь - сделать его импортируемым, повлияв его на globals()

globals()["P"] = P
person rachid el kedmiri    schedule 11.06.2018