Kitabı oku: «El gran libro de Python», sayfa 6

Yazı tipi:

Objetos mutables e inmutables

Ante dos etiquetas a y b que hacen referencia al mismo objeto, ¿qué ocurre si hacemos una asignación a una de las dos? Espero que nadie diga que será modificado el objeto al cual hacen referencia. Ya hemos dicho que no es posible modificar un objeto con una asignación. Lo que ocurre es que la etiqueta hará referencia al nuevo objeto que se le asignará. Consideremos el siguiente ejemplo:


Las dos etiquetas a y b se refieren al mismo objeto, como se muestra en la Figura 1.6.


Figura 1.6 - Las etiquetas a y b se refieren al mismo objeto, que tiene la identidad 3074330092.

Si ahora realizamos la asignación a = [1, 2, 3, 4], el objeto con identidad 3074330092 no será modificado:


De hecho, ha sido creado un nuevo objeto, que representa la lista [1, 2, 3, 4], que ha sido asignado a la etiqueta a, mientras que la etiqueta b continua haciendo referencia al mismo objeto que antes, como se muestra en la Figura 1.7.


Figura 1.7 - La etiqueta a se refiere a un nuevo objeto, mientras que b continua refiriéndose al objeto anterior.

Llegados a este punto, quizás nos hayamos preguntado lo siguiente: "¿cómo podemos modificar un objeto si no es posible hacerlo con una asignación ni implícita ni explícita?”. La respuesta nos permitirá entender mejor la diferencia entre objetos mutables e inmutables.

Modificación de objetos mutables

Los objetos mutables pueden ser modificados solo de tres maneras: mediante sus métodos, mediante las asignaciones aumentadas y mediante la instrucción del. Por ejemplo, los métodos list.append() y list.sort() de una lista modifican el objeto:


Las asignaciones aumentadas, como se especifica en la PEP-0203, también modifican el objeto:



Por último, la instrucción del, utilizada en un contexto de indexación o fragmentación, permite eliminar los elementos de un objeto contenedor, modificando así dicho objeto:


Si, en cambio, se aplica una simple etiqueta, solo tiene el efecto de eliminarla del namespace:


Cuando un objeto ya no tiene etiquetas que se refieran a él, Python libera la memoria que ocupaba:


NOTA

Los objetos en CPython tienen internamente dos campos: un descriptor de tipo, utilizado para determinar el tipo del objeto, y un contador de referencias, utilizado para saber si el objeto ya no tiene etiquetas que le hagan referencia y, por tanto, si puede ser eliminado de la memoria. Para más detalles: http:// docs.python.org/py3k/c-api/intro.html.

Vamos a aclarar este concepto con ilustraciones. Inicialmente han sido creadas dos etiquetas a y b que hacían referencia al mismo objeto, como muestra la Figura 1.8.


Figura 1.8 - Las dos etiquetas a y b hacen referencia al mismo objeto.

La instrucción del a ha cancelado la etiqueta a, pero la memoria no ha sido liberada por el objeto puesto que todavía había una etiqueta que hacia referencia a él, como se muestra en la Figura 1.9.


Figura 1.9 - La instrucción del a ha eliminado la etiqueta a, pero no el objeto al cual se refería.

En cambio, cuando ha sido cancelada también la etiqueta b, el objeto ya no tenía más etiquetas que le hicieran referencia, por lo que, siendo inutilizable, Python ha ejecutado el método _ _del_ _() y después ha liberado la memoria que ocupaba el objeto.

Inmutabilidad

Los objetos inmutables no tienen métodos que permitan su modificación, incluso ni las asignaciones aumentadas pueden hacerlo:


No existe ningún modo de modificarlos. Por ejemplo, en la siguiente cadena:


si quisiéramos ampliarla, no podríamos hacerlo. Solo podríamos crear un nuevo objeto y asignarlo a la etiqueta s:



Objetos inmutables que contienen objetos mutables

Consideremos ahora el caso de un objeto inmutable que contiene objetos mutables, como el de una tupla que contiene un conjunto y una lista:


Los elementos de un objeto contenedor son simplemente referencias a los correspondientes objetos, como se muestra en la Figura 1.10.


Figura 1.10 - Objeto inmutable que contiene objetos mutables.

La inmutabilidad consiste en no poder modificar las referencias de la tupla, es decir, en no poder efectuar una asignación del siguiente tipo:


Esto significa que no es posible que t[0] haga referencia a un objeto distinto al conjunto con identidad 3072619996. Pero esto no quiere decir que el objeto al cual t[0] se refiere no pueda ser modificado:


El resultado de t[0].add('c') y t[1].append(4) se muestra en la Figura 1.11, en la cual se puede ver cómo los objetos a los cuales t[0] y t[1] se refiere han sido modificados.


Figura 1.11 - Los objetos mutables contenidos en un objeto inmutable pueden ser modificados.

Los atributos son etiquetas

Cuando utilizamos la notación foo.attributo, tanto foo como attributo son etiquetas, cada una de las cuales se refiere al correspondiente objeto:


Así, si hablamos del atributo path del módulo sys, con el término “atributo” entendemos “etiqueta”. El atributo, siendo un sinónimo de etiqueta, hace referencia a un objeto, que en el caso de sys.path es una lista:


Etiquetas y nombres

En la sección Los elementos del código Python hemos dicho que, para crear y representar fácilmente las instancias de los tipos de datos básicos, han sido definidas ciertas formas textuales simbólicas, denominadas literales. Estas formas permiten determinar visualmente tanto el tipo como el valor de las instancias:



Para el resto de tipos de objeto el lenguaje no define una representación literal, también porque a menudo no es posible hacerlo, dado que el concepto de valor no es inmediato como para las instancias de los tipos de datos básicos. Para estos sí es necesario definir atributos que permitan distinguir el objeto respecto a otros objetos del mismo tipo. El elemento de distinción más insignificante en que podemos pensar es el que utilizamos en el día a día para distinguir a las personas entre ellas: el nombre. Para los objetos como las clases, las funciones y los métodos, el nombre se asigna al atributo _ _name_ _ en el momento de su creación, sobre la base del nombre de la etiqueta utilizada para hacer referencia al objeto (para las funciones y las clases) o del nombre del archivo (para los módulos):


Si un objeto tiene un atributo designado para representar el nombre, decimos que el nombre del objeto es la cadena a la cual hace referencia este atributo. Este, cuando existe, no es siempre _ _name_ _. Con los archivos, por ejemplo, se utiliza un atributo más explícito (sin guión bajo), el atributo name, el cual hace referencia a la cadena pasada a open() como primer argumento:


El nombre de una etiqueta es la cadena construida sobre la base de la etiqueta: por ejemplo, el nombre de la etiqueta foo es la cadena 'foo' y el del atributo path del módulo os es la cadena 'path'. La función integrada dir(), cuando se llama sin argumentos, devuelve una lista que contiene los nombres de las etiquetas en el ámbito actual; si no es así, al pasarle un objeto, como hemos visto en secciones anteriores, esta devuelve los nombres de los atributos más significativos de dicho objeto:


Así, el módulo os tiene el atributo path y el nombre de este atributo es la cadena 'path'. El nombre del objeto al cual hace referencia el atributo path de os, en los sistemas Unix-like, es la cadena 'posixpath':


NOTA

En la última sección de este capítulo hablaremos del módulo os, y veremos que en los sistemas Windows os.path hace referencia al módulo ntpath:


Un objeto no siempre tiene un nombre (las instancias de los tipos de datos básicos, por ejemplo, no lo tienen), mientras que una etiqueta sí, porque este lo da la etiqueta en forma de cadena. El nombre de un objeto, por tanto, no tiene nada que ver con el de las etiquetas que a él se refieren, porque, si lo pensamos bien, más de una etiqueta puede hacer referencia al mismo objeto. El único vínculo entre los dos es que, quizás (como para las clases, las funciones y los módulos), el nombre de la etiqueta se utiliza en el momento de la creación del objeto para asignarle el nombre:



Los nombres de las etiquetas tienen distintos contextos de uso. Por ejemplo, permiten recuperar los objetos a los cuales las etiquetas hacen referencia, utilizando los nombres como clave de diccionarios concretos que representan los espacios de nombres (namespace). Aunque hablaremos de ello con mayor profundidad en el Capítulo 4, vamos a mostrar un breve ejemplo, para exponer un caso práctico de uso. Para ello, consideremos la función integrada globals(), la cual devuelve los espacios de nombres global en forma de diccionario. Este último tiene como claves los nombres de las etiquetas globales, y como valores, los correspondientes objetos:


Dicho esto, cuando no exista ninguna posibilidad de error, hablaremos de la etiqueta pero refiriéndonos al objeto. Por ejemplo, en el caso siguiente:


en lugar de decir que "hemos sumado los objetos a los cuales se refieren a y b", diremos simplemente que hemos sumado a y b. Lo mismo es válido para los atributos, para los cuales diremos en el siguiente ejemplo que "hemos añadido un elemento al atributo a de Foo", en lugar de "hemos añadido un elemento al objeto al cual se refiere el atributo a de Foo":


En todos los casos que puedan dar lugar a malentendidos seremos más formales.

Tipos de errores

Los errores en un programa Python se manifiestan en forma de errores de sintaxis o de excepciones.

Errores de sintaxis

Como puede deducirse, los errores de sintaxis aparecen cuando una instrucción no se escribe correctamente. Por ejemplo, la omisión de los dos puntos al final de la instrucción for es un error de sintaxis:


Los errores de sintaxis se señalan con un mensaje que aparece en la línea de código en la cual se ha detectado el error, además de una pequeña flecha que indica el elemento siguiente al punto en que se ha detectado. En el ejemplo anterior, el error ha sido detectado antes de print(i) y, de hecho, inmediatamente después de range(10) faltan los dos puntos necesarios para terminar la instrucción for. También se muestran el nombre del archivo y el número de línea para así facilitar la identificación del error:


Los errores de sintaxis se señalan antes de que el programa sea ejecutado, puesto que se generan durante la fase de compilación en bytecode:


Las excepciones

Las excepciones se generan cuando una instrucción, aunque sintácticamente correcta, da lugar a errores durante su ejecución. Consideremos, por ejemplo, el archivo errors.py:


Hay un error tipográfico (hemos escrito prin en lugar de print), el cual será identificado solo cuando el flujo de ejecución llegue a esta instrucción, puesto que solo en dicho momento Python buscará la definición de la etiqueta prin. Así, el error se generará únicamente si el usuario escribe el carácter a:


Como se puede ver en este ejemplo, cuando las excepciones no son generadas por el programa, la ejecución termina y se muestra un mensaje de error. Este mensaje se distingue de los derivados de errores de sintaxis principalmente por la presencia del traceback.

NOTA

Las exepciones también pueden mostrarse a propósito, mediante la instrucción raise. Consideremos, por ejemplo, el archivo siguiente:


Esto es lo que ocurre cuando lo ejecutamos:


Hablaremos con mayor profundidad de la instrucción raise en el Capítulo 5.

Puesto que las excepciones son generadas solo cuando el fragmento de código que contiene el error es ejecutado, es posible que un programa funcione casi siempre. Si hubiéramos ejecutado el archivo errors.py efectuando 100 bucles, sin escribir nunca el carácter a, posiblemente hubiéramos estado seguros de que el programa no contenía error alguno. En el caso de que ocurran este tipo de problemas, nos podría ser útil utilizar alguna herramienta que analice el código Python con el fin de detectar posibles errores. Entre estos, citamos pyflakes y pylint. He aquí un ejemplo de uso de este último:


La gestión de las excepciones

En esta sección presentamos el mecanismo de gestión de las excepciones. Para ello, consideremos el archivo myfile.py:


Si lo ejecutamos y escribimos una cadena de texto que no puede ser convertida a entero, la función integrada int() detecta una excepción de tipo ValueError:


Podemos gestionar la excepción insertando entre las palabras clave try y except la línea lógica que da lugar al error. Este se gestionará en el bloque de código que sigue a la palabra clave except:



La palabra clave try es una instrucción compuesta y except es una cláusula única. Cuando son ejecutadas las instrucciones de la suite del try, si no surge ningún error, la ejecución pasa directamente de la última instrucción del bloque try a la instrucción siguiente a la try/except, omitiendo por tanto la suite de la except. En cambio, si una línea lógica en la suite del try detecta una excepción, la ejecución desde dicha línea pasa directamente a la cláusula except. Esta comprueba que el tipo de la excepción detectada corresponda al que se trata de gestionar, en cuyo caso se ejecuta su suite para poder gestionar el error; en caso contrario, la suite except se omite.

En nuestro caso, la except gestiona solo las excepciones de tipo ValueError. Por tanto, si escribimos un número que genera un IndexError cuando se intenta indexar la lista, esta excepción no será gestionada:


Si se desean gestionar los dos tipos de excepciones en la misma cláusula except, es posible insertar los tipos ValueError e IndexError en una tupla, del modo siguiente:



También es posible separar la gestión de los distintos tipos de excepción en diferentes cláusulas except:


Acabamos esta sección diciendo que, si en la cláusula except no se especifica ningún tipo de excepción, se capturarán las excepciones de todos los tipos:



No debemos utilizar esta modalidad solo por pereza, porque, si lo que hace nuestro código no está absolutamente claro, hay muchas probabilidades de que perdamos el tiempo en vez de ganarlo. Y esto porque en la except no se muestra ningún mensaje de error, por lo que, si se comprobaran los errores que no tenemos previstos, no conseguiríamos entender los motivos del mal funcionamiento. Además, estos errores imprevistos serían gestionados del mismo modo que aquellos que sí hemos previsto gestionar en la except, aunque quizás habrían debido ser gestionados de manera separada. Esto en el mejor de los casos, porque, por la ley de Murphy, nuestra pereza normalmente será la causa de errores lógicos difíciles de detectar y localizar.

En el Capítulo 5 hablaremos en detalle de las excepciones y, entre otras cosas, veremos como el mecanismo de la herencia nos permite capturar de manera apropiada todas las excepciones, dejando que se propaguen aquellas que no representan errores.

Objetos iterables, iteradores y contexto de iteración

Los argumentos tratados en esta sección pueden no resultar del todo claros, por lo que si lo encontramos difícil, no nos desesperemos: podremos regresar a dichos argumentos en otra ocasión, una vez hayamos adquirido un poco más de soltura con el lenguaje.

El protocolo de iteración

El protocolo de iteración se describe en la PEP-0234. Este define el comportamiento que debe tener un objeto contenedor para que puedar iterarse en él, por ejemplo, en un bucle for, para obtener sus elementos uno a uno. El protocolo se basa en un objeto denominado iterador, el cual dispone de los siguientes métodos:

• iterator._ _iter_ _(): es un método del objeto que devuelve una referencia al mismo objeto;

• iterator._ _next_ _(): es un método que devuelve el elemento siguiente del contenedor, o bien detecta una excepción de tipo StopIteration si los elementos del contenedor han sido todos devueltos.

Un objeto contenedor obj se considera iterable si es posible acceder a sus elementos mediante indexación, o bien si cuenta con un método obj._ _iter_ _() que devuelve un iterador.

Según estas definiciones, un iterador es un objeto iterable, mientras que un objeto iterable no se considera que sea un iterador. Por ejemplo, un conjunto no es un iterador porque no tiene el método set._ _next_ _():


Veamos si es un objeto iterable. En primer lugar, comprobamos que tenga un método set._ _iter_ _():


A continuación, debemos comprobar que este método devuelva un iterador. Si así fuera, el conjunto sería un objeto iterable. Vamos allá:


Ahora falta una condición para poder afirmar que obj es un interador. Su método obj._ _next_ _() debe devolver a cada llamada el elemento siguiente del contenedor y generar una excepción del tipo StopIteration cuando los elementos han terminado:


En definitiva, las instancias del tipo set son objetos iterables porque tienen un método set._ _iter_ _() que devuelve un iterador, pero no son iteradores porque no tienen el método set._ _next_ _().

Las funciones integradas iter() y next() también permiten iterar manualmente sobre un objeto iterable:


Una carácterística importante de los iteradores es que quedan vinculados al objeto iterable:


Profundizaremos en este argumento en el Capítulo 6.

Las clases integradas range y enumerate

En el Capítulo 2 trataremos las funciones integradas vinculadas a los objetos iterables; de momento nos limitamos a hablar únicamente de las clases range y enumerate. La clase range tiene un argumento stop obligatorio y dos argumentos start y step opcionales:


Devuelve un objeto iterable que contiene una secuencia de números progresivos. Cuando se utiliza con el argumento simple stop, el objeto contiene los números del 0 al stop exclusive:



Los argumentos opcionales start y step permiten indicar el número de inicio y el intervalo entre un número y el siguiente:


La clase enumerate toma como argumento un objeto iterable y devuelve un iterador que itera sobre tuplas. Cada tupla está formada por un índice progresivo y por un elemento del objeto iterable. Cuando el objeto iterable es una secuencia seq, el índice progresivo corresponde al índice del elemento en la secuencia, por lo que las tuplas serán (idx, seq[idx]):


Acabamos esta parte demostrando una importante diferencia entre iteradores y objetos iterables que no son iteradores. Mientras que para un objeto iterable que no es un iterador es posible utilizar diferentes iteradores sobre él de manera independiente, esto no es posible para un objeto iterador, puesto que el método iteratore._ _iter_ _() devuelve una referencia a sí mismo:


₺548,57

Türler ve etiketler

Yaş sınırı:
0+
Hacim:
1892 s. 2471 illüstrasyon
ISBN:
9788426729057
Yayıncı:
Telif hakkı:
Bookwire
İndirme biçimi:
Metin
Ortalama puan 0, 0 oylamaya göre
Ses
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 4,8, 5 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre
Metin
Ortalama puan 0, 0 oylamaya göre