Ir al contenido principal

This is my blog, more about me at marianoguerra.github.io

🦋 @marianoguerra.org 🐘 @marianoguerra@hachyderm.io 🐦 @warianoguerra

Sobre tipos y lenguajes de scripting

me llego un call for papers para este workshop http://www.cs.purdue.edu/homes/wrigstad/stop09/STOP/About.html

y se me ocurrió lo que se puede hacer con lenguajes de scripting para los "locos por los tipos"

acá va el código y después la explicación

ensure.py: provee los decoradores para asegurar tipos en parámetros y valor de retorno

import functools

ensure_types = True

def args_types(*args_types, **kwargs_types):
def wrapper(function):
@functools.wraps(function)
def replacement(*args, **kwargs):
for (count, (arg, arg_type)) in enumerate(zip(args, args_types)):
if type(arg) != arg_type:
raise ValueError('Invalid type for arg ' + str(count) +
' expected ' + str(arg_type) + ' got ' + str(type(arg)))

for name, ktype in kwargs_types.iteritems():
if name in kwargs:
if type(kwargs[name]) != ktype:
raise ValueError('Invalid type for karg "' + name +
'" expected ' + str(ktype) + ' got ' + str(type(kwargs[name])))

return function(*args, **kwargs)

if ensure_types:
replacement._is_wrapped = True
return replacement
else:
function._is_wrapped = False
return function

return wrapper

def return_type(return_type):
def wrapper(function):
@functools.wraps(function)
def replacement(*args, **kwargs):
result = function(*args, **kwargs)

if type(result) != return_type:
raise ValueError('Invalid return type expected ' +
str(return_type) + ' got ' + str(type(result)))

return result

if ensure_types:
replacement._is_wrapped = True
return replacement
else:
function._is_wrapped = False
return function

return wrapper


test_ensure.py: prueba la implementación
import ensure
# if ensure.ensure_types is set to False then the decorators
# return the same functions and don't make the type checking
# that an be done when development finished and you want to
# deliver the code (it remove the checks and make the code faster)
# ensure.ensure_types = False

@ensure.args_types(int, str, bool, float, sure=bool, price=float)

def do_something(number, name, sure=False, price=10.0):

print number, name, sure, price

@ensure.return_type(int)

def return_param(param):
return param

if do_something._is_wrapped:

print 'do_something contains type checks'
else:
print 'do_something doesn\'t contains type checks'

if return_param._is_wrapped:

print 'return_param contains type checks'
else:
print 'return_param doesn\'t contains type checks'

print do_something.__name__

print

do_something(1, "spongebob", True)
do_something(1, "spongebob", sure=False)

do_something(1, "spongebob", sure=False, price=2.0)

# Fail
try:
do_something(False, "spongebob")
except ValueError, error:

print error

try:
do_something(1, 12.3)

except ValueError, error:
print error

try:

do_something(1, "patricio", 10)
except ValueError, error:

print error

try:
do_something(1, "patricio", True, 10)

except ValueError, error:
print error

try:

do_something(1, "patricio", sure=None)
except ValueError, error:

print error

try:
do_something(1, "patricio", sure=True, price=None)

except ValueError, error:
print error

print 'returned', return_param(5)

try:
print 'returned', return_param("hi!")
except ValueError, error:

print error


la salida corriendo con ensure.ensure_types = True es:

do_something contains type checks
return_param contains type checks
do_something

1 spongebob True 10.0
1 spongebob False 10.0
1 spongebob False 2.0
Invalid type for arg 0 expected <type 'int'> got <type 'bool'>
Invalid type for arg 1 expected <type 'str'> got <type 'float'>
Invalid type for arg 2 expected <type 'bool'> got <type 'int'>
Invalid type for arg 3 expected <type 'float'> got <type 'int'>
Invalid type for karg "sure" expected <type 'bool'> got <type 'NoneType'>
Invalid type for karg "price" expected <type 'float'> got <type 'NoneType'>
returned 5
returned Invalid return type expected <type 'int'> got <type 'str'>

la salida corriendo con ensure.ensure_types = False es:

do_something doesn't contains type checks
return_param doesn't contains type checks
do_something

1 spongebob True 10.0
1 spongebob False 10.0
1 spongebob False 2.0
False spongebob False 10.0
1 12.3 False 10.0
1 patricio 10 10.0
1 patricio True 10
1 patricio None 10.0
1 patricio True None
returned 5
returned hi!

Conclusión:

con esta librería se le podría dar a los que lo quieran (no se si yo lo usaría mucho) una forma de checkear en tiempo de ejecución los tipos de datos recibidos como parámetros y los tipos devueltos por las funciones que decidan decorar.
Estas decoraciones sirven también como método para dar hints a los programadores sobre de que tipos son los valores que se reciben en la función y puede ser usado por IDEs para deducir los tipos y mostrar errores/warnings en el cuerpo del código.
Un aspecto positivo es que se puede deshabilitar el decorado seteando ensure.ensure_types = False. En tal caso no se incurre en ningún overhead en tiempo de ejecución, esto podría ser usado luego del ciclo de desarrollo y prueba, donde no se detecto ninguna "violacion de contrato" y se entrega al usuario final el código con los checks en tiempo de ejecución deshabilitados para incrementar la performance de la aplicación.

obviamente se pueden hacer mejoras en el decorador de los parámetros, pero para ser un modulo de 50 lineas y escrito en menos de una hora, esta bastante bien :)