Exception AttributeError: "'NoneType' object has no attribute 'dump'"

2

I find myself practicing with Python , doing a little POO , a class here, another over there ... :) ... and, also, with handling of files and pickle .

I expose the code block of a file called "manager.py":

# encoding: utf-8

from io import open
import pickle


class Categoria:

    def __init__(self, nombre):
        self._id = 0
        self.nombre = nombre

    def __str__(self):
        return '\t-> [{}] - {}.'.format( self._id, self.nombre.upper() )


class Gestor_Categorias:

    categorias = []
    id_nuevo = 0

    def __init__(self):
        self.cargar()

    '''
    Recogiendo el ID del último registro insertado
    '''
    def dame_id_nuevo(self):
        if len(self.categorias) == 0:
            self.id_nuevo += 1

        else:
            cat_ultimo_registro = self.categorias[-1]
            self.id_nuevo = int(cat_ultimo_registro._id) + 1

        return self.id_nuevo

    def agregar(self, cat):
        existe = self.buscar_si_existe(cat.nombre)
        if existe[0] == False:
            #Pasar el nuevo ID
            cat._id = self.dame_id_nuevo()
            self.categorias.append(cat)
            self.guardar()

    def buscar_si_existe(self, nombre):
        existe = False
        cat_bucle = ''
        for cat_bucle in self.categorias:
            if( cat_bucle.nombre == nombre ):
                existe = True
                cat_bucle = cat_bucle
        return [existe, cat_bucle]

    def mostrar(self):
        if len(self.categorias) == 0:
            print("\t-> El Gestor de CATEGORÍAS está vacío.")
            return

        print('')
        for cat in self.categorias:
            print '\t',
            print(cat)

    def cargar(self):
        fichero = open('gestor_categorias.pckl', 'ab+')
        #Como el comando anterior de APPEND pone el puntero al final,
        #habrá que recolocarlo al inicio del fichero
        fichero.seek(0)
        try:
            #en la primera carga, al estar vacío,
            #saltará hasta la excepción
            self.categorias = pickle.load(fichero)
        except:
            print("Creación satisfactoria del fichero de CATEGORÍAS ... ¡¡OK!!")
        finally:
            fichero.close()
            if( len(self.categorias) == 1 ):
                print("\nSe ha cargado {} categoría.".format( len(self.categorias) ))
            else:
                print("\nSe han cargado {} categorías.".format( len(self.categorias) ))

    def guardar(self):
        fichero = open('gestor_categorias.pckl', 'wb')
        pickle.dump(self.categorias, fichero)
        fichero.close()

    def borrar(self, nombre):
        #Borrado total o único :: ini
        #---------------------------------------------------
        if( nombre.lower() == 'total' ):
            tot = len(self.categorias)
            for cat in self.categorias:
                self.categorias.remove( cat )
                self.guardar()

            print("""
    Se borraron todas las categorías ({} en total).
        -> El archivo quedó vacío.
            """.format( tot ))

        else:
            existe = self.buscar_si_existe(nombre)
            if existe[0]:
                self.categorias.remove( existe[1] )
                self.guardar()

                print( '\nLa categoría llamada "{}" fue borrada.'.format( nombre ) )
                #return
        #---------------------------------------------------
        #Borrado total o único :: fin

    # Destructor de clase
    def __del__(self):
        self.guardar()  # guardado automático
        print("\n:: Se ha guardado el fichero de CATEGORÍAS - FIN ::")



#Acciones de Ejecución
#==========================================================
#[ CATEGORÍAS ]
''''''
gC = Gestor_Categorias()
gC.mostrar()
gC.agregar( Categoria('Arqueros') )

When executing this code, after creating the file if it does not already exist, the following exception is sent, I think, to the method " __ of __ " of the class " Gestor_Categorias ":

Exception AttributeError: "'NoneType' object has no attribute 'dump'" in <bound method Gestor_Categorias.__del__ of <__main__.Gestor_Categorias instance at 0x7f82dc406f38>> ignored

In the method " __ of __ " of the class " Gestor_Categorias ", I call the method of "save ()" to make a save of the file changes when closing the program.

As a curiosity, I have another file traced to it with two other classes (Character and Gestor_Personals) that also saves the records in the same way in a file and when closing (with its "__del__") it calls its "save () "but this exception is not thrown.

I have seen some other post about similar exceptions but I do not get to solve it with the recommendations that I have read.

So, can someone help me understand why the exception is thrown? How to solve it? Is it necessary not to implement the __del__ method? and, if it can not be implemented, where could I make the last call to "save ()" that I have on __del__ before closing?

Thanks for the possible answers.

    
asked by zacktagnan 23.04.2018 в 00:46
source

1 answer

2

The error gives us a clue:

  

'NoneType' object has no attribute 'dump'

That is, pickle is now None and therefore does not have the attribute / method dump .

Another hint, if you call __del__ explicitly at the end of the code everything works as we expect. What is happening here?

Well, simply the call to __del__ occurs during the process of terminating the interpreter (since your program has finished when your object is left without references and the GC takes over) and at this point the global variables , including the imports may have passed to a better life before the object instance itself. Our import of pickle does not exist at the moment in which the call to __del__ is made by the GC. The own documentation of the method warns us:

  

__del__() can be executed during interpreter shutdown. As a consequence, the global variables it needs to access (including other modules) may already have been deleted or set to None. Python guarantees that globals whose name begins with a single underscore are deleted from their module before other globals are deleted; if no other references to such globals exist, this may help in assuring that imported modules are still available at the time when the __del__( ) method is called.

What comes to say:

  

__del __ () can be executed during the shutdown of the interpreter. As a consequence, the global variables you need to access (including other modules) may have been removed or set to None . Python ensures that global variables whose name begins with a single underscore are removed from their module before other global variables are removed; if there are no other references to such global variables, this can help ensure that the imported modules are still available at the time the __del__ () method is called.

Since we have no guarantees that the imports are alive at this point because we do not have in our hands to know the exact moment when the GC is going to destroy the object, the use of __del__ is extremely fragile in this case. In general, one must be very careful with __del__ since it is susceptible to problems that affect the operation of the GC, for example, circular references.

In cases like this, a much more robust approach is to implement our own context manager instead of resorting to __del__ :

def __enter__(self):
    return self

def __exit__(self, ext_type, exc_value, traceback):
    self.guardar()
    print("\n:: Se ha guardado el fichero de CATEGORÍAS - FIN ::")

Then we instantiate using with :

with Gestor_Categorias() as gC:
    gC.mostrar()
    gC.agregar( Categoria('Arqueros') )
  

NOTE: The __del__ method will always be called when the object is destroyed because its reference counter reaches 0 and the interpreter is still running. However, we can not assume that the __del__ method will always be called in the case in which an object exists when the interpreter ends. This is especially true for Python 2.7 when there are circular references that prevent the GC from destroying the object, living it until the interpreter ends. In Python > = 3.4 ( PEP-442 ) the latter is solved.

This has nothing to do with the question, it's just an observation. To delete all the elements of a list do:

for cat in self.categorias:
    self.categorias.remove( cat )

It is also inefficient that as a general rule you should never modify the length of an iterable while iterating over the path for in or using an iterator. To clean a list use:

del self.categorias[:]

Or in Python 3 too:

self.categorias.clear()
    
answered by 23.04.2018 / 02:32
source