2012-01-19 13 views
5

Me encuentro tratando de convertir parámetros de constructor a sus tipos correctos muy a menudo en mis programas de Python. Hasta ahora he estado usando un código similar a este, así que no tengo que repetir los argumentos de excepción:Prácticas de Python: ¿hay una mejor manera de verificar los parámetros del constructor?

class ClassWithThreads(object): 
    def __init__(self, num_threads): 
     try: 
      self.num_threads= int(num_threads) 
      if self.num_threads <= 0: 
       raise ValueError() 
     except ValueError: 
      raise ValueError("invalid thread count") 

Es ésta una buena práctica? ¿Debo simplemente no tomar la molestia de detectar excepciones en la conversión y dejar que se propaguen a la persona que llama, con la posible desventaja de tener mensajes de error menos significativos y consistentes?

+2

si no se trata de una interfaz de usuario, no la toques, y deja que la excepción se propague de forma natural. Estás haciendo más trabajo por ti mismo, y envolver la excepción no agrega mucho valor. – monkut

Respuesta

11

Cuando tengo una pregunta como esta, voy a buscar en la biblioteca estándar un código con el que pueda modelar mi código. multiprocessing/pool.py tiene una clase algo cerca de la suya:

class Pool(object): 

    def __init__(self, processes=None, initializer=None, initargs=(), 
       maxtasksperchild=None): 
     ... 
     if processes is None: 
      try: 
       processes = cpu_count() 
      except NotImplementedError: 
       processes = 1 
     if processes < 1: 
      raise ValueError("Number of processes must be at least 1") 

     if initializer is not None and not hasattr(initializer, '__call__'): 
      raise TypeError('initializer must be a callable') 

en cuenta que no dice

processes = int(processes) 

Simplemente asume que lo envió un entero, no un flotador o una cadena, o lo que sea. Debería ser bastante obvio, pero si crees que no lo es, creo que basta con documentarlo.

Aumenta ValueError si processes < 1, y verifica que initializer, cuando se da, es invocable.

Así, si tomamos multiprocessing.Pool como modelo, su clase debe tener este aspecto:

class ClassWithThreads(object): 
    def __init__(self, num_threads): 
     self.num_threads = num_threads 
     if self.num_threads < 1: 
      raise ValueError('Number of threads must be at least 1') 

No sería este enfoque posiblemente fallar muy impredecible para algunos condiciones?

Pienso tipo preventivo se controle va en contra de la filosofía de diseño de Python (dynamic, pato-mecanografía).

pato escribiendo da Python programadores oportunidades de gran fuerza expresiva, y el desarrollo de código rápido, pero (algunos podrían decir) es peligroso porque no tiene intento de atrapar errores de tipo.

Algunos argumentan que los errores lógicos son mucho más graves y frecuentes que los errores tipo . Necesita pruebas unitarias para detectar los errores más graves. Entonces, incluso si realiza una verificación de tipo preventivo, no agrega mucha protección.

Este debate radica en el ámbito de las opiniones, no en los hechos, por lo que no es un argumento resoluble. En qué lado de la valla está sentado puede depender de su experiencia, su juicio sobre la probabilidad de errores tipo . Puede estar sesgado por los idiomas que ya conoce. Depende de el dominio de su problema.

Solo tienes que decidir por ti mismo.


PS. En un lenguaje estáticamente tipado, las comprobaciones de tipo se pueden realizar en tiempo de compilación, lo que no impide la velocidad del programa. En Python, las comprobaciones de tipo tienen que ocurrir en tiempo de ejecución. Esto ralentizará un poco el programa, y ​​tal vez mucho si la verificación ocurre en un bucle. A medida que el programa crece, también lo hará el número de controles de tipo. Y desafortunadamente, muchos de esos controles pueden ser redundantes. Entonces, si realmente cree que necesita verificación de tipo, probablemente debería utilizar un lenguaje de tipo estático.


PPS. Hay decoradores para verificación de tipos para (Python 2) y (Python 3). Esto separaría el código de verificación de tipo del resto de la función y le permitirá desactivar más fácilmente la verificación de tipos en el futuro si así lo desea.

+0

¿No sería posible que este enfoque fallara de manera impredecible para algunas condiciones? Por ejemplo, pasar una cadena no se tomaría como un error (ya que nunca se compararán como '<1'). Mi intención con las comprobaciones preventivas es tratar de garantizar cierta consistencia en el estado del objeto, detectando errores tan pronto como sea posible en lugar de tener que lanzar una excepción relacionada aleatoriamente más tarde. ¿Es "más pitónico" dejar que las cosas pasen y fracasen más adelante? – danielkza

4

Puede usar un decorador de comprobación de tipo como this activestate recipe o this other one for python 3. Ellos le permiten escribir código de algo como esto:

@require("x", int, float) 
@require("y", float) 
def foo(x, y): 
    return x+y 

que elevará una excepción si los argumentos no son del tipo requerido. Podría extender fácilmente los decoradores para verificar que los argumentos también tengan valores válidos.

+2

Estas recetas de decorador son agradables (y se desactivan fácilmente si ya no las necesita). –

+0

Esta parece ser la solución más elegante hasta ahora, definitivamente la verificare. En una nota no relacionada, ¿activestate.com cuelga Firefox (10.0) para cualquier otra persona? – danielkza

2

Ésta es subjetiva, pero aquí hay un argumento en contra:

>>> obj = ClassWithThreads("potato") 
ValueError: invalid thread count 

espera, ¿qué? Eso debería ser un TypeError. Haría esto:

if not isinstance(num_threads, int): 
    raise TypeError("num_threads must be an integer") 
if num_threads <= 0: 
    raise ValueError("num_threads must be positive") 

Bien, esto viola los principios del "tipado de pato". Pero no usaría el tipado de pato para objetos primitivos como int.

Cuestiones relacionadas