¿Son los bloques try/except anidados en Python una buena práctica de programación?

6 minutos de lectura

avatar de usuario
Mical

Estoy escribiendo mi propio contenedor, que debe dar acceso a un diccionario interno mediante llamadas de atributos. El uso típico del contenedor sería así:

dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

Sé que puede ser estúpido escribir algo como esto, pero esa es la funcionalidad que necesito proporcionar. Estaba pensando en implementar esto de la siguiente manera:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        try:
            return self.dict[item]
        except KeyError:
            print "The object doesn't have such attribute"

No estoy seguro de si los bloques try/except anidados son una buena práctica, por lo que otra forma sería usar hasattr() y has_key():

def __getattribute__(self, item):
        if hasattr(self, item):
            return object.__getattribute__(item)
        else:
            if self.dict.has_key(item):
                return self.dict[item]
            else:
                raise AttributeError("some customised error")

O para usar uno de ellos y uno intente atrapar un bloque como este:

def __getattribute__(self, item):
    if hasattr(self, item):
        return object.__getattribute__(item)
    else:
        try:
            return self.dict[item]
        except KeyError:
            raise AttributeError("some customised error")

¿Qué opción es más pitónica y elegante?

  • Apreciaría a los dioses pitón proporcionando if 'foo' in dict_container:. Amén.

    – gseattle

    15 de febrero de 2019 a las 4:26

avatar de usuario
lqc

Tu primer ejemplo está perfectamente bien. Incluso la documentación oficial de Python recomienda este estilo conocido como EAFP.

Personalmente, prefiero evitar anidar cuando no es necesario:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass  # Fallback to dict
    try:
        return self.dict[item]
    except KeyError:
        raise AttributeError("The object doesn't have such attribute") from None

PD. has_key() ha quedado en desuso durante mucho tiempo en Python 2. Use item in self.dict en cambio.

  • return object.__getattribute__(item) es incorrecto y producirá un TypeError porque se está pasando el número incorrecto de argumentos. en cambio, debería ser return object.__getattribute__(self, item).

    – martineau

    20 de junio de 2014 a las 1:05


  • PEP 20: plano es mejor que anidado.

    – 0 _

    25 de enero de 2015 a las 18:25

  • Lo que hace el from None significa en la última línea?

    – Nicolás

    27/03/2017 a las 23:10

  • @niklas Esencialmente suprime el contexto de excepción (“durante el manejo de esta excepción ocurrió otra excepción” -mensajes que). Mira aquí

    – Kade

    27 de febrero de 2018 a las 18:32

  • El hecho de que los documentos de Python recomienden anidar los intentos es una locura. Es obviamente un estilo horrendo. La forma correcta de manejar una cadena de operaciones donde las cosas pueden fallar sería usar algún tipo de construcción monádica, que Python no admite.

    – Henry Henrison

    18 sep 2019 a las 18:43


avatar de usuario
bruno penteado

Mientras que en Java es una mala práctica usar excepciones para el control de flujo (principalmente porque las excepciones obligan a la JVM a recopilar recursos (más aquí)), en Python tiene dos principios importantes: pato escribiendo y EAFP. Básicamente, esto significa que se le anima a intentar usar un objeto de la forma en que cree que funcionaría y manejarlo cuando las cosas no sean así.

En resumen, el único problema sería que su código tuviera demasiada sangría. Si lo desea, intente simplificar algunos de los anidamientos, como sugirió lqc en la respuesta sugerida anteriormente.

avatar de usuario
Sławomir Lenart

Sólo tenga cuidado – en este caso el primero finally se toca, pero saltado también.

def a(z):
    try:
        100/z
    except ZeroDivisionError:
        try:
            print('x')
        finally:
            return 42
    finally:
        return 1


In [1]: a(0)
x
Out[1]: 1

  • Vaya, me sorprende… ¿Podría indicarme algún fragmento de documentación que explique este comportamiento?

    – Michal

    28 de marzo de 2018 a las 13:56

  • @Michal: para tu información: ambos finally los bloques se ejecutan para a(0)pero solo padre finally-return es regresado.

    – Sławomir Lenart

    28 de marzo de 2018 a las 14:44

avatar de usuario
BrenBarn

Para su ejemplo específico, en realidad no necesita anidarlos. Si la expresión en el try block tiene éxito, la función regresará, por lo que cualquier código después de todo el bloque try/except solo se ejecutará si falla el primer intento. Así que solo puedes hacer:

def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass
    # execution only reaches here when try block raised AttributeError
    try:
        return self.dict[item]
    except KeyError:
        print "The object doesn't have such attribute"

Anidarlos no está mal, pero siento que dejarlo plano hace que la estructura sea más clara: estás probando secuencialmente una serie de cosas y devolviendo la primera que funciona.

Por cierto, es posible que desee pensar si realmente desea utilizar __getattribute__ en vez de __getattr__ aquí. Usando __getattr__ simplificará las cosas porque sabrá que el proceso normal de búsqueda de atributos ya ha fallado.

avatar de usuario
Cuidado devastador

Un buen y simple ejemplo de intento/excepto anidado podría ser el siguiente:

import numpy as np

def divide(x, y):
    try:
        out = x/y
    except:
        try:
            out = np.inf * x / abs(x)
        except:
            out = np.nan
    finally:
        return out

Ahora prueba varias combinaciones y obtendrás el resultado correcto:

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

(Por supuesto, tenemos NumPy, por lo que no necesitamos crear esta función).

En mi opinión, esta sería la forma más pitónica de manejarlo, aunque y porque hace que tu pregunta sea discutible. Tenga en cuenta que esto define __getattr__() en vez de __getattribute__() porque hacerlo significa que solo tiene que lidiar con los atributos “especiales” que se mantienen en el diccionario interno.

def __getattr__(self, name):
    ''' Only called when an attribute lookup in the "usual" places has failed. '''
    try:
        return self.my_dict[name]
    except KeyError:
        raise AttributeError("some customized error message")

avatar de usuario
Pedro Mortensen

De acuerdo a la documentaciónes mejor manejar múltiples excepciones a través de tuplas o así:

import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print "I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print "Could not convert data to an integer."
except:
    print "Unexpected error: ", sys.exc_info()[0]
    raise

  • Esta respuesta realmente no aborda la pregunta original, pero para cualquiera que la lea, tenga en cuenta que el “desnudo”, excepto que al final, es una idea terrible (por lo general), ya que detectará todo, incluido, por ejemplo, NameError & KeyboardInterrupt, ¡que generalmente no es lo que querías decir!

    – perdió

    10 de enero de 2019 a las 8:02

  • Dado que el código vuelve a generar la misma excepción justo después de la declaración de impresión, ¿es esto realmente un gran problema? En este caso, puede proporcionar más contexto sobre la excepción sin ocultarla. Si no volviera a subir, estaría totalmente de acuerdo, pero no creo que haya riesgo de ocultar una excepción que no tenía la intención de ocultar.

    – Escala Nimbus

    11 de junio de 2020 a las 21:25


¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad