En las
partes anteriores, vimos cortes, desempaque / empaquetando colecciones, y algunas características de operaciones y tipos booleanos.
Los
comentarios mencionaron la posibilidad de multiplicar colecciones por un escalar:
a = [0] * 3 s = 'a' * 2 print(a, s)
Un desarrollador de Python más o menos experimentado sabe que carece de un mecanismo de
copia al escribir a = [0] b = a b[0] = 1 print(a, b)
Entonces, ¿qué generará el siguiente código?
b = a * 2 b[0] = 2 print(a, b)
Python en este caso funciona según el principio de menor sorpresa: en la variable a tenemos una unidad, es decir, b podría declararse y cómo
b = [1] * 2
El comportamiento en este caso será el mismo:
b = a * 2 b[0] = 2 print(a, b)
Pero no es tan simple, python copia el contenido de la lista tantas veces como lo multiplicó y construye una nueva lista. Y en las listas se almacenan los enlaces a los valores, y si, al copiar referencias a tipos inmutables de esta manera, todo está bien, pero con mutable se ve el efecto de la ausencia de copia durante la escritura.
row = [0] * 2 matrix = [row] * 2 print(matrix)
Lista de generadores y numpy para ayudarte en este caso.
Las listas se pueden agregar e incluso aumentar, y cualquier iterador puede estar a la derecha:
a = [0] a += (1,) a += {2} a += "ab" a += {1: 2} print(a)
Pregunta con un truco (para una entrevista): en Python, ¿los parámetros se pasan por referencia o por valor?
def inc(a): a += 1 return a a = 5 print(inc(a)) print(a)
Como vemos, los cambios en el valor en el cuerpo de la función no cambiaron el valor del objeto fuera de él, de esto podemos concluir que los parámetros se pasan por valor y escribir el siguiente código:
def appended(a): a += [1] return a a = [5] print(appended(a))
Lenguajes como C ++ tienen variables almacenadas en la pila y en la memoria dinámica. Cuando se llama a la función, colocamos todos los argumentos en la pila, después de lo cual transferimos el control a la función. Ella conoce los tamaños y las compensaciones de las variables en la pila, por lo que puede interpretarlas correctamente.
Al mismo tiempo, tenemos dos opciones: copiar la memoria de la variable en la pila o poner una referencia al objeto en la memoria dinámica (o en niveles más altos de la pila).
Obviamente, al cambiar los valores en la pila de funciones, los valores en la memoria dinámica no cambiarán, pero al cambiar el área de memoria por referencia, modificamos la memoria compartida, por lo que todos los enlaces a la misma área de memoria "verán" el nuevo valor.
En python, abandonaron un mecanismo similar, el reemplazo es el mecanismo de asignación del nombre de la variable con el objeto, por ejemplo, al crear una variable:
var = "john"
El intérprete crea el objeto "juan" y el "nombre" var, y luego asocia el objeto con el nombre dado.
Cuando se llama a una función, no se crean objetos nuevos; en su lugar, se crea un nombre en su ámbito que está asociado con un objeto existente.
Pero Python tiene tipos mutables e inmutables. El segundo, por ejemplo, incluye números: durante las operaciones aritméticas, los objetos existentes no cambian, pero se crea un nuevo objeto, con el cual se asocia el nombre existente. Si, después de esto, no se asocia un solo nombre con el objeto antiguo, se eliminará utilizando el mecanismo de conteo de enlaces.
Si el nombre está asociado con una variable de tipo variable, entonces, durante las operaciones con él, la memoria del objeto cambia; en consecuencia, todos los nombres asociados con esta área de memoria "ven" los cambios.
Puede leer sobre esto en la
documentación , descrita con más detalle
aquí .
Otro ejemplo:
a = [1, 2, 3, 4, 5] def rev(l): l.reverse() return l l = a print(a, l)
Pero, ¿qué pasa si decidimos cambiar la variable fuera de la función? En este caso, el modificador global nos ayudará a:
def change(): global a a += 1 a = 5 change() print(a)
Nota: no haga esto (no, en serio, no use variables globales en sus programas, y especialmente no en los suyos). Es mejor devolver algunos valores de la función:
def func(a, b): return a + 1, b + 1
Sin embargo, en python hay otro alcance y la palabra clave correspondiente:
def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter vget, vset = private(42) print(vget())
En este ejemplo, creamos una variable que se puede cambiar (y cuyo valor se obtiene) solo a través de métodos, puede usar un mecanismo similar en las clases:
def private(value=None): def getter(): return value def setter(v): nonlocal value value = v return getter, setter class Person: def __init__(self, name): self.getid, self.setid = private(name) adam = Person("adam") print(adam.getid()) print(adam.setid("john")) print(adam.getid()) print(dir(adam))
Pero quizás sería mejor limitarnos a las
propiedades o la definición de
__getattr__ ,
__setattr__ .
Incluso puedes definir
__delattr__ .
Otra característica de python es la presencia de dos métodos para obtener el atributo: __getattr__ y
__getattribute__ .
¿Cuál es la diferencia entre ellos? El primero se llama solo si no se encontró el atributo en la clase, y el segundo es incondicional. Si ambos se declaran en la clase, entonces se llama a __getattr__ solo si se llama explícitamente en __getattribute__ o si __getattribute__ generó un AttributeError.
class Person(): def __getattr__(self, item): print("__getattr__") if item == "name": return "john" raise AttributeError def __getattribute__(self, item): print("__getattribute__") raise AttributeError person = Person() print(person.name)
Y, por último, un ejemplo de cómo Python manipula libremente variables y ámbitos:
e = 42 try: 1 / 0 except Exception as e: pass print(e)
Este, por cierto, es quizás el único ejemplo en el que la segunda python es mejor que la tercera, porque genera:
... print(e)