Metaprogramación con Python 3. Andrey Antukh github.com/niwibe

Documentos relacionados
Entendiendo Decoradores en Python

Introducción a la programación orientada a objetos con Python

Diseño Basado en Componentes. Curso 2008 / 09

Informe de Segunda Presentación

Introducción a. Python. Unlux Facundo Batista. Introducci. Arte gráfico: Diana Batista. ó n a Python Unlux 2007

Clase 5: CLI, templates y excepciones

Sistemas Distribuidos Java RMI (Remote Method Invocation) Alberto Lafuente Mikel Larrea Dpto. ATC, UPV/EHU

PROCESOS DE RAZONAMIENTO INVERSO: PATRÓN DE DISEÑO ADAPTER EN PYTHON Y PHP, LOS

Clases y objetos en python (Programacion Orientada a Objetos)

Tipos de Datos de python (1ª parte):

Tema 2: Introducción a Python

Introducción a PYTHON. Cesar Husillos & Víctor Terrón. Abril de 2014

Bloque IV: Usos avanzados

Curso de Python Inicial

CEFIRE: Curso Administración de APACHE

SHELL SCRIPTING: ANÁLISIS DE ARGUMENTOS

La sintaxis básica para definir una clase es la que a continuación se muestra:

en otra máquina exactamente de la misma manera que si se encontrará en la misma máquina

2.2 Nombres, Ligado y Ámbito

Descripción y Contenido del Curso. Programación C++ Capacity Academy.

El lenguaje de programación Java

Programación Funcional Avanzada

JAVA RMI (REMOTE METHOD INVOCATION)

pytod, un prototipo experimental para realizar Depuración Omnisciente a scripts escritos en el Lenguaje de Programación Python

Herencia. Hay clases que comparten gran parte de sus características.

Clase adicional 2. Estructuras básicas de control. Temas

Estructura de las Aplicaciones Orientadas a Objetos Herencia y Polimorfismo

ASP.NET MVC 3 ofrece una serie de herramientas y funciones para construir una aplicación utilizando sólo la definición de los objetos del modelo.

Tema 5 Diseño con Glade

MICROSITIOS. Perfiles

15. Parámetros o argumentos

Java en 3 horas. Ampliación de Sistemas Operativos. Rodrigo Santamaría

Laboratorio de Aplicaciones Telemáticas

Fundamentos de la Programación Orientada a Objetos Definición de Clases

MANUAL DE RUBY (PARTE V) Luis José Sánchez González

Laboratorio de Diseño de Robots Móviles Practica No. 2 Sistema mínimo del microcontrolador PIC16F877

Python Facundo Batista. Gracias especiales a nessita por su indispensable ayuda con LaTEXpara esta presentación

1. Generación automática de documentación (javadoc)

INDICE DEL CURSO APRENDER PROGRAMACIÓN JAVA DESDE CERO. PROGRAMACIÓN ORIENTADA A OBJETOS (CU00601B)

Conexión SQL Server y C# (Consola)

Tema 7.- Fundamentos de la Programación Orientada a Objetos

Arrays unidimensionales. Dim.Option Base. Erase. Ejemplos en Visual Basic (CU00311A)

Programación orientada a objetos

Tutorial: Python + Soap Web Service. Daniel Montenegro Cordero

LABORATORIO Nº 6 SUMA DE DOS NUMEROS EN POWER BUILDER

Serialización de datos en C# en Binario, Soap y Xml

Algorítmica. Curso 2009/2010. Seminario de Python 3 y El problema del Río Congo

Lección 2: Creando una Aplicación en Java. 1. Estructura del archivo de una clase. 3. Definiendo clases fundamentos

QUÉ ES UNA CLASE JAVA? ATRIBUTOS (PROPIEDADES O CAMPOS), CONSTRUCTOR Y MÉTODOS. (CU00623B)

Tema 4.- Pilas y Colas

Reflection (Reflexión)

Contenidos. Gestión dinámica de memoria. Gestión dinámica de memoria. Introducción. 1. Introducción 2. El operador NEW 3. El operador DELETE

Python hasta en los Rincones

ArcGIS Pro SDK for.net. Fernando Virgili

