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

Euler 6 (Python)

Enunciado 6

La suma de los cuadrados de los primeros diez números naturales es:

12 + 22 + ... + 102 = 385

El cuadrado de la suma de los primeros diez números naturales es:

(1 + 2 + ... + 10)2 = 552 = 3025

Así que la diferencia entre la suma de los cuadrados de los diez primeros números naturales y el cuadrado de la suma es 3025 − 385 = 2640.

Encontrar la diferencia entre la suma de los cuadrados de los primeros cien números naturales y el cuadrado de la suma.

Solución

La solución fue obtenida utilizando el intérprete interactivo de Python 2.5.2:

>>> def a(n):

...     s = sum(xrange(1,n+1))

...     a1 = s ** 2

...     a2 = sum([x**2 for x in xrange(1,n+1)])

...     return a1, a2, a1-a2

...

>>> a(10)

(3025, 385, 2640)

>>> a(100)

(25502500, 338350, 25164150)

Python tips

  • Con ** podemos elevar un número a cualquier potencia sin necesidad de importar ningún módulo.
  • Para denotar una tupla solo hacen falta las comas (,), no los paréntesis al principio y al final.


Euler 5 (Python)

Enunciado 5

2520 es el menor número que puede ser dividido sin resto por todos los números de 1 a 10. ¿Cuál es el menor número que que puede dividirse sin resto por todos los números de 1 a 20?

Solución

La solución fue obtenida ejecutando el siguiente programa Python 2.5.2:

from math import sqrt

from operator import mul, add



def primo(n):

    for i in xrange(2,int(sqrt(n))+1):

        if n % i == 0:

            return False

    return True



def factorizar(n):

    primos = [x for x in xrange(2,n+1) if primo(x)]

    factores = []

    if n == 1:

        return [1]

    for p in primos:

        if n == p:

                factores.append(p)

                return factores

        if n % p == 0:

                n /= p

                factores.append(p)

                while n % p == 0:

                    n /= p

                    factores.append(p)

    return factores



def mcm(l):

    '''Maximo comun multiplo'''

    ff = [factorizar(i) for i in l]

    singles = list(set(reduce(add,ff)))

    temp = []

    for s in singles:

        temp.extend([s] * max([f.count(s) for f in ff]))

    return reduce(mul,temp,1)



if __name__ == '__main__':

    #print mcm(range(1,11))

    print mcm(range(1,21))

Python tips

  • El módulo operator tiene varias funciones útiles equivalentes a operadores como * y + que pueden utilizarse con funciones como reduce, cuyo primer parámetro es una función.
  • reduce toma una función f de dos argumentos y una lista l. Aplica f a los dos primeros elementos de la lista y obtiene un resultado, luego aplica la función f al resultado con el tercer elemento de la lista, y así sucesivamente hasta consumir toda la lista y retornar el resultado final. Opcionalmente se le puede pasar un tercer parámetro para que la primer llamada a la función f sea aplicada sobre este y el primer elemento de la lista.


Euler 4 (Python)

Enunciado 4

Un número palíndromo se lee igual en ambos sentidos. El mayor palíndromo construido a partir del producto de dos números de dos dígitos es 9009 = 91 × 99.

Encontrar el mayor palíndromo que se puede construir como el producto de dos números de tres dígitos.

Solución

La solución fue obtenida en el intérprete interactivo de Python 3.0rc1+. Necesitaba Python 3 para utilizar itertools.combinations:


juanjo@fenix:~$ python3

Python 3.0rc1+ (py3k, Oct 28 2008, 09:23:29) 

[GCC 4.3.2] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> from itertools import combinations

>>> tri = range(999, 99, -1)

>>> mults = []

>>> for a,b in combinations(tri, 2):

...     s = str(a*b)

...     if s == s[::-1]:

...             mults.append(int(s))

... 

>>> max(mults)

906609

Python tips

  • combinations genera la combinación con los elementos de la lista argumento tomados de a n, si se especifica el parámetro n.
  • En Python 3 print deja de ser una sentencia para convertirse en una función, por lo que siempre debe ser llamado con parámetros.
  • unString[::-1] es un idiom usado para invertir una cadena de texto (en el ejemplo, unString). Se lee: los elementos de unString, desde el principio al final, con paso -1.


Euler 3 (Python)

Enunciado 3

Los factores primos de 13195 son 5, 7, 13 y 29.

¿Cual es el mayor factor primo del número 600851475143?

Solución

La solución fue obtenida en el intérprete interactivo de Python 2.5.2:

>>> from math import sqrt

>>> the_number = 600851475143

>>> my_number = sqrt(the_number)

>>> my_number

775146.09922452678

>>> my_number = int(my_number)

>>> my_number

775146

>>> def esprimo(n):

...     for i in xrange(2,int(sqrt(n))+1):

...             if n % i == 0:

...                     return False

...     return True

...

>>> esprimo(8)

False

>>> esprimo(3)

True

>>> esprimo(6823)

True

>>> esprimo(100109)

True

>>> esprimo(100110)

False

>>> for i in xrange(1, my_number+1):

...      if the_number % i == 0 and esprimo(i):

...             print i

...

1

71

839

1471

6857

Python tips

  • xrange, en lugar de crear una lista entera de números como hace range devuelve un generador que va entregando números a medida que los necesitamos.
  • La clase int se instancia para obtener enteros. Si la llamamos con un float como parámetro, podemos usarla para forzar un entero.


Euler 2 (Python)

Enunciado 2

Cada nuevo item en la secuencia de Fibonacci es generado sumando los dos términos previos. Empezando con 1 y 2, los primeros 10 términos serían:

1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

Encontrar la suma de todos los términos pares en la secuencia que no supera los 4 millones.

Solución

La solución fue obtenida en el intérprete interactivo de Python 2.5.2:

>>> MAX = 4000000

>>> def evsum(n):

...     s = 0

...     a,b = 1,2

...     while True:

...         if a >= n:

...             break

...         if a % 2 ==0:

...             print a, " ",

...             s += a

...         a,b = b,a+b

...     return s

...

>>> evsum(MAX)

2   8   34   144   610   2584   10946   46368   196418   832040   3524578

4613732

Python tips

  • La asignación múltiple es un idom muy cómodo: a,b = 1,2
  • Si agregamos una coma final a la enumeración de objetos a ser impresos con print, se logra el efecto de que no se imprima el salto de línea (\n) final.


Euler 1 (Python)

Project Euler es un sitio web que reta a los programadores a resolver problemas matemáticos mediante código. Me parece entretenido. Voy a ir resolviendo problemas y posteando mi solución en Python acompañada de comentarios sobre el código que puedan servirles a quienes están empezando a aprender el lenguaje.

Enunciado 1

Si listamos todos los números menores a 10 que son múltiplos de 3 o 5, obtenemos 3, 5, 6 y 9. La suma de esos múltiplos es 23.

Encontrar la suma de todos los múltiplos de 3 o 5 menores a 1000.

Solución

La solución fue obtenida en el intérprete interactivo de Python 2.5.2:

>>> def mults(n=10):

...     r = []

...     for i in range(1, n):

...             if i % 3 == 0:

...                     r.append(i)

...             elif i % 5 == 0:

...                     r.append(i)

...     return r

...

>>> mults()

[3, 5, 6, 9]

>>> sum(mults())

23

>>> sum(mults(n=1000))

233168

Python tips

  • En la definición de la función mults se incluye un argumento por defecto n con valor 10. Cuando más adelante se llama a esta función sin parámetros, n toma el valor por defecto.
  • La función range, incluida en el lenguaje devuelve una lista de números sobre la que se puede iterar. Muy útil para usar con la estructura for. Más información haciendo help(range) en el intérprete interactivo.


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 :)


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.


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.


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.