Не може да се извади int обектна грешка, когато обектът идва от SQLAlchemy?

Използвам YAML и SQLAlchemy. Дефинирах своя обект и мога да използвам YAML, за да го отпечатам добре. Въпреки това, когато се опитам да използвам YAML на обекта, върнат от заявка на SQLAlchemy, той се проваля с грешка can't pickle int objects. Разпечатах екземпляра, върнат от SQLAlchemy, и той показва правилния тип. Ще оставя кода да говори:

class HashPointer(Base):
    __tablename__ = 'hash_pointers'

    id = Column(Integer, primary_key=True)
    hash_code = Column(VARBINARY(64), unique=True)
    file_pointer = Column(Text)

    def __init__(self, hash_code, file_pointer):
        self.hash_code = hash_code
        self.file_pointer = file_pointer

    def __repr__(self):
        return "<HashPointer('%s', '%s')>" % (self.hash_code, self.file_pointer)

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
Engine = create_engine("mysql://user:pass@localhost/db", echo=True)
Session = sessionmaker(bind=Engine)
session = Session()
fhash = HashPointer(0x661623708235, "c:\\test\\001.txt")

# PRINTS FINE
print(yaml.dump(fhash))

for instance in session.query(HashPointer).all():
    # PRINTS FINE AS __repr__
    print instance

    # THROWS ERROR, 'CAN'T PICKLE INT OBJECTS'
    print(yaml.dump(instance))

person esac    schedule 21.10.2011    source източник
comment
Какъв е типът екземпляр? yaml.dump(10) работи добре, така че предполагам, че може да е тип SQLAlchemy, който няма необходимия метод за ецване (т.е. метод reduce, който сам по себе си връща типове за ецване).   -  person Raymond Hettinger    schedule 21.10.2011


Отговори (2)


Опитайте да добавите следното към вашия клас:

def __reduce__(self):
    'Return state information for pickling'
    return self.__class__, (int(self.hash_code), str(self.file_pointer))
person Raymond Hettinger    schedule 21.10.2011
comment
Объркан съм, тъй като print(yaml.dump(fhash)) работи добре, така че не се оплаква там. Защо типът ще се промени, когато идва от заявка? - person esac; 21.10.2011
comment
След print instance, поставете print type(instance). Какво получаваш? - person Raymond Hettinger; 21.10.2011
comment
отпечатва '‹class '__main__.HashPointer'›' - person esac; 21.10.2011
comment
А, тогава добавете метода __reduce__(), показан по-горе. Той учи YAML как да мариновате HashPointer, без излишно да бъркате в части на SQLAlchemy, които не могат да се пикат. - person Raymond Hettinger; 21.10.2011
comment
Има смисъл. Просто не съм сигурен защо типът, който fhash връща, не е ТОЧНО същият като това, което се връща от заявка към SQLalchemy. но сега поне знам как да го оправя :) - person esac; 21.10.2011

Оказва се, че методът по подразбиране reduce_ex (почти съм сигурен, че това е този в object(), но не е задължително.), който идва по линията, когато имате активен sqlalchemy, добавя член _sa_instance_state към 'състоянието', върнато в API reduce_ex, който PyYAML използва за извършване на сериализация.

Когато сериализирате обект, идващ от заявка на SqlAlchemy, това по същество е скрита част от метаданните на обекта, която е достъпна за по-нататъшни операции.

Това е този обект, в който сериализаторът на PyYAML се проваля. Можете да проверите това, като изпълните сериализацията си в PDB и видите две извиквания към represent_object във вашия стек за повиквания, дори и за сравнително прости резултати от обект на заявка на SQLAlchemy.

Тази връзка към екземпляр на заявка се използва, доколкото разбирам, за захранване на методи с възможност да се върнете към заявката, която генерира даден обект от живота на същия интерпретатор на python.

Ако се интересувате от тази функционалност (неща като session.new & session.dirty), ще трябва да внедрите поддръжка за това в сериализатора на PyYAML.

Ако не ви пука и просто искате вашите декларирани членове, можете да използвате базов клас, който „скрива“ тази връзка от извиквания към reduce* – имайте предвид, че това също ще наруши разширението на сериализатора на SQLAlchemy както и, така че обмислете внимателно плановете си.

Пример за базов клас за прилагане на тази промяна е:

DeclBase = declarative_base()

class Base(DeclBase):
    __abstract__ = True

    def __reduce_ex__(self, proto):
        ret = super(Base, self).__reduce_ex__(proto)
        ret = ( ret[0], ret[1], dict(ret[2]) ) + ret[3:]
        ret[2].pop('_sa_instance_state', None) # remove bad yamly from reduce state
        return ret

След това това ще ви позволи да обикаляте вашите обекти в/из yaml, въпреки че двупосочното пътуване ще ги разграничи от всички висящи транзакции или заявки. Това също може да има взаимодействия, ако използвате лениво заредени членове, например. Уверете се, че сериализирате всичко, което очаквате.

ЗАБЕЛЕЖКА/РЕДАКТИРАНЕ: Избрах да използвам reduce_ex тук, за да съм съвместим с евентуални други базови класове или миксини. Според https://docs.python.org/2/library/pickle .html#object.reduce_ex, това ще произведе правилното поведение за всички основни класове, като също така ще открие дали е деклариран само reduce().

Redux... reduce ще върне действителния dict на обекта на екземпляра -- ние не искаме да изтриваме от там, така че за __reduce* трябва всъщност плитко да копираме този dict.

person Bryon Roché    schedule 17.10.2014