LINUX.ORG.RU

python: как заставить pickle сохранять lambda функции

 , , ,


0

1
import pickle

class A:
   def __init__(self):
      self.__counter = count()
      self.__ids = defaultdict(lambda: next(self.__counter))
      self.processed_data = []

   def method1(self):
      # здесь используется __ids

   def method2(self):
      # здесь используется __ids

   def pickle_data(self, obj, filename="data.sav"):
      with open(filename, 'wb') as f:
         pickle.dump(obj, f)
      
   def unpickle_data(self, filename="data.sav"):
      with open(filename, 'rb') as f:
         return pickle.load(f)

my_class = A()
A.method1()
A.pickle_data(A)

pickle.dump() ругается что не может сохранить объект с lambda функцией:

AttributeError: Can't pickle local object 'Markov.__init__.<locals>.<lambda>'

Есть ли способ это решить?

★★
Ответ на: комментарий от lovesan

Есть. Просто нужен pickle поумнее, например cloudpickle или dill.

t184256 ★★★★★
()
Последнее исправление: t184256 (всего исправлений: 1)

Надо написать свой метод для пикля. Читать документацию по пиклю (__getstate__, __setstate__, кажись)

Sahas ★★★★☆
()

Просто объяви вместо лямбды функцию, которая делает то же самое. Можно прямо внутри __init__

anonymous
()

Есть, вынести фабрику отдельным методом.

def _default_id_factory(self):
    return next(self.__counter)

Лямбды – дело хорошее, но не бесплатное. Питонщики по подобным причинам их стараются избегать. Поэтому используют замыкания для бедных, т.е. объекты.

anonymous
()

Еще раз идея сохранять код не хорошая. Сохраняй данные. Хочется понять зачем ты это хочешь делать?

dem ★★
()
Ответ на: комментарий от bread

В питоне код это никакие не данные, это код.

И вообще, за выкрутасы такого рода в продакшне - руки отрубают.

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

lovesan ★★★
()

Будь это возможно, разговоров про «опенсорс поневоле» не было бы.

anonymous
()
Ответ на: комментарий от anonymous

Да ничего нет хорошего в лямбдах, только минусы к читаемости кода. Развлечение для студентов.

evgeny_aa ★★☆
()
Последнее исправление: evgeny_aa (всего исправлений: 1)
Ответ на: комментарий от lovesan

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

evgeny_aa ★★☆
()
Последнее исправление: evgeny_aa (всего исправлений: 1)

Есть способ.

Наследуете классы Pickler/Unpickler и перегружаете в них методы persistent_id/peristent_load

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

AntonI ★★★★★
()
Ответ на: комментарий от Sahas

get/setstate перегружаются для сериализуемого объекта. Для встроенных объектов питона их перегрузить скорее всего не выйдет.

Можно конечно вместо лямбда функции юзать экземпляр своего класса, тогда get/setstate могут помочь. Это не совсем то чего хочет ТС но тоже может сработать.

AntonI ★★★★★
()
Ответ на: комментарий от AntonI

ну, я имел в виду написать get/setstate для своего класса, а не для lambda :) Сходу мне не удалось придумать, как это реализовать, но как вариант можно просто запихать в pickle строку с кодом, где используется lambda. Правда, всё равно предложенный аноном вариант (с объявлением функции вместо lambda) выглядит самым простым и разумным...

Sahas ★★★★☆
()
Последнее исправление: Sahas (всего исправлений: 3)
Ответ на: комментарий от Sahas

Возможно это не то что хочет ТС - может ему хочется сохранять функцию по семантике а не по имени.

Так что да, хранится строка с кодом функции.

AntonI ★★★★★
()
Последнее исправление: AntonI (всего исправлений: 1)
Ответ на: комментарий от anonymous

Просто объяви вместо лямбды функцию, которая делает то же самое. Можно прямо внутри __init__

Попробовал вот так, но ругается:

class A:
   def __init__(self):
      def genid(self):
         return next(self.__counter)
      self.__counter = count()
      self.__ids = defaultdict(genid)
      self.processed_data = []

   def method1(self):
      # здесь ругается
      # TypeError: genid() missing 1 required positional argument: 'self'
      self.__ids['abc']
      self.__ids['def']
cruz7 ★★
() автор топика
Ответ на: комментарий от anonymous

Есть, вынести фабрику отдельным методом.

Спасибо, вот это помогло.

class A:
   def __init__(self):
      self.__counter = count()
      self.__ids = defaultdict(self._genid)
      self.processed_data = []

   def _genid(self):
      return next(self.__counter)

   def method1(self):
      self.__ids['abc']
      self.__ids['def']
      self.__ids['qqq']
      self.__ids['ppp']

   ....

cruz7 ★★
() автор топика
Ответ на: комментарий от AntonI

И получаете, внезапно, еще один cloudpickle или dill.

t184256 ★★★★★
()
Ответ на: комментарий от cruz7

Попробовал вот так, но ругается:

Ошибка, в genid() не нужно передавать self:

class A:
   def __init__(self):
      def genid():
         return next(self.__counter)
      self.__counter = count()
      self.__ids = defaultdict(genid)
      self.processed_data = []

   def method1(self):
      self.__ids['abc']
      self.__ids['def']

Но и в таком виде pickle не может сохранять данные:

AttributeError: Can't pickle local object 'A.__init__.<locals>.genid'

cruz7 ★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.