| You are here: Inicio > Inmersión en Python > Objetos y orientación a objetos > Exploración de UserDict: Una clase cápsula | << >> | ||||
Inmersión en PythonPython de novato a experto |
|||||
Como ha podido ver, FileInfo es una clase que actúa como un diccionario. Para explorar esto un poco más, veamos la clase UserDict del módulo UserDict, que es el ancestro de la clase FileInfo . No es nada especial; la clase está escrita en Python y almacenada en un fichero .py, igual que cualquier otro código Python. En particular, está almacenada en el directorio lib de su instalación de Python.
| En el IDE ActivePython en Windows, puede abrir rápidamente cualquier módulo de su ruta de bibliotecas mediante -> (Ctrl-L). | |
class UserDict:def __init__(self, dict=None):
self.data = {}
if dict is not None: self.update(dict)
![]()
![]()
| Observe que UserDict es una clase base, que no hereda de ninguna otra. | |
| Éste es el método __init__ que sustituimos en la clase FileInfo. Advierta que la lista de argumentos en la clase ancestro es diferente que en la descendiente. No pasa nada; cada subclase puede tener su propio juego de argumentos, siempre que llame a su ancestro de la manera correcta. Aquí la clase ancestro tiene una manera de definir valores iniciales (pasando un diccionario en el argumento dict) que no usa FileInfo. | |
| Python admite atributos de datos (llamados “variables de instancia” en Java y Powerbuilder, y “variables miembro” en C++). En este caso, cada instancia de UserDict tendrá un atributo de datos data. Para hacer referencia a este atributo desde código que esté fuera de la clase, debe calificarlo con el nombre de la instancia, instancia.data, de la misma manera que calificaría una función con el nombre de su módulo. Para hacer referencia a atributos de datos desde dentro de la clase, use self como calificador. Por convención, todos los atributos de datos se inicializan en el método __init__ con valores razonables. Sin embargo, esto no es un requisito, ya que los atributos, al igual que las variables locales, comienzan a existir cuando se les asigna su primer valor. | |
| El método update es un duplicador de diccionarios: copia todas las claves y valores de un diccionario dentro de otro. Esto no borra antes los valores previos del diccionario modificado; si el diccionario objetivo ya contenía algunas claves, las que coincidan con el diccionario fuente serán modificadas, pero no se tocará las otras. Piense en update como una función de mezcla, no de copia. | |
| Ésta es una sintaxis que puede no haber visto antes (no la he usado en los ejemplos de este libro). Hay una sentencia if, pero en lugar de un bloque sangrado en la siguiente líena, la sentencia está en una sola línea, tras los dos puntos. Esta sintaxis es perfectamente válida, y es sólo un atajo que puede usar cuando el bloque conste de sólo una sentencia (es como especificar una sola sentencia sin llaves en C++). Puede usar esta sintaxis, o sangrar el código en las siguientes líneas, pero no puede hacer ambas cosas en el mismo bloque. |
| Java y Powerbuilder admiten la sobrecarga de funciones por lista de argumentos, es decir una clase puede tener varios métodos con el mismo nombre, pero con argumentos en distinta cantidad, o de distinto tipo. Otros lenguajes (notablemente PL/SQL) incluso admiten sobrecarga de funciones por nombre de argumento; es decir una clase puede tener varios métodos con el mismo nombre y número de argumentos de incluso el mismo tipo, pero con diferentes nombres de argumento. Python no admite ninguno de estos casos; no hay forma de sobrecarga de funciones. Los métodos se definen sólo por su nombre, y hay un único método por clase con un nombre dado. De manera que si una clase sucesora tiene un método __init__, siempre sustituye al método __init__ de su ancestro, incluso si éste lo define con una lista de argumentos diferentes. Y se aplica lo mismo a cualquier otro método. | |
| Guido, el autor original de Python, explica el reemplazo de métodos así: "Las clases derivadas pueden reemplazar los métodos de sus clases base. Dado que los métodos no tienen privilegios especiales para llamar a otros métodos del mismo objeto, un método de una clase base que llama a otro método definido en la misma clase base, puede en realidad estar llamando a un método de una clase derivada que la reemplaza (para programadores de C++: todos los métodos de Python son virtuales a los efectos)". Si esto no tiene sentido para usted (a mí me confunde sobremanera), ignórelo. Sólo pensaba que debía comentarlo. | |
| Asigne siempre un valor inicial a todos los atributos de datos de una instancia en el método __init__. Le quitará horas de depuración más adelante, en busca de excepciones AttributeError debido a que está haciendo referencia a atributos sin inicializar (y por tanto inexistentes). | |
def clear(self): self.data.clear()
def copy(self):
if self.__class__ is UserDict:
return UserDict(self.data)
import copy
return copy.copy(self)
def keys(self): return self.data.keys()
def items(self): return self.data.items()
def values(self): return self.data.values()
| clear es un método normal de clase; está disponible públicamente para que cualquiera lo invoque en cualquier momento. Observe que clear, igual que todos los métodos de una clase, tiene self como primer argumento (recuerde que no ha de incluir self al invocar el método; Python lo hace por usted). Fíjese también en la técnica básica de esta clase wrapper: almacena un diccionario real (data) como atributo, define todos los métodos que tiene un diccionario real, y cada uno lo redirige al correspondiente en el diccionario (en caso de que lo haya olvidado, el método clear de un diccionario borra todas sus claves y sus valores asociados). | |
| El método copy de un diccionario real devuelve un nuevo diccionario que es un duplicado exacto del original (los mismos pares clave-valor). Pero UserDict no se puede redirigir simplemente a self.data.copy, porque el método devuelve un diccionario real, y lo que queremos es devolver una nueva instancia que sea de la misma clase que self. | |
| Puede usar el atributo __class__ para ver si self es un UserDict; si lo es, perfecto, porque sabemos cómo copiar un UserDict: simplemente creamos un nuevo UserDict y le proporcionamos el diccionario real que mantenemos en self.data. Entonces devolvemos de inmediato el nuevo UserDict sin llegar siquiera al import copy de la siguiente línea. | |
| Si self.__class__ no es un UserDict, entonces self debe ser alguna subclase de UserDict (por ejemplo FileInfo), en cuyo caso la vida se hace un poco más dura. UserDict no sabe cómo hacer una copia exacta de uno de sus descendientes; podría haber, por ejemplo, otros atributos de datos definidos en la subclase, así que necesitaríamos iterar sobre ellos y asegurarnos de que los copiamos todos. Por suerte, Python tiene un módulo que hace exactamente esto, y se llama copy. No entraré en detalles (aunque es un módulo muy interesante, por si alguna vez se siente inclinado a sumergirse en él usted mismo). Baste decir que copy puede copiar objetos arbitrarios de Python, y que para eso lo estamos usando aquí. | |
| El resto de los métodos son triviales, y redireccionan las llamadas a los métodos incorporados en self.data. |
| En las versiones de Python previas a la 2.20, no podía derivar directamente tipos de datos internos como cadenas, listas y diccionarios. Para compensarlo, Python proporcionaba clases encapsulantes que imitaban el comportamiento de estos tipos: UserString, UserList, y UserDict. Usando una combinación de métodos normales y especiales, la clase UserDict hace una excelente imitación de un diccionario. En Python 2.2 y posteriores, puede hacer que una clase herede directamente de tipos incorporados como dict. Muestro esto en los ejemplos que acompañan al libro, en fileinfo_fromdict.py. | |
En Python, puede heredar directamente del tipo incorporado dict, como se muestra en este ejemplo. Hay tres diferencias si lo comparamos con la versión que usa UserDict.
class FileInfo(dict):"store file metadata" def __init__(self, filename=None):
self["name"] = filename
<< Instanciación de clases |
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | |
Métodos de clase especiales >> |