StrongTypedMethods

Verificação de Parâmetros de métodos com metaclasses

Este exemplo, que acbaou ficnado menos simples do que eu gostaria, mostra uma forma de se verificar a compatibilidade de tipos de entrada de um método e dos tipos de saída, usando metaclasses, e eespernado a descrição desses tipos na docstring do método.

Em linhas gerais:

# coding: utf-8

# AUTHOR: João S. O. Bueno (2009)
# Copyright: João S. O. Bueno
# License: LGPL V 3.0


""" the StrongTyped  class is designed to worka s a metaclass
for cases where one could want to raise a type error when methods 
are called with unexpected parameter types, or return
unexpected typed results.

It could provide some confort for testing during development for
people coming from strong typed languages

To use import this module: set StrongTyped as the metaclass for your desired 
class, and fill in the doc string for method as lined in the "soma" 
example bellow

"""

#TODO: implement support for keyword arguments or variable arugmetn lenght
#TODO: Group expected parameter types and return types
# cuyrrently, any match is good - 

def parse_parms(name, doc):
    lines = doc.split("\n")
    parm_types = []
    return_types = []
    for line in lines:
        line = line.strip()
        if line.startswith(name):
            #picks the parenthizesd argumetn type list, 
            # allowing for "#" initiated comments
            types_expr = line.split(name)[1].split("#")[0]
            parm_types.append(eval(types_expr))
        elif line.startswith("->"):
            #return_types
            types_expr = line.split("->")[1].split("#")[0]
            return_types.append(eval(types_expr))
        elif line and not line.startswith("#"):
            # if not a blank or "comment" line, then it
            # is the end of the parameter checking session
            break
    return parm_types, return_types
    
class StrongTypedMethod(object):
    def __init__(self, method, parm_types, return_types):
        self.method = method
        self.parm_types = parm_types
        self.return_types = return_types
        self.owner_object = None
    
    
    def verify_signatures(self, arguments, signatures):
        # verifies if given parametes or result tuple equals one
        # of the registered signatures for the method
        if not isinstance(arguments, tuple):
            arguments = (arguments,)
        for signature in signatures:
            if not isinstance(signature, tuple):
                signature = (signature,)
            if len(signature) != len(arguments):
                continue
            this_signature_ok = True
            for result, expected_type in zip(arguments, signature):
                if not isinstance(result, expected_type):
                    this_signature_ok = False
            if this_signature_ok:
                return True
        return False
    
    # TODO: implement support for keyword arguments
    def __call__(self, *args):
        if self.owner_object is None:
            owner_object = args[0]
            args = args[1:]
        else:
            owner_object = self.owner_object
        if not self.verify_signatures(args, self.parm_types):
            raise TypeError ("""Method %s called with incorrect parameters types.
                                Expected one of:\n %s\n\ngot: \n%s\n""" %
                             (self.method.__name__, str(self.parm_types), str(args))
                            )
        results = self.method(owner_object, *args)
        if not self.verify_signatures(results, self.return_types):
            raise TypeError ("""Method %s returned incorrect  types.
                             Expected one of:\n %s\n\ngot: \n%s\n""" %
                             (self.method.__name__, str(self.return_types), str(results))
                            )
        return results

class NewMethodForStrongTyped(object):
    def __init__(self, method_list, original__new__):
        self.method_list = method_list
        self.original__new__ = original__new__
    def __call__(self, cls, *args, **kwargs):
        if self.original__new__:
            obj = self.original__new__(cls, *args, **kwargs)
        else:
            #warning: "super" didnṫ work here - possible bug could issue from this construct:
            obj = cls.__bases__[0].__new__(cls, *args, **kwargs)
        for method in self.method_list:
            m = getattr(obj, method)
            m.owner_object = obj
        return obj
    

class StrongTyped(type):
    def __new__(cls, name, bases, dct):
        changed_methods = []
        for attr, method in dct.items():
            if not hasattr(method, "__call__"):
                continue
            if not method.__doc__:
                continue
            if attr == "__new__": 
                continue
            parm_types, return_types = parse_parms(attr, method.__doc__)
            if not parm_types and not return_types:
                continue
            dct[attr] = StrongTypedMethod(method, parm_types, return_types)
            changed_methods.append(attr)
        if changed_methods:
            dct["__new__"] = NewMethodForStrongTyped(changed_methods, dct.get("__new__", None))
        return  type.__new__(cls, name, bases, dct)
            
            
    
class Testing(object):
    __metaclass__ = StrongTyped
    def soma(self, a, b):
        """ soma(int, int)
            soma(float, float)
            -> int
            -> float
        """
        return a + b
if __name__ == "__main__":
    t = Testing()
    print t.soma(5, 10)
    print t.soma(5.0 , 10.0)
    try:
        print t.soma("ban", "ana")
    except TypeError, error:
        print "Soma de strings falhou como esperado:\n %s" % error

CookBook

StrongTypedMethods (editada pela última vez em 2009-04-19 23:05:55 por JoaoBueno3)