Las plantillas permiten definir funciones genéricas.

Programación Orientada a Objetos (POO)

Programación Orientada a Objetos con Java. Elementos Básicos del Lenguaje Java. Creación de un objeto. Creación de un objeto. Creación de un objeto

Ficheros y streams. Desde el punto de vista de Java, cada fichero no es más que una secuencia o flujo de bytes [stream].

Generics y Visual Basic.NET

GUIA DE PROCEDIMIENTOS Y FUNCIONES ALMACENADOS EN MYSQL

Prof. Dr. Paul Bustamante

Dentro del.net los eventos se utilizan para notificar a nuestros objetos que se ha producido algún tipo de hecho al que nos hemos suscrito.

Java en 2 horas. Rodrigo Santamaría

INTERFACE COMPARATOR. DIFERENCIAS ENTRE COMPARATOR Y COMPARABLE. CLASE COLLECTIONS. EJERCICIOS RESUELTOS. (CU00918C)

Nano Taller de Python

Álgebra y Matemática Discreta Sesión de Prácticas 1

Práctica 1: sockets en Python

Programación Orientada a Objetos en C#.NET CAPÍTULO 5 H E R E N C I A. Ing. Bruno López Takeyas, M.C.

Introducción al desarrollo de RIA's con Adobe Flex 3.0 Dia 4

b) Qué tipo de variable son las de la línea 6? Cuál es su visibilidad? Explique su

Manual del entorno de integración continua

Introducción a la Computación. Python avanzado. Maximiliano Geier. Facultad de Ciencias Exactas y Naturales, UBA 24/06/2015

Programación Orientada a Objetos en Java

INSTRUMENTACIÓN N AVANZADA. ARRAYS EN LABVIEW Relacionando Datos Arrays y Clusters

El lenguaje de Programación C. Fernando J. Pereda

Clase 4: Modularizando código

Escrito por Renan Huanca Sábado, 28 de Febrero de :46 - Actualizado Sábado, 28 de Febrero de :09

Node.JS: Plataforma de fácil programación de servidores para aplicaciones de red escalables

INGENIERÍA DE SOFTWARE:

Workshop: Behavior Driven Development (BDD) in JavaScript

Tema 3: Herencia en C++ Programación Orientada a Objetos Curso 2008/2009 Begoña Moros Valle

PRÁCTICA DE LABORATORIO 4 Programación Orientada a Objetos

PHP y MySQL. Inicio: - Herencia - Palabra clave Final - Polimorfismo - Type Hinting - Abstracción de clases

Ejecutando SAS desde EXCEL:

1. Sobrecarga de operadores. 2. Métodos operadores unarios Operador de incremento (prefijo)

class Nombre_Clase extends Nombre_SuperClase { cuerpo de la clase extendida }

Programación Orientada a Objetos en JAVA

Conceptos de Programación Orientada a Objetos

Imprimir PDF en WebDynpro para JAVA sin utilizar Interactive Forms en llamadas RFC.

Resumen Lenguaje Java

03.04 Unity. Integración de Sistemas. Parte II. Diseño e implementación de aplicaciones Web con.net

Transcripción:

Metaprogramación con Python 3 Andrey Antukh www.niwi.be @niwibe github.com/niwibe

Las clases Consideramos esta clase como ejemplo: class Spam(object): def init (self, name): self.name = name def say_hello(self): print("hello {0}".format(self.name)) Podemos deducir estos datos: Nombre: Spam Bases: object Metodos: init y say_hello

Como se construye una clase? Como primer paso, se crea un dict donde se van a almacenar los atributos de clase: >>> cls_attrs = type. prepare () >>> type(cls_attrs) <class 'dict'>

Como se construye una clase? Como segundo paso, se extrae el body de la clase, o es decir lo que representa la definicion de los metoros y atributos: >>> body = """ def init (self, name): self.name = name def say_hello(self): print("hello {0}".format(self.name)) """

Como se construye una clase? Como tercer paso, se compila el body extraido y se rellena el contexto de la clase: >>> exec(body, globals(), clsattrs) >>> clsattrs {'say_hello': <function say_hello at 0x7f0b840e5e60>, ' init ': <function init at 0x7f0b840e5dd0>}

