BaseException.message en desuso en Python 2.6

177

Recibo una advertencia de que BaseException.message está en desuso en Python 2.6 cuando uso la siguiente excepción definida por el usuario:

class MyException(Exception):

    def __init__(self, message):
        self.message = message

    def __str__(self):
        return repr(self.message)

Esta es la advertencia:

DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
self.message = message

¿Qué tiene de malo esto? ¿Qué debo cambiar para deshacerme de la advertencia de desaprobación?

Desolat
fuente
9
Consulte PEP 352 para conocer los motivos: python.org/dev/peps/pep-0352/#retracted-ideas
balpha

Respuestas:

154

Solución: casi no se necesita codificación

Simplemente herede su clase de excepción Exceptiony pase el mensaje como primer parámetro al constructor

Ejemplo:

class MyException(Exception):
    """My documentation"""

try:
    raise MyException('my detailed description')
except MyException as my:
    print my # outputs 'my detailed description'

Puede usar str(my)o (menos elegante) my.args[0]para acceder al mensaje personalizado.

Antecedentes

En las versiones más recientes de Python (de 2.6) se supone que heredemos nuestras clases de excepción personalizadas de Exception que (a partir de Python 2.5 ) hereda de BaseException. El fondo se describe en detalle en PEP 352 .

class BaseException(object):

    """Superclass representing the base of the exception hierarchy.
    Provides an 'args' attribute that contains all arguments passed
    to the constructor.  Suggested practice, though, is that only a
    single string argument be passed to the constructor."""

__str__y __repr__ya están implementados de manera significativa, especialmente para el caso de un solo argumento (que puede usarse como mensaje).

No necesita repetir __str__o __init__implementar o crear _get_messagesegún lo sugerido por otros.

geekQ
fuente
55
Cambió BaseException a Exception según lo sugerido por @Matt
geekQ
8
Usar strinterrupciones si la excepción se construyó con un argumento unicode: str(MyException(u'\xe5'))plantea UnicodeEncodeError. El uso en unicodelugar de strno es infalible porque unicode(MyException('\xe5'))aumenta UnicodeDecodeError. ¿Significa esto que si no sé de antemano si el argumento es stro unicodetengo que usarlo .args[0]donde lo usé anteriormente .message?
kasperd
1
@kasperd Al igual que prácticamente todos los problemas de Python Unicode, esto se puede resolver con un sandwich Unicode .
Ryan P
3
@RyanP Eso supone que en realidad tengo control sobre lo que entra. Aquí hay un hecho de la vida que enfrenté. Tengo que manejar excepciones de múltiples bibliotecas de terceros. Algunos de ellos pasan unicode a sus excepciones y algunos pasan str. Una de las bibliotecas incluso tiene su propia clase que hereda de unicode pero tiene su propio método repr, que devuelve unicode en lugar de str como lo requiere la especificación.
Kasperd
1
Lo hice por cierto. reemplace todo uso de .messageen nuestro proyecto con .args[0], y eso no nos ha causado ningún problema.
kasperd
26

Sí, está en desuso en Python 2.6 porque desaparecerá en Python 3.0

La clase BaseException ya no proporciona una forma de almacenar mensajes de error. Tendrás que implementarlo tú mismo. Puede hacer esto con una subclase que usa una propiedad para almacenar el mensaje.

class MyException(Exception):
    def _get_message(self): 
        return self._message
    def _set_message(self, message): 
        self._message = message
    message = property(_get_message, _set_message)

Espero que esto ayude

