script propias procedimientos por parametros omision metodos lista funciones ejecutar con python typechecking

propias - lista de funciones de python



¿Es Pythonic verificar los tipos de argumento de función? (6)

Además de las ideas ya mencionadas, es posible que desee "forzar" los datos de entrada en un tipo que tenga las operaciones que necesita. Por ejemplo, es posible que desee convertir una tupla de coordenadas en una matriz de Numpy, de modo que pueda realizar operaciones de álgebra lineal en ella. El código de coerción es bastante general:

input_data_coerced = numpy.array(input_data) # Works for any input_data that is a sequence (tuple, list, Numpy array…)

Lo sé, los argumentos de la función de comprobación de tipos generalmente están mal vistos en Python, pero creo que se me ha ocurrido una situación en la que tiene sentido hacerlo.

En mi proyecto tengo una clase de base abstracta Coord , con una subclase Vector , que tiene más características como rotación, cambio de magnitud, etc. Las listas y tuplas de números también devolverán True para isinstance(x, Coord). También tengo muchas funciones y métodos que aceptan estos tipos de Coord como argumentos. He creado decoradores para comprobar los argumentos de estos métodos. Aquí hay una versión simplificada:

class accepts(object): def __init__(self, *types): self.types = types def __call__(self, func): def wrapper(*args): for i in len(args): if not isinstance(args[i], self.types[i]): raise TypeError return func(*args) return wrapper

Esta versión es muy simple, todavía tiene algunos errores. Solo está ahí para ilustrar el punto. Y sería usado como:

@accepts(numbers.Number, numbers.Number) def add(x, y): return x + y

Nota: Solo estoy comparando los tipos de argumentos con Clases base abstractas.

¿Es esta una buena idea? ¿Hay una mejor manera de hacerlo sin tener que repetir un código similar en cada método?

Editar:

¿Qué pasaría si hiciera lo mismo, pero en lugar de revisar los tipos de antemano en el decorador, detecto las excepciones en el decorador?

class accepts(object): def __init__(self, *types): self.types = types def __call__(self, func): def wrapper(*args): try: return func(*args) except TypeError: raise TypeError, message except AttributeError: raise AttributeError, message return wrapper

¿Es eso mejor?


Se ha hablado sobre esto porque Py3k admite una función de anotaciones de las cuales las anotaciones de tipo son una aplicación. También hubo un esfuerzo para rodar la comprobación de tipos en Python2.

Creo que nunca se resolvió porque el problema básico que intentas resolver ("encontrar errores de tipo") es trivial para empezar (ves un TypeError ) o bastante difícil (ligera diferencia en las interfaces de tipo). Además, para hacerlo bien , necesita clases de tipos y clasificar cada tipo en Python. Es mucho trabajo para casi nada. Sin mencionar que estarías haciendo chequeos en tiempo de ejecución todo el tiempo.

Python ya tiene un sistema de tipos fuerte y predecible. Si alguna vez vemos algo más poderoso, espero que venga a través de anotaciones de tipo e IDEs inteligentes.


Si esta es una excepción a la regla, está bien. Pero si la ingeniería / diseño de su proyecto gira en torno a la comprobación de tipos de todas las funciones (o la mayoría de ellas), entonces tal vez no quiera usar Python, ¿qué hay de C #?

Desde mi punto de vista, hacer un decorador para la comprobación de tipos generalmente significa que lo va a utilizar mucho . Entonces, en ese caso, mientras que el código común de factorización en un decorador es pythonic, el hecho de que sea para la comprobación de tipos no es muy pythonic.


Su gusto puede variar, pero el estilo Pythonic (tm) es simplemente seguir adelante y usar los objetos como sea necesario. Si no admiten las operaciones que está intentando, se generará una excepción. Esto se conoce como tipificación de pato .

Existen algunas razones para favorecer este estilo: primero, permite el polimorfismo al permitirle usar nuevos tipos de objetos con el código existente, siempre que los nuevos objetos admitan las operaciones correctas. Segundo, agiliza el camino exitoso al evitar numerosas comprobaciones.

Por supuesto, el mensaje de error que obtiene al usar argumentos incorrectos será más claro con la verificación de tipos que con la tipificación de pato, pero como digo, su gusto puede variar.


Una de las razones por las que se recomienda el uso de Duck Typing en Python es que alguien podría envolver uno de sus objetos, y luego se verá como el tipo incorrecto, pero seguirá funcionando.

Aquí hay un ejemplo de una clase que envuelve un objeto. Un LoggedObject actúa de todas formas como el objeto que envuelve, pero cuando llama al LoggedObject , registra la llamada antes de realizar la llamada.