Como se construye una clase? Como cuarto paso, se crea un nuevo tipo: >>> Spam = type("spam", (object,), clsattrs) >>> Spam <class ' main.spam'> >>> instance = Spam("Andrey") >>> instance.say_hello() Hello Andrey

Que es metaclase? Metaclase, es la clase responsible de crear clases: class SomeMeta(type): def new (clst, name, bases, attrs): print("somemeta. new ", clst, name, bases, {}) return super(). new (clst, name, bases, attrs) def init (cls, name, bases, attrs): print("somemeta. init ", cls, name, bases, {}) super(). init (name, bases, attrs) def call (cls, *args, **kwargs): print("somemeta. call ", cls, args, kwargs) return super(). call (*args, **kwargs) class A(metaclass=SomeMeta): def new (cls, *args, **kwargs): print("a. new ", cls, args, kwargs) return super(). new (cls) a = A(2) def init (self, *args, **kwargs): print("a. init ", self, args, kwargs)

Que es metaclase? Ejecución en una shell interactiva: ~ python -i p1_meta.py SomeMeta. new <class ' main.somemeta'> A () {} SomeMeta. init <class ' main.a'> A () {} SomeMeta. call <class ' main.a'> (2,) {} A. new <class ' main.a'> (2,) {} A. init < main.a object at 0x7fc5f4b01b10> (2,) {} Que es cada cosa? meta. new : se encarga de crear la metaclase meta. init : se encarga de inicializar la metaclase meta. call : hace que la clase que se crea a partir de esta meta clase sea callable A. new : se encargade crear la instancia A. init : se encarga de inicializar la instancia

Que es metaclase? Metaclase Instancia de? Clase Instancia de? Objecto (instancia)

Constructor generico class Person(object): def init (self, first_name, last_name, birthday, location, zipcode, country, sex): self.first_name = first_name self.last_name = last_name self.birthday = birthday self.location = location self.zipcode = zipcode self.country = country self.sex = sex

Constructor generico Vamos a definir estructuras de datos y queremos eliminar la repeticion de definicion del metodo init.

Constructor generico Primera aproximación: from inspect import Signature, Parameter class Struct(object): _fields = [] def init (self, *args, **kwargs): params = [Parameter(field, Parameter.POSITIONAL_OR_KEYWORD) for field in self._fields] sig = Signature(params) bound_values = sig.bind(*args, **kwargs) for name, value in bound_values.arguments.items(): setattr(self, name, value) class Point(Struct): _fields = ["x", "y"]

Constructor generico Ejemplo de uso en una shell: ~ python -i example3-signatures.py >>> p = Point(2, 5) >>> p.x, p.y (2, 5) >>> p = Point(2, 7, z=2) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "example3-signatures.py", line 12, in init bound_values = sig.bind(*args, **kwargs) File "/usr/lib64/python3.3/inspect.py", line 2036, in bind return bind_self._bind(args, kwargs) File "/usr/lib64/python3.3/inspect.py", line 2027, in _bind raise TypeError('too many keyword arguments') TypeError: too many keyword arguments

Comprobación de tipo de atributos Primera aproximación, usando properties: class Point(Struct): _fields = ["x", "y"] @property def x(self): return self._x @x.setter def x(self, value): if not isinstance(value, int): raise TypeError("unexpected type for x") self._x = value

Comprobación de tipo de atributos Ejemplos en la shell interactiva: >>> Point(2, 3) < main.point object at 0x7f10fd76a150> >>> Point(2.2, 3) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "x4_prop.py", line 16, in init setattr(self, name, value) File "x4_prop.py", line 29, in x raise TypeError("unexpected type for x") TypeError: unexpected type for x

Descriptores Segunda aproximación para comprobar los tipos usando descriptores: class Descriptor(object): def init (self, name=none): self.name = name def get (self, instance, cls): if instance is None: return self return instance. dict [self.name] def set (self, instance, value): instance. dict [self.name] = value def delete (self, instance): del instance. dict [self.name]

Descriptores class TypedDescriptor(Descriptor): _type = None def set (self, instance, value): if not isinstance(value, self._type): raise TypeError("unexpected type for {0}".format(self.name)) super(). set (instance, value)

Descriptores Aplicamos los descriptors a nuestra estructura de ejemplo: class Integer(TypedDescriptor): _type = int class Point(Struct): _fields = ["x", "y"] x = Integer("x") y = Integer("y") Observaciones: _fields sirve para el constructor. Los descriptores reciben repetidamente el nombre del atributo. Obtenemos el mismo comportamiento que con properties.

Descriptores con Metaclases Automatizamos ciertas partes de la creacion de clases de nuestras estructuras en el proceso de su definición (compilación): class MetaStruct(type): def prepare (cls, *args, **kwargs): return collections.ordereddict() def new (clst, name, bases, attrs): params = [] param_type = Parameter.POSITIONAL_OR_KEYWORD for name, attr in attrs.items(): if isinstance(attr, Descriptor): params.append(parameter(name, param_type)) attr.name = name attrs = dict(attrs) attrs[" signature "] = Signature(params) return super(). new (clst, name, bases, attrs)

Descriptores con Metaclases Automatizamos ciertas partes de la creacion de clases de nuestras estructuras en el proceso de su definición (compilación): class MetaStruct(type): def prepare (cls, *args, **kwargs): return collections.ordereddict() def new (clst, name, bases, attrs): params = [] param_type = Parameter.POSITIONAL_OR_KEYWORD for name, attr in attrs.items(): if isinstance(attr, Descriptor): params.append(parameter(name, param_type)) attr.name = name attrs = dict(attrs) attrs[" signature "] = Signature(params) return super(). new (clst, name, bases, attrs)

Descriptores con Metaclases Automatizamos ciertas partes de la creacion de clases de nuestras estructuras en el proceso de su definición (compilación): class MetaStruct(type): def prepare (cls, *args, **kwargs): return collections.ordereddict() def new (clst, name, bases, attrs): params = [] param_type = Parameter.POSITIONAL_OR_KEYWORD for name, attr in attrs.items(): if isinstance(attr, Descriptor): params.append(parameter(name, param_type)) attr.name = name attrs = dict(attrs) attrs[" signature "] = Signature(params) return super(). new (clst, name, bases, attrs)

Descriptores con Metaclases Ahora el constructor de la clase base de estructuras: class Struct(object, metaclass=metastruct): def init (self, *args, **kwargs): bound_values = self. signature.bind(*args, **kwargs) for name, value in bound_values.arguments.items(): setattr(self, name, value)

Descriptores con Metaclases Y así queda la definición final de estructuras, sin atributos innecesarios y sin repetir el nombre para los descriptores.: class Point(Struct): x = Integer() y = Integer()

Como afecta todo esto al rendimiento? Clase simple, con comprobación de tipos en el constructor: python -m timeit -s "import x2_sim as s" "x = s.point(2, 5)" 1000000 loops, best of 3: 1.01 usec per loop Structura usando signaturas genericas y properties: python -m timeit -s "import x4_prop as s" "x = s.point(2, 5)" 10000 loops, best of 3: 61.6 usec per loop Structura usando signaturas genericas y descriptores: python -m timeit -s "import x5_desc as s" "x = s.point(2, 5)" 10000 loops, best of 3: 64.8 usec per loop Structura usando signaturas genericas, descriptores y metaclases: python -m timeit -s "import x6_meta as s" "x = s.point(2, 5)" 10000 loops, best of 3: 38.8 usec per loop

Generación dinamica código Para evitar la constante de resolcion de herencia de los descriptores, intentaremos generar en tiempo de definicion (compilación) el codigo del setter y del constructor: def _make_setter_code(descriptor): code = ["def set (self, instance, value):"] for cls in descriptor. mro : if "code" in cls. dict : for line in cls.code(): code.append(" " + line) return "\n".join(code) def _make_init_code(fields): code = ["def init (self, {0}):\n".format( ", ".join(fields))] for field in fields: code.append(" self.{0} = {0}\n".format(field)) return "".join(code)

Generación dinamica código Esto es lo que hacen las funciones para generar el setter y el constructor: python -i x7_exec.py >>> print(_make_init_code(["x", "y"])) def init (self, x, y): self.x = x self.y = y >>> print(_make_setter_code(point.x. class )) def set (self, instance, value): if not isinstance(value, self._type): raise TypeError('unexpected type') instance. dict [self.name] = value

Generación dinamica código Este es el aspecto que tendiria el nuevo decriptor: class DescriptorMeta(type): def init (cls, name, bases, attrs): if " set " not in attrs: exec(_make_setter_code(cls), globals(), attrs) setattr(cls, " set ", attrs[" set "]) return super(). init (name, bases, attrs) class Descriptor(object, metaclass=descriptormeta): @staticmethod def code(): return ["instance. dict [self.name] = value"] def get (self, instance, cls): if instance is None: return self return instance. dict [self.name]

Generación dinamica código Este es el aspecto que tendiria el nuevo decriptor: class DescriptorMeta(type): def init (cls, name, bases, attrs): if " set " not in attrs: exec(_make_setter_code(cls), globals(), attrs) setattr(cls, " set ", attrs[" set "]) return super(). init (name, bases, attrs) class Descriptor(object, metaclass=descriptormeta): @staticmethod def code(): return ["instance. dict [self.name] = value"] def get (self, instance, cls): if instance is None: return self return instance. dict [self.name]

Generación dinamica código Y este es el aspecto que tendria la clase base de las estructuras: class MetaStruct(type): def prepare (cls, *args, **kwargs): return collections.ordereddict() def new (clst, name, bases, attrs): fields = [k for k,v in attrs.items() if isinstance(v, Descriptor)] if fields: exec(_make_init_code(fields), globals(), attrs) for name in fields: attrs[name].name = name attrs = dict(attrs) return super(). new (clst, name, bases, attrs) class Struct(object, metaclass=metastruct): pass

Generación dinamica código Y este es el aspecto que tendria la clase base de las estructuras: class MetaStruct(type): def prepare (cls, *args, **kwargs): return collections.ordereddict() def new (clst, name, bases, attrs): fields = [k for k,v in attrs.items() if isinstance(v, Descriptor)] if fields: exec(_make_init_code(fields), globals(), attrs) for name in fields: attrs[name].name = name attrs = dict(attrs) return super(). new (clst, name, bases, attrs) class Struct(object, metaclass=metastruct): pass

Generación dinamica código Y este es el aspecto que tendria la clase base de las estructuras: class MetaStruct(type): def prepare (cls, *args, **kwargs): return collections.ordereddict() def new (clst, name, bases, attrs): fields = [k for k,v in attrs.items() if isinstance(v, Descriptor)] if fields: exec(_make_init_code(fields), globals(), attrs) for name in fields: attrs[name].name = name attrs = dict(attrs) return super(). new (clst, name, bases, attrs) class Struct(object, metaclass=metastruct): pass

Hemos mejorado en el rendimiento? Clase simple, con comprobación de tipos en el constructor: python -m timeit -s "import x2_sim as s" "x = s.point(2, 5)" 1000000 loops, best of 3: 1.01 usec per loop Structura usando signaturas genericas y properties: python -m timeit -s "import x4_prop as s" "x = s.point(2, 5)" 10000 loops, best of 3: 61.6 usec per loop Structura usando signaturas genericas y descriptores: python -m timeit -s "import x5_desc as s" "x = s.point(2, 5)" 10000 loops, best of 3: 64.8 usec per loop Structura usando signaturas genericas, descriptores y metaclases: python -m timeit -s "import x6_meta as s" "x = s.point(2, 5)" 10000 loops, best of 3: 38.8 usec per loop Structura usando generación dinamica de codigo: python -m timeit -s "import x7_exec as s" "x = s.point(2, 5)" 100000 loops, best of 3: 2.08 usec per loop