Sahas
fuente
1
¿Cómo inicializarías el mensaje durante el aumento? Su código mostró que el mensaje se estaba configurando llamando a MyException ("algún mensaje")
eric.frederich
Los métodos en mi ejemplo son solo para implementar la propiedad del mensaje. La forma en que se usa la propiedad depende del codificador. En este caso, OP utiliza los métodos init y str que ha publicado en su código.
Sahas
1
Considere usar una variable pública en lugar de un captador / definidor, si solo lee otra variable. Siempre puede actualizarlo más tarde a una @propertysintaxis cuando realmente necesite encapsulación.
vdboor
2
@vdboor: está usando @propertypara deshabilitar la advertencia de desaprobación.
bukzor 01 de
No es propónico e inútil crear propiedades que no hacen nada más que obtener y establecer el valor, sin marcar ni modificar. El objetivo de las propiedades es permitir que uno use los atributos públicos libremente, hasta el momento en que realmente se necesite un captador o un definidor. Luego, convierte el atributo público en una propiedad, sin romper ningún código de cliente.
Luciano Ramalho
9
class MyException(Exception):

    def __str__(self):
        return repr(self.args[0])

e = MyException('asdf')
print e

Esta es tu clase en estilo Python2.6. La nueva excepción toma un número arbitrario de argumentos.

Maxim Sloyko
fuente
1
La antigua clase Exception también toma cualquier número de argumentos. Puede evitar por completo la propiedad del mensaje como lo que está haciendo, pero si eso rompiera su código existente, puede resolver el problema implementando su propia propiedad del mensaje.
Sahas
8

Cómo replicar la advertencia

Permítanme aclarar el problema, ya que uno no puede replicar esto con el código de muestra de la pregunta, esto replicará la advertencia en Python 2.6 y 2.7, si tiene las advertencias activadas (a través del -Windicador , la PYTHONWARNINGSvariable de entorno o el módulo de advertencias ):

>>> error = Exception('foobarbaz')
>>> error.message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foobarbaz'

Dejar de usar .message

Prefiero repr(error), que devuelve una cadena que contiene el nombre del tipo de error, la repr del mensaje, si hay una, y la repr de los argumentos restantes.

>>> repr(error)
"Exception('foobarbaz',)"

Eliminando la advertencia mientras sigue usando .message

Y la forma de deshacerse de él DeprecationWarninges subclasificar una excepción integrada como pretendían los diseñadores de Python:

class MyException(Exception):

    def __init__(self, message, *args):
        self.message = message
        # delegate the rest of initialization to parent
        super(MyException, self).__init__(message, *args)

>>> myexception = MyException('my message')
>>> myexception.message
'my message'
>>> str(myexception)
'my message'
>>> repr(myexception)
"MyException('my message',)"

obteniendo solo el .messageatributo sinerror.message

Si sabe que hubo un argumento, un mensaje, para la Excepción y eso es lo que desea, es preferible evitar el atributo del mensaje y simplemente eliminar strel error. Digamos para una subclase Exception:

class MyException(Exception):
    '''demo straight subclass'''

Y uso:

>>> myexception = MyException('my message')
>>> str(myexception)
'my message'

Ver también esta respuesta:

¿Forma correcta de declarar excepciones personalizadas en Python moderno?

Aaron Hall
fuente
4

Por lo que puedo decir, el simple uso de un nombre diferente para el atributo del mensaje evita el conflicto con la clase base y, por lo tanto, detiene la advertencia de desaprobación:

class MyException(Exception):

def __init__(self, message):
    self.msg = message

def __str__(self):
    return repr(self.msg)

Me parece un truco.

Quizás alguien pueda explicar por qué se emite la advertencia incluso cuando la subclase define un atributo de mensaje explícitamente. Si la clase base ya no tiene este atributo, no debería haber ningún problema.

Hollister
fuente
4

Continuando con la respuesta de geekQ , el reemplazo de código preferido depende de lo que debe hacer:

### Problem
class MyException(Exception):
    """My documentation"""

try:
    raise MyException('my detailed description')
except MyException as my:
    ### Solution 1, fails in Python 2.x if MyException contains 🔥
    # with UnicodeEncodeError: 'ascii' codec can't encode characters in position 24-25: ordinal not in range(128)
    print(my)  # outputs 'my detailed description'

### Solution 2
# Works in Python 2.x if exception only has ASCII characters,
# should always work in Python 3.x
str(my)

### Solution 3
# Required in Python 2.x if you need to handle non-ASCII characters,
# such as δσφφδσ (as pointed out by jjc) or emoji 🔥 💕 🎁 💯 🌹
# but does not work in Python 3.x
unicode(my)

A veces las excepciones tienen más de un argumento, así que my.args[0] lo que no se garantiza que proporcione toda la información relevante.

Por ejemplo:

# Python 2.7
try:
    u'\u12345'.encode('utf-8').encode('utf-8')
except UnicodeDecodeError as e:
    print e.args[0]
    print e.args
    print str(e)

Imprime como salida:

ascii
('ascii', '\xe1\x88\xb45', 0, 1, 'ordinal not in range(128)')
'ascii' codec can't decode byte 0xe1 in position 0: ordinal not in range(128)

Sin embargo, es una compensación sensible al contexto, porque, por ejemplo:

# Python 2.7
>>> str(SyntaxError())
'None'
# 'None' compares True which might not be expected
pzrq
fuente
1
Sí, no lo pienses demasiado. Usar en str(my)lugar de my.message.
Christian Long
1

El consejo de usar str (myexception) conduce a problemas Unicode en Python 2.7, por ejemplo:

str(Exception(u'δσφφδσ'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-5: ordinal not in range(128)

:(

unicode(Exception(u'δσφφδσ')) 

funciona como se esperaba y se prefiere en los casos en que parte del contenido de la cadena de error incluye la entrada del usuario

jjc
fuente
1

La publicación de pzrq dice usar:

str(e)

Esto era exactamente lo que necesitaba.

(Si se encuentra en un entorno Unicode, parece que:

unicode(e)

funcionará, y parece funcionar bien en un entorno no Unicode)

Pzrq dijo muchas otras cosas buenas, pero casi me pierdo su respuesta debido a todas las cosas buenas. Como no tengo 50 puntos, no puedo comentar su respuesta para intentar llamar la atención sobre la solución simple que funciona, y como no tengo 15, no puedo votar esa respuesta, pero puedo publicar (se siente al revés, pero oh bueno), así que aquí estoy publicando, probablemente pierda puntos por eso ...

Dado que mi punto es llamar la atención sobre la respuesta de pzrq, por favor, no te deslumbres y te lo pierdas en todo lo que sigue. Las primeras líneas de esta publicación son las más importantes.

Mi historia:

El problema por el que vine aquí fue si quieres atrapar una excepción de una clase sobre la que no tienes control, ¿entonces qué? ¡Ciertamente no voy a subclasificar todas las clases posibles que usa mi código en un intento de poder obtener un mensaje de todas las posibles excepciones!

Estaba usando:

except Exception as e:
   print '%s (%s)' % (e.message,type(e))

que, como todos sabemos, da la advertencia sobre la que OP preguntó (que me trajo aquí), y esto, que pzrq da como una forma de hacerlo:

except Exception as e:
   print '%s (%s)' % (str(e),type(e))

No.

No estoy en un entorno Unicode, pero la respuesta de jjc me hizo preguntarme, así que tuve que intentarlo. En este contexto esto se convierte en:

except Exception as e:
   print '%s (%s)' % (unicode(e),type(e))

que, para mi sorpresa, funcionó exactamente como str (e), así que ahora eso es lo que estoy usando.

No sé si 'str (e) / unicode (e)' es la 'forma aprobada de Python', y probablemente descubriré por qué eso no es bueno cuando llegue a 3.0, pero uno espera que la capacidad de manejar un excepción inesperada (*) sin morir y aún así obtener información de ella nunca desaparecerá ...

(*) Hmm. "excepción inesperada" - ¡Creo que simplemente tartamudeé!

rustycar
fuente