AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79569792
Accepted
Paul C
Paul C
Asked: 2025-04-12 06:30:33 +0800 CST2025-04-12 06:30:33 +0800 CST 2025-04-12 06:30:33 +0800 CST

Pickle funciona, mas falha no pytest

  • 772

Criei um módulo "persist" contendo uma função obj_pickle que estou usando para conservar vários objetos no meu projeto. Funciona bem, mas está falhando no pytest, retornando:

>           pickle.dump(obj, file_handle, protocol=protocol)
E           AttributeError: Can't pickle local object 'test_object.<locals>.TestObject'

Correndo:

  • python 3.12
  • Versão do pytest: 8.3.5

Vejo que há problemas semelhantes relacionados ao multiprocessamento, mas não acho que o PyTest esteja expondo esse problema. Código abaixo, e muito obrigado.

# lib.persist.py
from pathlib import Path
import pickle


def obj_pickle(obj: object, dir:Path, protocol: int = pickle.HIGHEST_PROTOCOL) -> None:
    """
    Pickle an object to a byte file.
    """
    if not dir.exists():
        dir.mkdir(parents=True, exist_ok=True)
    path = Path(dir, obj.instance_name + '.pkl')
    with open(path, "wb") as file_handle:
        pickle.dump(obj, file_handle, protocol=protocol)
    print(f"{obj.__class__.__name__} object {obj.instance_name} saved to {path}")
# tests.test_persist.py
from pathlib import Path

import pytest

from lib.persist import obj_pickle

TEST_DIR = Path("test_dir")


@pytest.fixture
def test_object():
    class TestObject():
        def __init__(self, instance_file_name):
            self.instance_file_name = instance_file_name
            self.data = "This is a test object."

    test_object = TestObject("test_object_file")
    
    return test_object


def test_obj_pickle(test_object):
    obj_pickle(test_object, Path(TEST_DIR))
    path = Path(TEST_DIR, "test_object_file" + ".pkl")
    assert path.exists()
python
  • 2 2 respostas
  • 33 Views

2 respostas

  • Voted
  1. Best Answer
    user2357112
    2025-04-12T07:56:41+08:002025-04-12T07:56:41+08:00

    Isso não tem nada a ver com o pytest.

    Quando você carrega um objeto de um pickle, o picklemecanismo precisa saber qual deve ser a classe desse objeto. Mas se você definir uma classe dentro de uma função, cada execução dessa função gera uma classe totalmente nova. picklenão tem como dizer qual versão da classe usar, se é que existe alguma versão na sessão Python em que você está carregando o pickle.

    Em vez de tentar fazê-lo funcionar e criar uma pilha de inconsistências estranhas e casos extremos, pickleele simplesmente não oferece suporte à decapagem de instâncias de classes como essa.

    Você precisa mover sua definição de classe para fora da função em que ela está:

    class TestObject:
        def __init__(self, instance_file_name):
            self.instance_file_name = instance_file_name
            self.data = "This is a test object."
    
    @pytest.fixture
    def test_object():
        return TestObject("test_object_file")
    
    • 2
  2. welp
    2025-04-12T07:44:51+08:002025-04-12T07:44:51+08:00

    O pacote pickle essencialmente faz a pickle de um objeto, salvando-o como ele é em relação ao módulo atual. Se essa relação relativa não for definida, a pickle falhará. A maneira como o pytest é executado remove essa relação relativa, pois o pickle espera recebê-la. Aqui estão 4 soluções que você pode tentar para que os testes funcionem:

    1: Basta omitir a linha que está causando o problema ao executar o pytest. Você pode fazer isso com uma instrução if do tipo:

    import sys
    if "pytest" not in sys.modules: 
        with open(path, "wb") as file_handle: 
            pickle.dump(obj, file_handle, protocol=protocol)
    

    2: Você pode codificar o caminho relativo do seu objeto para a existência no nível do módulo. Isso é um pouco ruim e pode ser bastante instável, mas funciona se o seu objetivo for apenas executar o pytest durante o pickle. Isso provavelmente fará com que o pickle seja descompactado diretamente no módulo que o carrega (dependendo das versões do pacote e do interpretador do pickle) em vez de onde você esperaria, o que também pode criar colisões de namespace (especialmente porque o nome do seu objeto é "obj"). Modifiquei sua função para fazer isso:

    
    def obj_pickle(obj: object, dir:Path, protocol: int = pickle.HIGHEST_PROTOCOL) -> None:
        """
        Pickle an object to a byte file.
        """
        if not dir.exists():
            dir.mkdir(parents=True, exist_ok=True)
        path = Path(dir, obj.instance_name + '.pkl')
        
        # hardcoding object to module in case pytest muddies this
        # ----> 
        import __main__
        __main__.obj= obj
        # <----
    
        with open(path, "wb") as file_handle:
            pickle.dump(obj, file_handle, protocol=protocol)
        print(f"{obj.__class__.__name__} object {obj.instance_name} saved to {path}")
    

    3: Você pode combinar as duas sugestões anteriores para que a codificação do objeto no módulo SOMENTE aconteça durante o uso do pytest e os casos de uso normais de produção operem normalmente (não sofra com a instabilidade de forçar o objeto na função principal). Em seguida, você precisará recarregar o que estava na variável obj antes. Modifiquei sua função para fazer isso:

    import copy 
    import sys 
    
    def obj_pickle(obj: object, dir:Path, protocol: int = pickle.HIGHEST_PROTOCOL) -> None:
        """
        Pickle an object to a byte file.
        """
        if not dir.exists():
            dir.mkdir(parents=True, exist_ok=True)
        path = Path(dir, obj.instance_name + '.pkl')
        
    
        # only enforce module hardcoding during pytests
        if "pytest" in sys.modules: 
            
            # create the __main__ module object
            import __main__
            # check if there's a obj object already in the module attrs, if so copy it
            object_exists_flag = False
            try: tmp_obj_holder, object_exists_flag  = copy.deepcopy(__main__.obj), True
            except AttributeError: pass
            # assign your object to it
            __main__.obj= copy.deepcopy(obj)
        
        # run the writing operation
        with open(path, "wb") as file_handle:
            pickle.dump(obj, file_handle, protocol=protocol)
        print(f"{obj.__class__.__name__} object {obj.instance_name} saved to {path}")
        
        # only take concluding action during pytests
        if "pytest" in sys.modules: 
            # remove existing object
            del __main__.obj
            # place old object back in if it existed
            if object_exists_flag: __main__.obj= copy.deepcopy(tmp_obj_holder)
            
    

    4: Considere os pacotes marshalou dill, eles também serializam objetos, mas têm dependências muito diferentes do pickle e podem oferecer melhor compatibilidade com o pytest.

    Espero que tudo isso ajude!

    • -1

relate perguntas

  • Como divido o loop for em 3 quadros de dados individuais?

  • Como verificar se todas as colunas flutuantes em um Pandas DataFrame são aproximadamente iguais ou próximas

  • Como funciona o "load_dataset", já que não está detectando arquivos de exemplo?

  • Por que a comparação de string pandas.eval() retorna False

  • Python tkinter/ ttkboostrap dateentry não funciona quando no estado somente leitura

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve