Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Budín de remolacha y nuez

Ingredientes

60 g de manteca.

150 g de miel.

2 huevos.

125 g de remolacha cruda rallada.

100 g de nueces picadas.

300 g de harina leudante.

C/n de leche.

Preparación

Batir la manteca con la miel y agregar los huevos de a uno. Incorporar la remolacha y las nueces. Incorporar el harina tamizada y si la preparación queda muy dura, un poco de leche.

Colocar en molde enmantecado u enharinado o rociado con rocío vegetal. Hornear 18 grados 40 min.

[gallery]



URLs elegantes con Django

Este texto es parte del informe de nuestro proyecto final de carrera, lo publico en forma separada aquí por que tiene valor propio y puede convencer a más de un programador PHP de probar Django. Las URLs elegantes son la forma natural de las URL en Django.

Manejo de URLs

Tener URLs elegantes y limpias es un requisito común para aplicaciones web modernas. Django provee un mecanismo de manejo de URLs basado en expresiones regulares que asocia una expresión regular a una vista.

Para diseñar las URLs de una aplicación Django, se construye una especie de tabla que mapea patrones de URL a funciones Python a ejecutar (vistas). Con esto se logra que las URLs estén desacopladas del resto de la aplicación.

El siguiente es un ejemplo de una entrada de esa tabla:

(r'^index/$', index)

Suponiendo que la aplicación web está corriendo en el servidor con nombre localhost, cuando alguien acceda a http://localhost/index/ se ejecutará la vista ‘index’.

El siguiente es un ejemplo un poco más complejo:

(r'^cliente/datos/(\d+)/$', cliente_datos)

En el patrón de la URL se utilizan paréntesis para capturar una parte de la misma y poder accederla luego como parámetros en la vista. La expresión encerrada entre paréntesis se denomina grupo y son propias de las expresiones regulares en Python. Así, siempre que se acceda a, por ejemplo, http://localhost/cliente/datos/1/ o http://localhost/cliente/datos/100/ se ejecutará la vista cliente_datos y recibirá como parámetro el número correspondiente.

Algo similar sucede en el siguiente ejemplo:

(r'^inmueble/fotos/(\d+)/eliminar/(\d+)/$', eliminar_foto)

Con la diferencia de que ahora la vista recibe dos parámetros, en este caso particular el primero corresponde a un identificador de inmueble y el segundo a un identificador de foto.

Las expresiones regulares en Python soportan también lo que se denomina grupos nombrados. Esto permite obtener un grupo por su nombre. Si escribimos una especificación de URL como la siguiente:

(r'^inmueble/fotos/(?P<inmueble>\d+)/eliminar/(?P<foto>\d+)/$', eliminar_foto)

la vista será llamada utilizando parámetros nombrados. Esto tiene la ventaja de que si cambia el orden de los parámetros en la URL, la vista seguirá funcionando sin que se necesite redefinirla.


Intercambio de valores rápido en Python

Cuando empecé a cursar Ingeniería en Sistemas en el año 2003, tuvimos una materia llamada Algoritmos y Estructuras de Datos. La semana del curso estaba compuesta por una clase teórica, una clase práctica y una clase "especial" dictada por un docente de apellido Marina que tenía como objetivo hacernos pensar resolviendo problemas; en las primeras clases ni siquiera programábamos.

El lenguaje de programación de la materia era C y en una de las clases, este docente recordaba risueño que un alumno había querido intercambiar el valor de dos variables

int a = 1;

int b = 2;

haciendo:

a = b;

b = a;

El error es evidente; en a se copia el valor contenido en b (2) pisando el valor original (1) y al ejecutarse la segunda sentencia, el nuevo valor de a (2) es copiado en b.

La siguiente tabla muestra los valores que van tomando las variables a y b: La forma correcta de intercambiar los valores habría sido utilizando una variable auxiliar en la cual mantener uno de los valores:

int aux;

int a = 1;

int b = 2;

aux = a;

a = b;

b = aux;

La siguiente tabla muestra los valores que van tomando las variables aux, a y b:

Lo gracioso del asunto es que unos años más tarde conocí otro lenguaje de programación, Python.

En Python un tipo de dato que viene con el lenguaje es la tupla. Una tupla es una secuencia (sus elementos tienen orden) inmutable (no se puede cambiar su tamaño o contenido) que puede tener dentro objetos de distinto tipo. Un ejemplo de tupla en Python (contiene tres números y dos cadenas de texto):

(1, 2, "tres", 4, "Juan")

La forma de apuntar a ese objeto desde una variable es simplemente:

a = (1, 2, "tres", 4, "Juan")

Aunque podemos obviar los paréntesis y de todas formas funcionará. Decimos que la tupla es empaquetada:

a = 1, 2, "tres", 4, "Juan"

De forma similar, podemos desempaquetar la tupla en nuevas variables:

b, c, d, e, f = a

La condición es que el número de variables en el lado izquierdo del operador = coincida con el número de elementos en la tupla.

La siguiente sentencia, empaqueta y desempaqueta:

x, y, z = "Juan", 100, 1

Y es equivalente a:

x = "Juan"

y = 100

z = 1

Finalmente, esta propiedad del lenguaje nos permite intercambiar rápidamente los valores de 2 (o n) variables:

a = 1

b = 2

a, b = b, a

Así, lo que un alumno despistado quiso hacer en 2 sentencias y Marina mostró que se hacía correctamente en 3, yo lo hago en 1 :)


Budín de pera

Las siguientes son las instrucciones para preparar un budín de pera. Las fotos y las recetas, como en tantas otras, me las proporcionó mi hermana.

Ingredientes

  • Harina leudante 300 g
  • Azúcar 200 g
  • Manteca 150 g
  • Huevos 3
  • Peras 2
  • Limón 1

Preparación

Batir la manteca blanda con el azúcar. Agregar los huevos de a uno. Incorporar el jugo de un limón y su ralladura. Colocar una pera cortada en cubos, agregar el harina leudante tamizada.

Volcar la preparación en un molde de budín (nº 5) enmantecado y enharinado.

Colocar rodajas de pera, espolvorear con azúcar y hornear.

Imágenes

[gallery]


La historia de Python: Cómo todo se convirtió en sentencias ejecutables

El siguiente texto es una traducción del artículo How Everything Became an Executable Statement de Guido van Rossum publicado en http://python-history.blogspot.com/.

Cómo todo se convirtió en sentencias ejecutables

Los nuevos usuarios de Python a veces se sorprenden al descubrir que todas las partes del lenguaje son sentencias ejecutables, incluyendo la definición de funciones y clases. Eso significa que cualquier sentencia puede aparecer en cualquier lugar en un programa. Por ejemplo, una definición de una función puede aparecer dentro de una sentencia "if" si así se lo desea.

En una versión muy temprana de la gramática de Python esto no era así: los elementos de la gramática tenían un "sabor decorativo", las sentencias import y la definición de funciones solo eran permitidas en el nivel superior de un módulo o script (dónde eran ejecutadas para efectivizarse).

De todas formas, cuando estaba agregando soporte para clases, decidí que esto era muy restrictivo.

Mi razonamiento fue más o menos como sigue. En lugar de definir el cuerpo de una clase sólo como una serie de declaraciones de funciones, también parecía adecuado permitir asignaciones a variables allí. De todas formas, si iba a permitir eso, ¿por qué no ir un escalón más arriba y permitir código ejecutable arbitrario? O, llevando esto aún más lejos, ¿por qué no permitir declaración de funciones dentro de sentencias "if", por ejemplo? Rápidamente se vio que esto permitía una simplificación de la gramática, ya que ahora todos los usos de sentencias (estén identados o no) podían compartir la misma regla de gramática, y de hecho el compilador podría usar la misma función generadora de byte code para todas ellas.

A pesar de que este razonamiento me permitía simplificar la gramática y los usuarios podían colocar sentencias Python en cualquier lugar, esta característica no habilitaba necesariamente ciertos estilos de programación. Por ejemplo, la gramática de Python técnicamente permitía a los usuarios escribir cosas como funciones anidadas aunque la semántica subyacente de Python no aceptara ámbitos anidados. Por lo tanto, el código así operaría de formas inesperadas o "rotas" comparadas con lenguajes que realmente estaban diseñados con esa característica en mente. Con el paso del tiempo, muchas de esas características "rotas" se arreglaron. Por ejemplo, la definición de funciones anidadas sólo empezó a funcionar un poco más correcta en Python 2.1.

Traducido por Juan José Conti.

Revisado por César Portela.

Si encontrás errores en esta traducción, por favor reportalos en un comentario y los corregiremos a la brevedad.

Todas las traducciones de esta serie pueden encontrarse en La historia de Python.


Cookies de avena y pasas

Estas son unas masitas de avena y pasas (también pueden hacerse de avena y miel).

Ingredientes

  • Manteca 100 g
  • Huevo 1
  • Azúcar negra 1/4 taza
  • Miel 2 cucharadas
  • Avena 1 taza
  • Harina integral 1 taza
  • Esencia de vainilla c/n

Preparación

Batir la manteca pomada con el huevo y esencia de vainilla. Dividir en dos bols. Al primero agregar azúcar negra, ir revolviendo e incorporar avena y las pasas de uvas y harina integral en cantidad necesaria hasta que tome una consistencia que se puedan formar las masitas.

En el segundo, agregar miel luego también avena y harina integral.

Tomar un poco de masa con una cuchara y formar las cookies sobre una placa enmantecada y hornear de 10 a 15 minutos.


La historia de Python: Todo de primera clase

El siguiente texto es una traducción del artículo First-class Everything de Guido van Rossum publicado en http://python-history.blogspot.com/.

Todo de primera clase

Uno de mis objetivos para Python era hacerlo de tal forma que todos los objetos sean de "primera clase". Con esto me refiero a que quería que todos los objetos puedan ser nombrados en el lenguaje (por ejemplo, enteros, strings, funciones, clases, módulos, métodos, etc.) para tener igual status. Entonces pueden ser asignados a variables, ubicados en listas, almacenados en diccionarios, pasados como argumentos y más.

La implementación interna de Python hizo que esto sea fácil de hacer. Todos los objetos de Python estaban basados en una estructura de datos de C común que se usaba en todos los lugares del intérprete. Variables, listas, funciones y todo lo demás usaba variaciones de esta estructura de datos; directamente no importaba si la estructura representaba un objeto simple como un entero o algo más complicado como una clase.

Aunque la idea de tener "todo de primera clase" es conceptualmente simple, había aún un aspecto de las clases que necesitaba resolver; el problema de hacer que los métodos sean objetos de primera clase.

Consideremos esta clase simple en Python (copiada de la entrada de la semana pasada):

class A:

    def __init__(self,x):

        self.x = x

    def spam(self,y):

        print self.x, y

Si los métodos van a ser objetos de primera clase, entonces pueden ser asignados a otras variables y usados como cualquier otro objeto en Python. Por ejemplo, alguien podría escribir una sentencia en Python como "s = A.spam". En este caso la variable "s" referencia un método de una clase, que en realidad es solo una función. Sin embargo, un método no es exactamente igual a una función. En concreto, se supone que el primer argumento de un método es una instancia de la clase en la que el método fue definido.

Para tratar esto cree un tipo de objeto invocable (callable) conocido como "unbound method". Un unbound method era en realidad un wrapper delgado alrededor de un objeto función que implementaba un método, pero forzaba la restricción de que el primer argumento tenía que ser una instancia de la clase en la cual el método fue definido. Así, si alguien quería llamar al unbound method "s" como una función, tendrían que pasar una instancia de la clase "A" como primer argumento. Por ejemplo, "a = A(); s(a)".(*)

Un problema relacionado ocurre si alguien escribe una sentencia Python que refiere al método en una instancia específica de un objeto. Por ejemplo, alguien puede crear una instancia usando "a = A()" y luego escribir una sentencia como "s = a.spam". Aquí la variable "s" nuevamente referencia al método de una clase, pero la referencia a ese método se obtuvo a través de la instancia "a". Para manejar esta situación se usa un objeto invocable diferente llamado "bound method". Este objeto es también un wrapper delgado alrededor del objeto función para el método. Sin embargo, este envoltorio implícitamente almacena la instancia original que fue usada para obtener el método. Así, una sentencia futura como "s()" llamará al método implícitamente con la instancia "a" como el primer argumento.

En realidad el mismo objeto interno es usado para representar los bound y unbound methods. Uno de los atributos de este objeto contiene una referencia a una instancia. Si es None, el método es unbound. De otro modo, el método es bound.

A pesar de que bound y unbound methods parezcan un detalle sin importancia, son una parte crítica de como las clases funcionan bajo el tapete. Siempre que una sentencia como "a.spam()" aparece en un programa, la ejecución de la sentencia ocurre en dos partes. Primero ocurre la búsqueda de "a.spam". Esto retorna un bound method; un objeto invocable. Luego, una operación de llamado de función "()" es aplicada a ese objeto para invocar el método con los argumentos provistos por el usuario.

(*) En Python 3000, el concepto de unbound methods se eliminó y la expresión "A.spam" retorna un objeto función normal. Nos dimos cuenta de que la restricción de que el primer argumento sea una instancia de A ayudaba pocas veces al diagnosticar problemas y frecuentemente era un obstáculo para usos avanzados; alguien lo llamó "duck typing self", el cual parece un nombre apropiado.

Traducido por Juan José Conti.

Revisado por César Portela.

Si encontrás errores en esta traducción, por favor reportalos en un comentario y los corregiremos a la brevedad.

Todas las traducciones de esta serie pueden encontrarse en La historia de Python.


Tortitas negras

Como habíamos prometido, la receta de hoy es de Tortitas negras (y con esto se nos termina el paquete de levadura, por lo que cerramos la serie de recetas de panadería). Al igual que en la receta anterior, Marisú cocinó con una mano y con la otra sacaba fotos :D.

Ingredientes para la cobertura

  • Fermento levadura fresca 50g
  • Azúcar 1 cucharada
  • Agua tibia 2 cucharadas
  • Harina 2 cucharadas
  • Para la masa:

Ingredientes para la masa

  • Harina 0000 1 Kg
  • Azúcar 80 g
  • Agua o leche 450 g
  • Sal 20 g
  • Manteca 100 g
  • Azúcar negra C/N

Preparación

Formar el fermento. Agregar a la harina, la sal, el azúcar, la manteca, el fermento y la leche:

Formar una masa lisa. Dejar levar:

Desgasificar:

Estirar la masa con medio cm de espesor. Enmantecar y enharinar una placa:

Cortar círculos de 2 cm de diámetro. Pintar con agua y azúcar impalpable. Dejar levar:

Colocar azúcar negro por encima de cada tortita:

Hornear:

¡Poner la pava para los mates y disfrutar!


La historia de Python: Clases definidas por los usuarios

El siguiente texto es una traducción del artículo Adding Support for User-defined Classes de Guido van Rossum publicado en http://python-history.blogspot.com/.

Añadir clases definidas por los usuarios

Crease o no, las clases fueron un añadido tardío durante el primer año del desarrollo de Python, todavía en el CWI, aunque bastante antes de la primera versión pública. En cualquier caso, para entender como se añadieron las clases, ayuda saber un poco más sobre los detalles de implementación de Python.

Python está escrito en C en forma de un intérprete de código intermedio o pseudo-binario (bytecode), usando la clásica estructura de pila, junto con una colección de tipos primitivos, también implementados en C. La arquitectura subyacente usa "objetos", pero como C no soporta objetos directamente, se implementan usando estructuras de objetos y punteros a funciones. La máquina virtual Python define docenas de operaciones estándar que cada objeto debe o puede implementar (por ejemplo, get_attribute, add y call).

Un objeto se representa mediante una estructura estática que contiene una serie de punteros a funciones, uno para cada operación estándar. Estos punteros son inicializados normalmente con referencias a funciones estáticas. Pero algunas operaciones son opcionales y un objeto puede dejar esas entradas apuntando a NULL si decide no implementar la función. En este caso, la máquina virtual o bien genera un error en tiempo de ejecución o, en determinadas circunstancias, puede que proporcione una implementación por defecto de la operación. La estructura C contiene también varios campos de datos, uno de los cuales es una referencia a la lista de métodos adicionales que son únicos para ese tipo de datos, representada como una matriz de estructuras que constan de un texto (el nombre del método) y un puntero a una función (la implementación). El enfoque a la introspección de

Python deriva de esta habilidad de hacer que la propia estructura del tipo sea accesible en tiempo de ejecución, como cualquier otro objeto.

Un aspecto importante de esta implementación es que está completamente centrada en el lenguaje C. De hecho, todas las operaciones y los métodos estándar están implementados por funciones en C. En un principio, el interprete de bytecode solo soportaba llamadas a funciones escritas en Python puro y funciones o métodos implementados en C. Creo que fue mi colega Siebren van der Zee el primero en sugerir que Python debería permitir definiciones de clases similares a las de C++, que permitieran al programador crear objetos propios.

Para poder implementar estos objetos de usuario, me ceñí al diseño más simple que pude imaginar: un esquema donde los objetos de usuario se representarían por nuevos objetos que almacenarían una referencia de clase que apuntaría a un "objeto clase" compartido por todas las instancias de la misma clase, y un diccionario, bautizado "diccionario de instancia", que contendría las variables particulares de cada instancia.

En esta implementación, el diccionario de la instancia contendría los valores de las variables de cada instancia, mientras que el objeto clase contendría la información que fuera compartida entre todas las instancias de la misma clase, especialmente, los métodos. Al implementar la clase objeto opté de nuevo por el diseño más sencillo posible; el conjunto de métodos de la clase se almacenaría en un diccionario, cuyas claves serían los nombres de los métodos, con lo que se creó el diccionario de la clase. Para implementar la herencia, los objetos clase almacenarían opcionalmente una referencia a los objetos clase correspondientes a las clases base. En esa época era bastante ingenuo en lo que se refería a las clases, pero sabía que existía la herencia múltiple, que C++ había incorporado recientemente. Decidí que si iba a implementar la herencia, bien podría implementar una versión simplificada de la herencia múltiple, de forma que una clase pudiera derivar de más de una clase base.

En esta implementación, los mecanismos subyacentes que gestionaban los objetos eran en realidad muy simples. Cualquier cambio hecho a las variables, ya sea de clase o de instancia, se verían reflejados en el objeto diccionario respectivo.

Por ejemplo, asignar un valor a una variable de una instancia actualizaría su diccionario local. De igual forma, cuando buscáramos el valor de una variable de instancia de un objeto, simplemente miramos en el diccionario subyacente. Si la variable no se encuentra allí, las cosas se ponen un poco más interesantes. En ese caso, las búsquedas deben realizarse en el diccionario asociado a la clase, y si tampoco se encontrara allí, en los diccionarios de cada clase de la que derive.

Es más habitual ver este mecanismo de búsqueda de atributos en la clase del objeto, así como en sus clases antecesoras, en el caso de la búsqueda de métodos. Como se ha mencionado anteriormente, los métodos se almacenan en el diccionario de la clase, por lo que son compartidos por todas las instancias de objetos pertenecientes a dicha clase. Así, cuando se invoca un método, lo normal es que no lo encuentres en el diccionario local del objeto. En vez de eso, se busca el método en la clase del objeto, y de no encontrarse, su busca sistemáticamente por todas las clases de las que deriva hasta encontrarlo. Cada una de las clases básicas implementa el mismo algoritmo recursivo. Esto se conoce habitualmente como la regla de primero en profundidad, luego de derecha a izquierda, y ha sido el método de ordenación y selección de métodos (MRO - Method Resolution Order) usado por Python en la mayoría de sus versiones.

Las versiones más modernas han adoptado un MRO más sofisticado, que se discutirá en un futuro artículo de esta serie.

Al implementar las clases, uno de mis objetivos fue mantener las cosas sencillas. Así, Python no realiza comprobaciones de errores ni comprueba inconsistencias a la hora de localizar métodos. Por ejemplo, si una clase sobreescribe un método definido en una clase antecesora, no se realiza ninguna comprobación para verificar que el método redefinido tenga el mismo número de argumentos, ni que puede ser llamada de la misma manera que el método original. El algoritmo de resolución y localización de métodos se limita a devolver el primer método que encuentre, y lo ejecuta con cualesquiera argumentos que haya indicado el usuario.

A partir de este diseño emergieron otras características. Por ejemplo, aunque el diccionario de clase se pensó inicialmente como un repositorio de métodos, no existía ninguna razón que le impidiera contener también otros tipos de objetos.

Así, objetos como números enteros o cadenas de texto podían ser almacenados en el diccionario de la clase, lo que los convertía a todos los efectos en variables de clase; variables que son compartidas por todas las instancias de una determinada clase, en vez de estar almacenadas localmente.

Aunque la implementación era sencilla, también proporcionaba un alto grado de flexibilidad. Por ejemplo, la implementación hacía que las propias clases fueran objetos, en pie de igualdad con cualquier otro objeto (objetos de primera clase, o first-class objects, como se les suele describir en la documentación), lo que significaba que podían ser inspeccionadas de forma introspectiva en tiempo de ejecución, e incluso ser modificadas inámicamente. Se podían añadir o modificar métodos simplemente actualizando el diccionario de la clase, una vez que la clase hubiera sido creada (*). La naturaleza dinámica de Python significaba que esos cambios tendrían un efecto inmediato en todas las instancias de esa clase o de sus clases derivadas. De igual manera, se podía modificar dinámicamente objetos individuales añadiendo, modificando o borrando variables de instancia (una característica que, como comprendí posteriormente, hacía que la implementación de clases y objetos de Python fuera más permisiva que la de Smalltalk, que restringía el conjunto de atributos a aquellos especificados en el momento de la creación).

Desarrollo de la sintaxis de clases

Habiendo diseñado las representaciones en tiempo de ejecución para las clases definidas por el usuario, mi siguiente tarea era diseñar la sintaxis para las definiciones de clases, y en particular, para las definiciones de métodos dentro de la clase. Había una restricción fuerte y era que yo no quería que la sintaxis para definir métodos fuera distinta de la sintaxis para definir funciones.

Reconstruir la gramática y el generador de bytecode para manejar estos dos casos tan similares de forma diferente fue una tarea ardua. Aun así, aunque conseguí mantener la gramática igual, aún tenía que encontrar la manera de tratar con las variables de instancia. Inicialmente había esperado emular las variables de instancia implícitas que podemos ver, por ejemplo, en C++. En ese lenguaje, las clases se definen con un código como el siguiente:


    class A {

    public:

       int x;

       void spam(int y) {

            printf("%d %d\n", x, y);

       }

    };

En esta clase se ha declarado la variable de instancia x. En los métodos, las referencias a x se refieren implícitamente a la variable de instancia.

Por ejemplo, en el método spam(), no se declara la variable x ni como parámetro, ni como variable local, pero como la clase ha declarado una variable de instancia del mismo nombre, se asume que las referencias a x se refieren a dicha variable. Aunque deseaba proporcionar a Python algo similar, pronto me di cuenta de que esta aproximación sería imposible, ya que, en un lenguaje que carece de declaración de variables, no habría una manera elegante de distinguir las variables de instancia de las variables locales.

En teoría, obtener el valor de las variables de instancia debería ser bastante fácil. Python ya disponía de un orden de búsqueda predefinido para nombres de variables no cualificados: locales, globales e internas (built-ins).

Cada una de estas áreas estaba representada por un diccionario que mapeaba los nombres de las variables con sus valores. Cada referencia a una variable se convertía, así, en una serie de búsquedas en diccionarios que concluía cuando se encontrada el nombre de la variable. Por ejemplo, durante la ejecución de una función con una variable local p y una variable global q, en una sentencia como, por ejemplo, print p, q buscaría p en el primer diccionario, el de las variables locales, y lo encontraría. Luego buscaría q en ese mismo diccionario y no lo encontraría, por lo que continuaría la búsqueda por el segundo diccionario, el de las variables globales, hasta encontrarlo.

Habría sido muy fácil añadir el diccionario de instancia del objeto actual al principio de esta lista de diccionarios a la hora de ejecutar un método. De esa forma, en un método de un objeto con una variable de instancia x y una variable local y, una sentencia como print x,y encontraría x en el diccionario de la instancia (el primer diccionario según la nueva ordenación), e y en el diccionario de variables locales (el segundo

diccionario).