Hemos mejorado en el rendimiento? Clase simple, con comprobación de tipos en el constructor: python -m timeit -s "import x2_sim as s" "x = s.point(2, 5)" 1000000 loops, best of 3: 1.01 usec per loop Structura usando signaturas genericas y properties: python -m timeit -s "import x4_prop as s" "x = s.point(2, 5)" 10000 loops, best of 3: 61.6 usec per loop Structura usando signaturas genericas y descriptores: python -m timeit -s "import x5_desc as s" "x = s.point(2, 5)" 10000 loops, best of 3: 64.8 usec per loop Structura usando signaturas genericas, descriptores y metaclases: python -m timeit -s "import x6_meta as s" "x = s.point(2, 5)" 10000 loops, best of 3: 38.8 usec per loop Structura usando generación dinamica de codigo: python -m timeit -s "import x7_exec as s" "x = s.point(2, 5)" 100000 loops, best of 3: 2.08 usec per loop

Generar codigo a partir de estructuras en json Supongamos que tenemos la siguiente estructura de datos en json: [ ] { }, { } "name": "Foo", "fields": [ {"name": "foo", "type": "Integer"}, {"name": "bar", "type": "Integer"} ] "name": "Person", "fields": [ {"name": "age", "type": "Integer"} ]

Generar codigo a partir de estructuras en json Podemos generar codigo a partir de esa estructura: def _json_struct_to_code(struct): code = ["class {0}(_ts.Struct):".format(struct["name"])] for field in struct["fields"]: c = "{0} = _ts.{1}()".format(field["name"], field["type"]) code.append(" " + c) code.append("\n") return code def _json_to_code(filename): with io.open(filename, "rt") as f: data = json.load(f) code = ["import x7_exec as _ts"] for struct in data: code.extend(_json_struct_to_code(struct)) return "\n".join(code)

Generar codigo a partir de estructuras en json Este es el resultado: ~ python -i x8_json.py >>> print(_json_to_code("jsonstruct.json")) import x7_exec as _ts class Foo(_ts.Struct): foo = _ts.integer() bar = _ts.integer() class Person(_ts.Struct): age = _ts.integer()

Generar codigo a partir de estructuras en json Creamos la clase responsible de crear el modulo: import imp class JsonLoader(object): def init (self, filename): self._filename = filename def load_module(self, fullname): mod = imp.new_module(fullname) mod. file = self._filename mod. loader = self code = _json_to_code(self._filename) exec(code, mod. dict, mod. dict ) sys.modules[fullname] = mod return mod

Generar codigo a partir de estructuras en json Creamos la clase responsible de importar el fichero json: class StructImporter(object): def init (self, path): self._path = path def find_module(self, fullname, path=none): name = fullname.partition(".")[0] if path is None: path = self._path for dir in path: final_name = os.path.join(dir, "{0}.json".format(name)) if os.path.exists(final_name): return JsonLoader(final_name) return None import sys sys.meta_path.append(structimporter(sys.path))

Generar codigo a partir de estructuras en json Y este es el resultado: ~ python -i x8_json.py >>> import jsonstruct as jt >>> jt. file '/home/niwi/niwi-slides/meta-py3/sources/jsonstruct.json' >>> jt.person <class 'jsonstruct.person'> >>> jt.person(2) <jsonstruct.person object at 0x7ffb841b0a90> >>> jt.person(2.2) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 2, in init File "<string>", line 3, in set TypeError: unexpected type

Generar codigo a partir de estructuras en json Y este es el resultado: ~ python -i x8_json.py >>> import jsonstruct as jt >>> jt. file '/home/niwi/niwi-slides/meta-py3/sources/jsonstruct.json' >>> jt.person <class 'jsonstruct.person'> >>> jt.person(2) <jsonstruct.person object at 0x7ffb841b0a90> >>> jt.person(2.2) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 2, in init File "<string>", line 3, in set TypeError: unexpected type

Generar codigo a partir de estructuras en json Y este es el resultado: ~ python -i x8_json.py >>> import jsonstruct as jt >>> jt. file '/home/niwi/niwi-slides/meta-py3/sources/jsonstruct.json' >>> jt.person <class 'jsonstruct.person'> >>> jt.person(2) <jsonstruct.person object at 0x7ffb841b0a90> >>> jt.person(2.2) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<string>", line 2, in init File "<string>", line 3, in set TypeError: unexpected type

Preguntas? https://github.com/niwibe/niwi-slides/tree/master/meta-py3 Andrey Antukh www.niwi.be @niwibe github.com/niwibe