from somewhere import log from myclass import A class LoggedObject(object): def __init__(self, obj, name=None): if name is None: self.name = str(id(obj)) else: self.name = name self.obj = obj def __call__(self, *args, **kwargs): log("%s: called with %d args" % (self.name, len(args))) return self.obj(*args, **kwargs) a = LoggedObject(A(), name="a") a(1, 2, 3) # calls: log("a: called with 3 args")

Si prueba explícitamente la isinstance(a, A) , fallará, porque a es una instancia de LoggedObject . Si solo dejas que la escritura del pato haga su trabajo, esto funcionará.

Si alguien pasa el tipo incorrecto de objeto por error, se generará una excepción como AttributeError . La excepción podría ser más clara si verifica tipos explícitamente, pero creo que, en general, este caso es una ganancia para la tipificación de pato.

Hay ocasiones en las que realmente necesitas probar el tipo. El que aprendí recientemente es: cuando estás escribiendo código que funciona con secuencias, a veces realmente necesitas saber si tienes una cadena, o es cualquier otro tipo de secuencia. Considera esto:

def llen(arg): try: return max(len(arg), max(llen(x) for x in arg)) except TypeError: # catch error when len() fails return 0 # not a sequence so length is 0

Se supone que esto devuelve la longitud más larga de una secuencia, o cualquier secuencia anidada dentro de ella. Funciona:

lst = [0, 1, [0, 1, 2], [0, 1, 2, 3, 4, 5, 6]] llen(lst) # returns 7

Pero si llama a llen("foo") , se repetirá para siempre hasta que la pila se desborde.

El problema es que las cadenas tienen la propiedad especial de que siempre actúan como una secuencia, incluso cuando se toma el elemento más pequeño de la cadena; una cadena de un carácter sigue siendo una secuencia. Por lo tanto, no podemos escribir llen () sin una prueba explícita para una cadena.

def llen(arg): if isinstance(arg, basestring): # Python 2.x; for 3.x use isinstance(arg, str) return len(arg) try: return max(len(arg), max(llen(x) for x in arg)) except TypeError: # catch error when len() fails return 0 # not a sequence so length is 0


Es.

"Ser Pythonic" no es un concepto bien definido, pero generalmente se entiende como escribir código usando construcciones de lenguaje apropiadas, no es más detallado de lo que es necesario, sigue la guía de estilo de Python (PEP 8) y generalmente se esfuerza por tener un código agradable leer. También tenemos el Zen de Python ( import this ) como guía.

¿ @accepts(...) anotación @accepts(...) en la parte superior de su función ayuda o perjudica la legibilidad? Probablemente ayuda, porque la Regla # 2 dice: "Explicit is better than implicit" . También hay PEP-484 que fue diseñado específicamente para exactamente el mismo propósito.

¿Los tipos de verificación en tiempo de ejecución cuentan como Pythonic? Seguramente, se cobra un peaje en la velocidad de ejecución, pero el objetivo de Python nunca fue producir el código más eficaz posible, todo lo demás, maldito sea. Por supuesto, el código rápido es mejor que lento, pero luego el código legible es mejor que el código spaghetti, el código de mantenimiento es mejor que el código hackish y el código confiable es mejor que el buggy. Entonces, dependiendo del sistema que está escribiendo, puede encontrar que la compensación vale la pena, y el uso de controles de tipo de tiempo de ejecución vale la pena.

En particular, la Regla # 10 "Errors should never pass silently." Puede ser visto como el apoyo de las comprobaciones de tipo extra. Como ejemplo, considere el siguiente caso simple:

class Person: def __init__(self, firstname: str, lastname: str = ""): self.firstname = firstname self.lastname = lastname def __repr__(self) -> str: return self.firstname + " " + self.lastname

¿Qué sucede cuando lo llamas así: p = Person("John Smith".split()) ? Bueno, nada al principio. (Esto ya es problemático: se creó un objeto Person no válido, pero este error se ha superado). Luego, algún tiempo después, tratas de ver a la persona y

>>> print(p) TypeError: can only concatenate tuple (not "str") to tuple

Si JUSTO ha creado el objeto, y si tiene experiencia como programador de Python, entonces descubrirá qué es lo que está mal con bastante rapidez. Pero ¿y si no? El mensaje de error es inútil en el límite (es decir, necesita conocer los aspectos internos de la clase Person para hacer uso de él). ¿Y qué sucede si no vio este objeto en particular, sino que lo guardó en un archivo, que se envió a otro departamento y se cargó pocos meses después? Cuando el error se identifique y se corrija, es posible que su trabajo ya esté en problemas ...

Dicho esto, no tienes que escribir tú mismo los decoradores de comprobación de tipos. Ya existen módulos específicamente para este propósito, por ejemplo.