El problema con esta estrategia es que fracasa al intentar declarar los valores de las variables de instancia. La asignación en Python no busca el nombre de la variable en los diccionarios, sino que se limita a añadir o reemplazar la variable en el primer diccionario de la lista, normalmente el de variables locales. Esto provoca que las variables siempre se creen en el ámbito local, si no se especifica nada (aunque hay que hacer notar que existe una “declaración global" que invalida este comportamiento para una variable dentro de una función).

Si no cambiamos esta aproximación minimalista a la asignación, el que el diccionario de la instancia fuera el primero en la lista de búsqueda haría

imposible asignar valores a las variables locales dentro de un método. Porejemplo, si tuviéramos un método así:


    def spam(y):

        x = 1       

        y = 2       

Las asignaciones a x e y sobreescribirían el valor de la variable de instacia x y crearían una nueva variable de instancia y, que impediría acceder al valor de la variable local y. Cambiar el orden de los diccionarios (pasar el de instacia al segundo lugar y que el diccionario

local se convirtiera en el primero) simplemente la daría la vuelta al problema, haciendo imposible realizar asignaciones a variables de instancia.

Tampoco funcionaría cambiar la semántica de las asignaciones para usar una variable de instancia, si existe alguna, o usar una variable local en caso contrario, porque esto nos crearía un problema de auto-referencias: ¿cómo crearíamos una variable de instancia, en primer lugar? Una posible solución podría ser obligar a declarar explícitamente las variables de instancia, de forma similar a la usada para declarar variables globales, pero no quería añadir una característica como esta, habiendo llegado tan lejos como había llegado sin requerir ninguna declaración de variables. Además, la especificación extra para indicar una variable global era un caso especial que apenas se usaba en la mayoría del código. La declaración explícita de variables de instancia, por otro lado, tendría que ser usada en prácticamente cualquier definición de clase. Otra posible solución era distinguir lexicamente las variables de instancia. Por ejemplo, usando un símbolo especial como el caracter @ (una aproximación tomada por ruby) o usando alguna convención de nombres que implicara prefijos o un uso particular de mayúsculas y minúsculas. Ninguna de estas opciones me agradaba (y sigue sin hacerlo).

En vez de esto, decidí abandonar la idea de referencias implícitas a las variables de instancia. Los lenguajes como C++ permiten escribir cosas como this->foo, para señalar explícitamente que la variable foo es de instancia, distinguiéndola así de una posible variable local foo. Decidí,

por tanto, hacer que la única manera de acceder a las variables de instancia fueran estas referencias explícitas. Además, tomé la decisión de que this, la variable que representaba al objeto actual, no fuera una palabra clave, simplemente haría que this (o su equivalente) fuera un primer argumento de cada método. Las variables de instancia sería siempre atributos de ese argumento.

Usando referencias explícitas, no había ninguna necesidad de tener una sintaxis especial para la definición de métodos, ni tenía uno que complicarse con semánticas adicionales para la búsqueda de variables. En vez de eso, simplemente se definía una función, sabiendo que el primer argumento correspondería con el objeto instanciado. Por convención, se suele dar a este primer argumento el nombre de self. Por ejemplo:


    def spam(self,y):

        print self.x, y

Esta aproximación recuerda algo a Modula-3, que ya me había proporcionado la sintaxis para las importaciones y para el manejo de excepciones. Modula-3 no tenía clases, pero permitía definir tipos estructurados que podían contener punteros a funciones, que eran inicializadas por defecto con funciones definidas previamente y añadía azúcar sintáctico para que, si x era una estructura de ese tipo y m un puntero a una función almacenada en dicho registro, inicializado a una función f, entonces llamar a x.m(args) equivalía a llamar a f(x, args). Esto se ajusta a la implementación de objetos y métodos, y hace posible equiparar las variables de instancia con atributos del primer argumento.

El resto de los detalles de la sintaxis de Python para clases se derivan de este diseño o de las demás restricciones impuestas por la implementación. Siguiendo con mis aspiraciones de sencillez, imaginaba la sentencia class como una serie de definiciones de métodos, que son sintácticamente iguales a las definiciones de funciones, aun cuando se estableciera por convención que todas deberían tener un primer argumento llamado self. Además, en vez de desarrollar una nueva sintaxis para los métodos especiales (como los constructores y los destructores), tomé la decisión de que estos casos se resolverían obligando al usuario a utilizar nombres especiales, como init, del y demás. Esta convención de nombres se tomó del lenguaje C, en el que los identificadores que empezaban con el caracter guión bajo estaban reservados para el compilador y tenían, a menudo, significados especiales (por ejemplo, macros como FILE en el preprocesador de C).

Así, la visión que tenía del código para definir una clase era esta:


    class A:

         def __init__(self,x):

             self.x = x

         def spam(self,y):

            print self.x, y




También quería seguir reutilizando la máxima cantidad posible de código.

Normalmente, una definición de una función es una sentencia ejecutable que, simplemente, realiza una asignación; asigna a una variable, en el espacio de nombres local, el objeto función (el nombre de la variable será, por tanto, el nombre de la función). Se me ocurrió que, en vez de inventar una solución distinta, era razonable hacer la misma interpretación para las definiciones de métodos dentro del cuerpo de la clase, simplemente usando como espacio de

nombres un nuevo diccionario. Este nuevo diccionario sería entonces tratado y usado para inicializar el diccionario de la clase, creando de esa forma una nueva clase. Detrás de escena, la estrategia que se implementó fue convertir el cuerpo de la clase en una función anónima, que ejecutaba todas las sentencias de definición de métodos que encontrara en el cuerpo de la clase, y que terminaba devolviendo un diccionario con todas las variables/métodos definidas. Este diccionario se pasaba a una función auxiliar, que creaba la clase en sí. Finalmente, el objeto que definía la propia clase se almacenaba en una variable en el entorno local, siendo su nombre el mismo que el de la clase.

Los usuarios de Python a menudo se sorprenden al comprender que cualquier sentencia válida de Python puede aparecer en el cuerpo de una clase. Esta característica era en realidad una extensión de mi deseo de mantener la sintaxis lo más limpia posible, a la vez que trataba de no limitar artificialmente aquellas cosas que pudieran resultar útiles.

Un detalle final acerca de la sintaxis usada para instanciar objetos de una clase. Otros lenguajes, como C++ o Java, usan para crear objetos un operador

especial, new. En C++ esta opción es defendible, porque los nombres de las clases tienen un estatus especial para el analizador, pero en Python eso no era así. Como el analizador de Python no se preocupa en absoluto por el tipo de objeto que esta llamando, hacer que la propia clase fuera ejecutable era la solución correcta, "mínima" en el sentido de que no requería una nueva sintaxis.

Creo que me adelanté un poco a los tiempos aquí; a día de hoy, el “patrón de diseño Factory” es a menudo el sistema más empleado para la creación de instancias y lo que yo hice fue simplemente convertir cada clase en su propia fábrica (Factory).

Métodos especiales

Como decía en la última sección, uno de los objetivos que perseguía era que la implementación de las clases fuera sencilla. En los demás lenguajes orientados a objetos, normalmente existe una diversidad de métodos y operadores especiales que sólo se aplican a las clases. Por ejemplo, en C++, hay una sintaxis especial para definir constructores y destructores, diferente de la usada para definir funciones o métodos normales.

En realidad, no quería introducir una nueva sintaxis para manejar las operaciones especiales con los objetos. Así que me las arreglé para mapear los

operadores específicos con un conjunto de nombres especiales de métodos, como init y del. Los usuarios podrían definir su propio código asociado a la creación y destrucción de objetos, simplemente definiendo métodos con estos nombres especiales.

Usé la misma técnica para permitir a los usuarios redefinir el comportamiento de los operadores de Python. Como ya se ha dicho, Python está escrito en C y usa tablas que contienen punteros a funciones para implementar diferentes capacidades de los objetos internos (por ejemplo, get attribute, add y call). Para permitir que el usuario pudiera definir estas mismas capacidades en sus clases, mapeé los punteros a diferentes funciones con nombres especiales como getattr, add y call.

Existe una correspondencia directa entre estos nombres y las tablas de punteros de funciones que uno tiene que definir cuando se implemente un nuevo tipo de objeto en C.

(*) Eventualmente, el nuevo estilo de clases hace que sea necesario controlar los cambios en el dict de la clase; aún se puede modificar dinámicamente las clases, pero se debe utilizar asignación de atributos en lugar de la variable dict directamente.

Traducido por Juan I. Rodriguez.

Revisado por Juan José Conti y César Portela.

Si encontrás errores en esta traducción, por favor reportalos en un comentario y los corregiremos a la brevedad.

Todas las traducciones de esta serie pueden encontrarse en La historia de Python.