Preguntas frecuentes de programación¶
Contenido
Preguntas frecuentes de programación
-
¿Por qué obtengo un UnboundLocalError cuando la variable tiene un valor?
¿Cuáles son las reglas para las variables locales y globales en Python?
¿Cuáles son las «buenas prácticas» para usar import en un módulo?
¿Por qué los valores por defecto se comparten entre objetos?
¿Cómo puedo pasar parámetros por palabra clave u opcionales de una función a otra?
¿Por qué cambiando la lista “y” cambia, también, la lista “x”?
¿Cómo puedo escribir una función sin parámetros (invocación mediante referencia)?
¿Cómo se puede hacer una función de orden superior en Python?
¿Es posible escribir expresiones en una línea de forma ofuscada en Python?
¿Qué hace la barra (/) en medio de la lista de parámetros de una función?
-
¿Cómo puedo comprobar si un objeto es una instancia de una clase dada o de una subclase de la misma?
¿Cómo invoco a un método definido en una clase base desde una clase derivada que la extiende?
¿Cómo puedo organizar mi código para hacer que sea más sencillo modificar la clase base?
¿Cómo puedo crear datos estáticos de clase y métodos estáticos de clase?
¿Como puedo sobrecargar constructores (o métodos) en Python?
Intento usar __spam y obtengo un error sobre _SomeClassName__spam.
Mi clase define __del__ pero no se le invoca cuando borro el objeto.
¿Cómo puedo obtener una lista de todas las instancias de una clase dada?
¿Cuándo puedo fiarme de pruebas de identidad con el operador is?
¿Cómo puede una subclase controlar qué datos se almacenan en una instancia inmutable?
Preguntas generales¶
¿Existe un depurador a nivel de código fuente con puntos de interrupción, depuración paso a paso, etc?¶
Sí.
Debajo se describen algunos depuradores para Python y la función integrada breakpoint()
te permite ejecutar alguno de ellos.
El módulo pdb es en depurador en modo consola simple pero conveniente para Python. Es parte de la biblioteca estándar de Python y está documentado en el manual de referencia de la biblioteca
. Puedes escribir tu propio depurador usando el código de pdb como ejemplo.
The IDLE interactive development environment, which is part of the standard Python distribution (normally available as Tools/scripts/idle3), includes a graphical debugger.
PythonWin es un IDE Python que incluye un depurador con GUI basado en pdb. El depurador PythonWin colorea los puntos de interrupción y dispone de características geniales como la depuración de programas no modificados mediante PythonWin. PythonWin está disponible como parte del proyecto Las extensiones de Python para Windows y como parte de la distribución ActivePython <https://www.activestate.com/activepython>.
Eric is an IDE built on PyQt and the Scintilla editing component.
trepan3k es un depurador similar a gdb.
Visual Studio Code es un IDE con herramientas de depuración que se integra con software de control de versiones.
Existen varios IDEs comerciales para Python que incluyen depuradores gráficos. Entre ellos tenemos:
¿Existe alguna herramienta que ayude a encontrar errores o realizar análisis estático?¶
Sí.
Pylint and Pyflakes do basic checking that will help you catch bugs sooner.
Inspectores estáticos de tipos como Mypy, Pyre, y Pytype pueden hacer comprobaciones de las anotaciones de tipos en código fuente Python.
¿Cómo puedo crear un binario independiente a partir de un programa Python?¶
No necesitas tener la habilidad de compilar Python a código C si lo único que necesitas es un programa independiente que los usuarios puedan descargar y ejecutar sin necesidad de instalar primero una distribución Python. Existe una serie de herramientas que determinan el conjunto de módulos que necesita un programa y une estos módulos conjuntamente con un binario Python para generar un único ejecutable.
One is to use the freeze tool, which is included in the Python source tree as Tools/freeze. It converts Python byte code to C arrays; with a C compiler you can embed all your modules into a new program, which is then linked with the standard Python modules.
Funciona escaneando su fuente de forma recursiva en busca de declaraciones de importación (en ambas formas) y buscando los módulos en la ruta estándar de Python, así como en el directorio de la fuente (para los módulos incorporados). Luego convierte el bytecode de los módulos escritos en Python en código C (inicializadores de arrays que pueden ser convertidos en objetos de código usando el módulo marshal) y crea un archivo de configuración a medida que sólo contiene aquellos módulos incorporados que se usan realmente en el programa. A continuación, compila el código C generado y lo enlaza con el resto del intérprete de Python para formar un binario autónomo que actúa exactamente igual que su script.
Los siguientes paquetes pueden ayudar con la creación de ejecutables de consola y GUI:
Nuitka (Multiplataforma)
PyInstaller (Cross-platform)
PyOxidizer (Multiplataforma)
cx_Freeze (Multiplataforma)
py2app (macOS solamente)
py2exe (Windows only)
¿Existen estándares de código o una guía de estilo para programas Python?¶
Sí. El estilo de código requerido para los módulos de la biblioteca estándar se encuentra documentado como PEP 8.
Núcleo del lenguaje¶
¿Por qué obtengo un UnboundLocalError cuando la variable tiene un valor?¶
It can be a surprise to get the UnboundLocalError
in previously working
code when it is modified by adding an assignment statement somewhere in
the body of a function.
Este código:
>>> x = 10
>>> def bar():
... print(x)
...
>>> bar()
10
funciona, pero este código:
>>> x = 10
>>> def foo():
... print(x)
... x += 1
results in an UnboundLocalError
:
>>> foo()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'x' referenced before assignment
Esto es debido a que cuando realizas una asignación a una variable en un ámbito de aplicación, esa variable se convierte en local y enmascara cualquier variable llamada de forma similar en un ámbito de aplicación exterior. Desde la última declaración en foo asigna un nuevo valor a x
, el compilador la reconoce como una variable local. Consecuentemente, cuando el print(x)
más próximo intenta mostrar la variable local no inicializada se muestra un error.
En el ejemplo anterior puedes acceder al ámbito de aplicación exterior a la variable declarándola como global:
>>> x = 10
>>> def foobar():
... global x
... print(x)
... x += 1
...
>>> foobar()
10
Esta declaración explícita es necesaria de cara a recordarte que (a diferencia de la situación superficialmente análoga con las variables de clase e instancia) estás modificando el valor de la variable en un ámbito de aplicación más externo:
>>> print(x)
11
Puedes hacer algo similar en un ámbito de aplicación anidado usando la palabra clave nonlocal
:
>>> def foo():
... x = 10
... def bar():
... nonlocal x
... print(x)
... x += 1
... bar()
... print(x)
...
>>> foo()
10
11
¿Cuáles son las reglas para las variables locales y globales en Python?¶
En Python, las variables que solo se encuentran referenciadas dentro de una función son globales implícitamente. Si a una variable se le asigna un valor en cualquier lugar dentro del cuerpo de una función, se asumirá que es local a no ser que explícitamente se la declare como global.
Aunque, inicialmente, puede parecer sorprendente, un momento de consideración permite explicar esto. Por una parte, requerir global
para variables asignadas proporciona una barrera frente a efectos secundarios indeseados. Por otra parte, si global
es requerido para todas las referencias globales, deberás usar global
en todo momento. Deberías declarar como global cualquier referencia a una función integrada o a un componente de un módulo importado. Este embrollo arruinaría la utilidad de la declaración «global» para identificar los efectos secundarios.
¿Por qué las funciones lambda definidas en un bucle con diferentes valores devuelven todas el mismo resultado?¶
Considera que usas un bucle for para crear unas pocas funciones lambda (o, incluso, funciones normales), por ejemplo.:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
Lo siguiente proporciona una lista que contiene 5 funciones lambda que calculan x**2
. Esperarías que, cuando se les invoca, retornaran, respectivamente, 0
, 1
, 4
, 9``y ``16
. Sin embargo, cuando lo ejecutes verás que todas devuelven 16
:
>>> squares[2]()
16
>>> squares[4]()
16
Esto sucede porque x
no es una función lambda local pero se encuentra definida en un ámbito de aplicación externo y se accede cuando la lambda es invocada — no cuando ha sido definida. Al final del bucle, el valor de x
es 4
, por tanto, ahora todas las funciones devuelven 4**2
, i.e. 16
. También puedes verificar esto mediante el cambio del valor de x
y ver como los resultados de las lambdas cambian:
>>> x = 8
>>> squares[2]()
64
De cara a evitar esto necesitas guardar los valores en variables locales a las funciones lambda de tal forma que no dependan del valor de la x
global:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda n=x: n**2)
Aquí, n=x
crea una nueva variable n
local a la función lambda y ejecutada cuando la función lambda se define de tal forma que tiene el mismo valor que tenía x
en ese punto en el bucle. Esto significa que el valor de n
será 0
en la primera función lambda, 1
en la segunda, 2
en la tercera y así sucesivamente. Por tanto, ahora cada lambda retornará el resultado correcto:
>>> squares[2]()
4
>>> squares[4]()
16
Es de destacar que este comportamiento no es peculiar de las funciones lambda sino que aplica también a las funciones regulares.
¿Cuáles son las «buenas prácticas» para usar import en un módulo?¶
En general, no uses from modulename import *
. Haciendo eso embarulla el espacio de nombres del importador y hace que sea más difícil para los linters el detectar los nombres sin definir.
Importar los módulos en la parte inicial del fichero. Haciéndolo así deja claro los módulos que son necesarios para tu código y evita preguntas sobre si el nombre del módulo se encuentra en el ámbito de la aplicación. Usar una importación por línea hace que sea sencillo añadir y eliminar módulos importados pero usar múltiples importaciones por línea usa menos espacio de pantalla.
Es una buena práctica si importas los módulos en el orden siguiente:
third-party library modules (anything installed in Python’s site-packages directory) – e.g.
dateutil
,requests
,PIL.Image
locally developed modules
Hay veces en que es necesario mover las importaciones a una función o clase para evitar problemas de importaciones circulares. Gordon McMillan dice:
No hay problema con las importaciones circulares cuando ambos módulos usan la forma de importación «import <module>». Fallará cuando el segundo módulo quiera coger un nombre del primer módulo («from module import name») y la importación se encuentre en el nivel superior. Esto sucede porque los nombres en el primero todavía no se encuentran disponibles debido a que el primer módulo se encuentra ocupado importando al segundo.
En este caso, si el segundo módulo se usa solamente desde una función, la importación se puede mover de forma sencilla dentro de la función. En el momento en que se invoca a la importación el primer módulo habrá terminado de inicializarse y el segundo módulo podrá hacer la importación.
También podría ser necesario mover importaciones fuera del nivel superior del código si alguno de loa módulos son específicos a la plataforma. En ese caso podría, incluso, no ser posible importar todos los módulos en la parte superior del fichero. Para esos casos, la importación correcta de los módulos en el código correspondiente específico de la plataforma es una buena opción.
Solo debes mover importaciones a un ámbito de aplicación local, como dentro de la definición de una función, si es necesario resolver problemas como una importación circular o al intentar reducir el tiempo de inicialización de un módulo. Esta técnica es especialmente útil si muchas de las importaciones no son necesarias dependiendo de cómo se ejecute el programa. También podrías mover importaciones a una función si los módulos solo se usan dentro de esa función. Nótese que la primera carga de un módulo puede ser costosa debido al tiempo necesario para la inicialización del módulo,pero la carga de un módulo múltiples veces está prácticamente libre de coste ya que solo es necesario hacer búsquedas en un diccionario. Incluso si el nombre del módulo ha salido del ámbito de aplicación el módulo se encuentre, probablemente, en sys.modules
.
¿Cómo puedo pasar parámetros por palabra clave u opcionales de una función a otra?¶
Recopila los argumentos usando los especificadores *
y **
en la lista de parámetros de la función; esto te proporciona los argumentos posicionales como una tupla y los argumentos con palabras clave como un diccionario. Puedes, entonces, pasar estos argumentos cuando invoques a otra función usando *
y **
:
def f(x, *args, **kwargs):
...
kwargs['width'] = '14.3c'
...
g(x, *args, **kwargs)
¿Cuál es la diferencia entre argumentos y parámetros?¶
Parameters are defined by the names that appear in a function definition, whereas arguments are the values actually passed to a function when calling it. Parameters define what kind of arguments a function can accept. For example, given the function definition:
def func(foo, bar=None, **kwargs):
pass
foo, bar y kwargs son parámetros de func
. Sin embargo, cuando invocamos a func
, por ejemplo:
func(42, bar=314, extra=somevar)
los valores 42
, 314
y somevar
son argumentos.
¿Por qué cambiando la lista “y” cambia, también, la lista “x”?¶
Si escribes código como:
>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]
te estarás preguntando porque añadir un elemento a y
ha cambiado también a x
.
Hay dos factores que provocan este resultado:
Las variables son simplemente nombres que referencian a objetos. Haciendo
y = x
no crea una copia de la lista – crea una nueva variabley
que referencia al mismo objeto al que referenciax
. Esto significa que solo existe un objeto (la lista) y tantox
comoy
hacen referencia al mismo.Las listas son mutable, lo que significa que puedes cambiar su contenido.
Después de la invocación a append()
, el contenido del objeto mutable ha cambiado de []
a``[10]``. Ya que ambas variables referencian al mismo objeto, el usar cualquiera de los nombres accederá al valor modificado [10]
.
Si, por otra parte, asignamos un objeto inmutable a x
:
>>> x = 5 # ints are immutable
>>> y = x
>>> x = x + 1 # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5
podemos ver que x
e y
ya no son iguales. Esto es debido a que los enteros son immutable, y cuando hacemos x = x + 1
no estamos mutando el entero 5
incrementando su valor; en su lugar, estamos creando un nuevo objeto (el entero 6
) y se lo asignamos a x
(esto es, cambiando el objeto al cual referencia x
). Después de esta asignación tenemos dos objetos (los enteros 6
y 5
) y dos variables que referencian a ellos (x
ahora referencia a 6
pero y
todavía referencia a 5
).
Some operations (for example y.append(10)
and y.sort()
) mutate the
object, whereas superficially similar operations (for example y = y + [10]
and sorted(y)
) create a new object. In general in Python (and in all cases
in the standard library) a method that mutates an object will return None
to help avoid getting the two types of operations confused. So if you
mistakenly write y.sort()
thinking it will give you a sorted copy of y
,
you’ll instead end up with None
, which will likely cause your program to
generate an easily diagnosed error.
Sin embargo, existe una clase de operaciones en las cuales la misma operación tiene, a veces, distintos comportamientos con diferentes tipos: los operadores de asignación aumentada. Por ejemplo, +=
muta listas pero no tuplas o enteros (a_list += [1, 2, 3]
es equivalente a a_list.extend([1, 2, 3])
y muta a_list
, mientras que some_tuple += (1, 2, 3)
y some_int += 1
crea nuevos objetos).
En otras palabras:
Si tenemos un objeto mutable (
list
,dict
,set
, etc.), podemos usar algunas operaciones específicas para mutarlo y todas las variables que referencian al mismo verán el cambio reflejado.Si tenemos un objeto inmutable (
str
,int
,tuple
, etc.), todas las variables que referencian al mismo verán siempre el mismo valor pero las operaciones que transforman ese valor en un nuevo valor siempre retornan un nuevo objeto.
Si deseas saber si dos variables referencian o no al mismo objeto puedes usar el operador is
o la función incorporada id()
.
¿Cómo puedo escribir una función sin parámetros (invocación mediante referencia)?¶
Recuerda que los argumentos son pasados mediante asignación en Python. Ya que las asignaciones simplemente crean referencias a objetos, no hay alias entre el nombre de un argumento en el invocador y el invocado y, por tanto, no hay invocación por referencia per se. Puedes obtener el mismo efecto deseado de formas distintas.
Mediante el retorno de una tupla de resultados:
>>> def func1(a, b): ... a = 'new-value' # a and b are local names ... b = b + 1 # assigned to new objects ... return a, b # return new values ... >>> x, y = 'old-value', 99 >>> func1(x, y) ('new-value', 100)
Esta es, casi siempre, la solución más clara.
Mediante el uso de variables globales. No es thread-safe y no se recomienda.
Pasando un objeto mutable (intercambiable en el mismo sitio):
>>> def func2(a): ... a[0] = 'new-value' # 'a' references a mutable list ... a[1] = a[1] + 1 # changes a shared object ... >>> args = ['old-value', 99] >>> func2(args) >>> args ['new-value', 100]
Pasando un diccionario que muta:
>>> def func3(args): ... args['a'] = 'new-value' # args is a mutable dictionary ... args['b'] = args['b'] + 1 # change it in-place ... >>> args = {'a': 'old-value', 'b': 99} >>> func3(args) >>> args {'a': 'new-value', 'b': 100}
O empaquetar valores en una instancia de clase:
>>> class Namespace: ... def __init__(self, /, **args): ... for key, value in args.items(): ... setattr(self, key, value) ... >>> def func4(args): ... args.a = 'new-value' # args is a mutable Namespace ... args.b = args.b + 1 # change object in-place ... >>> args = Namespace(a='old-value', b=99) >>> func4(args) >>> vars(args) {'a': 'new-value', 'b': 100}
Casi nunca existe una buena razón para hacer esto tan complicado.
Tu mejor opción es retornar una tupla que contenga los múltiples resultados.
¿Cómo se puede hacer una función de orden superior en Python?¶
Tienes dos opciones: puedes usar ámbitos de aplicación anidados o puedes usar objetos invocables. Por ejemplo, supón que querías definir linear(a,b)
que devuelve una función f(x)
que calcula el valor a*x+b
. Usar ámbitos de aplicación anidados:
def linear(a, b):
def result(x):
return a * x + b
return result
O usar un objeto invocable:
class linear:
def __init__(self, a, b):
self.a, self.b = a, b
def __call__(self, x):
return self.a * x + self.b
En ambos casos,
taxes = linear(0.3, 2)
nos da un objeto invocable donde taxes(10e6) == 0.3 * 10e6 + 2
.
El enfoque del objeto invocable tiene la desventaja que es un ligeramente más lento y el resultado es un código levemente más largo. Sin embargo, destacar que una colección de invocables pueden compartir su firma vía herencia:
class exponential(linear):
# __init__ inherited
def __call__(self, x):
return self.a * (x ** self.b)
Los objetos pueden encapsular el estado de varios métodos:
class counter:
value = 0
def set(self, x):
self.value = x
def up(self):
self.value = self.value + 1
def down(self):
self.value = self.value - 1
count = counter()
inc, dec, reset = count.up, count.down, count.set
Aquí inc()
, dec()
y reset()
se comportan como funciones las cuales comparten la misma variable de conteo.
¿Cómo copio un objeto en Python?¶
En general, prueba copy.copy()
o copy.deepcopy()
para el caso general. No todos los objetos se pueden copiar pero la mayoría sí que pueden copiarse.
Algunas objetos se pueden copiar de forma más sencilla. Los diccionarios disponen de un método copy()
:
newdict = olddict.copy()
Las secuencias se pueden copiar usando un rebanado:
new_l = l[:]
¿Cómo puedo encontrar los métodos o atributos de un objeto?¶
For an instance x
of a user-defined class, dir(x)
returns an alphabetized
list of the names containing the instance attributes and methods and attributes
defined by its class.
¿Cómo puede mi código descubrir el nombre de un objeto?¶
Hablando de forma general no podrían puesto que los objetos no disponen, realmente, de un nombre. Esencialmente, las asignaciones relacionan un nombre con su valor; Lo mismo se cumple con las declaraciones def
y class
pero, en este caso, el valor es un invocable. Considera el siguiente código:
>>> class A:
... pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print(b)
<__main__.A object at 0x16D07CC>
>>> print(a)
<__main__.A object at 0x16D07CC>
Arguably the class has a name: even though it is bound to two names and invoked
through the name B
the created instance is still reported as an instance of
class A
. However, it is impossible to say whether the instance’s name is a
or
b
, since both names are bound to the same value.
En términos generales, no debería ser necesario que tu código «conozca los nombres» de determinados valores. A menos que estés escribiendo deliberadamente programas introspectivos, esto suele ser una indicación de que un cambio de enfoque podría ser beneficioso.
En comp.lang.python, Fredrik Lundh proporcionó una vez una excelente analogía en respuesta a esta pregunta:
De la misma forma que obtienes el nombre de ese gato que te has encontrado en tu porche el propio gato (objeto) no te puede indicar su nombre y, realmente, no importa – por tanto, la única forma de encontrar cómo se llama sería preguntando a todos los vecinos (espacios de nombres) si es su gato (objeto)…
…y no te sorprendas si encuentras que se le conoce mediante diferentes nombres o ¡nadie conoce su nombre!
¿Qué ocurre con la precedencia del operador coma?¶
La coma no es un operador en Python. Considera la sesión:
>>> "a" in "b", "a"
(False, 'a')
Debido a que la coma no es un operador sino un separador entre expresiones lo anterior se evalúe como se ha introducido:
("a" in "b"), "a"
no:
"a" in ("b", "a")
Lo mismo sucede con varios operadores de asignación (=
, +=
, etc). No son realmente operadores sino delimitadores sintácticos en declaraciones de asignación.
¿Existe un equivalente al operador ternario de C «?:»?¶
Sí, existe. La sintaxis es como sigue:
[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y
Antes de que esta sintaxis se introdujera en Python 2.5 una expresión común fue el uso de operadores lógicos:
[expression] and [on_true] or [on_false]
Sin embargo, esa expresión no es segura ya que puede retornar valores erróneos cuando on_true tiene un valor booleano falso. Por tanto, siempre es mejor usar la forma ... if ... else ...
.
¿Es posible escribir expresiones en una línea de forma ofuscada en Python?¶
Yes. Usually this is done by nesting lambda
within
lambda
. See the following three examples, slightly adapted from Ulf Bartelt:
from functools import reduce
# Primes < 1000
print(list(filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))))
# First 10 Fibonacci numbers
print(list(map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1:
f(x,f), range(10))))
# Mandelbrot set
print((lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+'\n'+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24))
# \___ ___/ \___ ___/ | | |__ lines on screen
# V V | |______ columns on screen
# | | |__________ maximum of "iterations"
# | |_________________ range on y axis
# |____________________________ range on x axis
¡No probéis esto en casa, personitas!
¿Qué hace la barra (/) en medio de la lista de parámetros de una función?¶
A slash in the argument list of a function denotes that the parameters prior to
it are positional-only. Positional-only parameters are the ones without an
externally usable name. Upon calling a function that accepts positional-only
parameters, arguments are mapped to parameters based solely on their position.
For example, divmod()
is a function that accepts positional-only
parameters. Its documentation looks like this:
>>> help(divmod)
Help on built-in function divmod in module builtins:
divmod(x, y, /)
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
El slash al final de la lista de parámetros indica que los tres parámetros son únicamente posicionales. Por tanto, invocar a pow()
con argumentos con palabra clave podría derivar en un error:
>>> divmod(x=3, y=4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: divmod() takes no keyword arguments
Números y cadenas¶
¿Cómo puedo especificar enteros hexadecimales y octales?¶
Para especificar un dígito octal, prefija el valor octal con un cero y una «o» en minúscula o mayúscula. Por ejemplo, para definir la variable «a» con el valor octal «10» (8 en decimal), escribe:
>>> a = 0o10
>>> a
8
Un hexadecimal es igual de simple. Simplemente añade un cero y una «x», en minúscula o mayúscula, antes del número hexadecimal . Los dígitos hexadecimales se pueden especificar en minúsculas o mayúsculas. Por ejemplo, en el intérprete de Python:
>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178
¿Por qué -22 // 10 devuelve -3?¶
Es debido, principalmente al deseo que i % j
tenga el mismo signo que j
. Si quieres eso y, además, quieres:
i == (i // j) * j + (i % j)
entonces la división entera debe retornar el valor base más bajo. C también requiere que esa esa identidad se mantenga de tal forma que cuando los compiladores truncan i // j
necesitan que i % j
tenga el mismo signo que i
.
Existen unos pocos casos para i % j
cuando j
es negativo. Cuando j
es positivo, existen muchos casos y, virtualmente, en todos ellos es más útil para i % j
que sea >= 0
. Si el reloj dice que ahora son las 10, ¿qué dijo hace 200 horas? -190 % 12 == 2
es útil; -190 % 12 == -10
es un error listo para morderte.
¿Cómo puedo obtener un atributo int literal en lugar de SyntaxError?¶
Trying to lookup an int
literal attribute in the normal manner gives
a SyntaxError
because the period is seen as a decimal point:
>>> 1.__class__
File "<stdin>", line 1
1.__class__
^
SyntaxError: invalid decimal literal
La solución es separar el literal del punto con un espacio o un paréntesis.
>>> 1 .__class__
<class 'int'>
>>> (1).__class__
<class 'int'>
¿Cómo convierto una cadena a un número?¶
Para enteros puedes usar la función incorporada constructor de tipos int()
, por ejemplo int('144') == 144
. De forma similar, float()
convierte a un número de coma flotante, por ejemplo float('144') == 144.0
.
Por defecto, estas interpretan el número como decimal de tal forma que int('0144') == 144
y int('0x144')
lanzará ValueError
. int(string, base)
toma la base para convertirlo desde un segundo parámetro opcional, por tanto int('0x144', 16) == 324
. Si la base se especifica como 0, el número se interpreta usando las reglas de Python’s rules: un prefijo “0o” indica octal y un prefijo “0x” indica un número hexadecimal.
No uses la función incorporada eval()
si todo lo que necesitas es convertir cadenas a números. eval()
será considerablemente más lento y presenta riesgos de seguridad: cualquiera podría introducir una expresión Python que presentara efectos indeseados. Por ejemplo, alguien podría pasar``__import__(“os”).system(«rm -rf $HOME»)`` lo cual borraría el directorio home al completo.
eval()
también tiene el efecto de interpretar números como expresiones Python , de tal forma que, por ejemplo, eval('09')
dará un error de sintaxis porque Python no permite un “0” inicial en un número decimal (excepto “0”).
¿Cómo puedo convertir un número a una cadena?¶
To convert, e.g., the number 144
to the string '144'
, use the built-in type
constructor str()
. If you want a hexadecimal or octal representation, use
the built-in functions hex()
or oct()
. For fancy formatting, see
the Literales de cadena formateados and Sintaxis de formateo de cadena sections,
e.g. "{:04d}".format(144)
yields
'0144'
and "{:.3f}".format(1.0/3.0)
yields '0.333'
.
¿Cómo puedo modificar una cadena in situ?¶
No puedes debido a que las cadenas son inmutables. En la mayoría de situaciones solo deberías crear una nueva cadena a partir de varias partes que quieras usar para crearla. Sin embargo, si necesitas un objeto con la habilidad de modificar en el mismo lugar datos unicode prueba usando el objeto io.StringIO
o el módulo array
:
>>> import io
>>> s = "Hello, world"
>>> sio = io.StringIO(s)
>>> sio.getvalue()
'Hello, world'
>>> sio.seek(7)
7
>>> sio.write("there!")
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a = array.array('u', s)
>>> print(a)
array('u', 'Hello, world')
>>> a[0] = 'y'
>>> print(a)
array('u', 'yello, world')
>>> a.tounicode()
'yello, world'
¿Cómo puedo usar cadenas para invocar funciones/métodos?¶
Existen varias técnicas.
Lo mejor sería usar un diccionario que mapee cadenas a funciones. La principal ventaja de esta técnica es que las cadenas no necesitan ser iguales que los nombres de las funciones. Esta es también la principal técnica que se usa para emular un constructo case:
def a(): pass def b(): pass dispatch = {'go': a, 'stop': b} # Note lack of parens for funcs dispatch[get_input()]() # Note trailing parens to call function
Usa la función incorporada
getattr()
:import foo getattr(foo, 'bar')()
Nótese que
getattr()
funciona en cualquier objeto, incluido clases, instancias de clases, módulos, etc.Esto se usa en varios lugares de la biblioteca estándar, como esto:
class Foo: def do_foo(self): ... def do_bar(self): ... f = getattr(foo_instance, 'do_' + opname) f()
Use
locals()
para resolver el nombre de la función:def myFunc(): print("hello") fname = "myFunc" f = locals()[fname] f()
¿Existe un equivalente a chomp() en Perl para eliminar nuevas líneas al final de las cadenas?¶
Puedes usar S.rstrip("\r\n")
para eliminar todas las ocurrencias de cualquier terminación de línea desde el final de la cadena S
sin eliminar el resto de espacios en blanco que le siguen. Si la cadena S
representa más de una línea con varias líneas vacías al final, las terminaciones de línea para todas las líneas vacías se eliminarán:
>>> lines = ("line 1 \r\n"
... "\r\n"
... "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '
Ya que esto solo sería deseable, típicamente, cuando lees texto línea a línea, usar S.rstrip()
de esta forma funcionaría bien.
¿Existe un equivalente a scanf() o a sscanf() ?¶
No de la misma forma.
For simple input parsing, the easiest approach is usually to split the line into
whitespace-delimited words using the split()
method of string objects
and then convert decimal strings to numeric values using int()
or
float()
. split()
supports an optional «sep» parameter which is useful
if the line uses something other than whitespace as a separator.
For more complicated input parsing, regular expressions are more powerful
than C’s sscanf
and better suited for the task.
¿Qué significa “UnicodeDecodeError” o “UnicodeEncodeError”?¶
Ver CÓMO (HOWTO) Unicode.
Can I end a raw string with an odd number of backslashes?¶
A raw string ending with an odd number of backslashes will escape the string’s quote:
>>> r'C:\this\will\not\work\'
File "<stdin>", line 1
r'C:\this\will\not\work\'
^
SyntaxError: unterminated string literal (detected at line 1)
There are several workarounds for this. One is to use regular strings and double the backslashes:
>>> 'C:\\this\\will\\work\\'
'C:\\this\\will\\work\\'
Another is to concatenate a regular string containing an escaped backslash to the raw string:
>>> r'C:\this\will\work' '\\'
'C:\\this\\will\\work\\'
It is also possible to use os.path.join()
to append a backslash on Windows:
>>> os.path.join(r'C:\this\will\work', '')
'C:\\this\\will\\work\\'
Note that while a backslash will «escape» a quote for the purposes of determining where the raw string ends, no escaping occurs when interpreting the value of the raw string. That is, the backslash remains present in the value of the raw string:
>>> r'backslash\'preserved'
"backslash\\'preserved"
Also see the specification in the language reference.
Rendimiento¶
Mi programa es muy lento. ¿Cómo puedo acelerarlo?¶
Esa es una pregunta difícil, en general. Primero, aquí tienes una lista de cosas a recordar antes de ir más allá:
Las características del rendimiento varían entre las distintas implementaciones de Python. Estas preguntas frecuentes se enfocan en CPython.
El comportamiento puede variar entre distintos sistemas operativos, especialmente cuando se habla de tareas I/O o multi-tarea.
Siempre deberías encontrar las partes importantes en tu programa antes de intentar optimizar el código (ver el módulo
profile
).Escribir programas de comparación del rendimiento te permitirá iterar rápidamente cuando te encuentres buscando mejoras (ver el módulo
timeit
).Es altamente recomendable disponer de una buena cobertura de código (a partir de pruebas unitarias o cualquier otra técnica) antes de introducir potenciales regresiones ocultas en sofisticadas optimizaciones.
Dicho lo anterior, existen muchos trucos para acelerar código Python. Aquí tienes algunos principios generales que te permitirán llegar a alcanzar niveles de rendimiento aceptables:
El hacer más rápido tu algoritmo (o cambiarlo por alguno más rápido) puede provocar mayores beneficios que intentar unos pocos trucos de micro-optimización a través de todo tu código.
Utiliza las estructuras de datos correctas. Estudia la documentación para los Tipos Integrados y el módulo
collections
.Cuando la biblioteca estándar proporciona una primitiva de hacer algo, esta supuestamente será (aunque no se garantiza) más rápida que cualquier otra alternativa que se te ocurra. Esto es doblemente cierto si las primitivas han sido escritas en C, como los builtins y algunos tipos extendidos. Por ejemplo, asegúrate de usar el método integrado
list.sort()
o la función relacionadasorted()
para ordenar (y ver HOW TO - Ordenar para ver ejemplos de uso moderadamente avanzados).Las abstracciones tienden a crear rodeos y fuerzan al intérprete a trabajar más. Si el nivel de rodeos sobrepasa el trabajo útil realizado tu programa podría ser más lento. Deberías evitar abstracciones excesivas, especialmente, en forma de pequeñas funciones o métodos (que también va en detrimento de la legibilidad).
If you have reached the limit of what pure Python can allow, there are tools to take you further away. For example, Cython can compile a slightly modified version of Python code into a C extension, and can be used on many different platforms. Cython can take advantage of compilation (and optional type annotations) to make your code significantly faster than when interpreted. If you are confident in your C programming skills, you can also write a C extension module yourself.
Ver también
La página de la wiki dedicada a trucos de rendimiento.
¿Cuál es la forma más eficiente de concatenar muchas cadenas conjuntamente?¶
Los objetos str
y bytes
son inmutables, por tanto, concatenar muchas cadenas en una sola es ineficiente debido a que cada concatenación crea un nuevo objeto. En el caso más general, el coste total en tiempo de ejecución es cuadrático en relación a la longitud de la cadena final.
Para acumular muchos objetos str
, la forma recomendada sería colocarlos en una lista y llamar al método str.join()
al final:
chunks = []
for s in my_strings:
chunks.append(s)
result = ''.join(chunks)
(otra forma que sería razonable en términos de eficiencia sería usar io.StringIO
)
Para acumular muchos objetos bytes
, la forma recomendada sería extender un objeto bytearray
usando el operador de concatenación in situ (el operador +=
):
result = bytearray()
for b in my_bytes_objects:
result += b
Secuencias (Tuplas/Listas)¶
¿Cómo convertir entre tuplas y listas?¶
El constructor tuple(seq)
convierte cualquier secuencia (en realidad, cualquier iterable) en una tupla con los mismos elementos y en el mismo orden.
Por ejemplo, tuple([1, 2, 3])
lo convierte en (1, 2, 3)
y tuple('abc')
lo convierte en ('a', 'b', 'c')
. Si el argumento es una tupla no creará una nueva copia y retornará el mismo objeto, por tanto, llamar a tuple()
no tendrá mucho coste si no estás seguro si un objeto ya es una tupla.
El constructor list(seq)
convierte cualquier secuencia o iterable en una lista con los mismos elementos y en el mismo orden. Por ejemplo, list((1, 2, 3))
lo convierte a [1, 2, 3]
y list('abc')
lo convierte a ['a', 'b', 'c']
. Si el argumento es una lista, hará una copia como lo haría seq[:]
.
¿Qué es un índice negativo?¶
Las secuencias en Python están indexadas con números positivos y negativos. Para los números positivos el 0 será el primer índice, el 1 el segundo y así en adelante. Para los índices negativos el -1 el último índice, el -2 el penúltimo, etc. Piensa en seq[-n]
como si fuera seq[len(seq)-n]
.
El uso de índices negativos puede ser muy conveniente. Por ejemplo S[:-1]
se usa para todo la cadena excepto para su último carácter, lo cual es útil para eliminar el salto de línea final de una cadena.
¿Cómo puedo iterar sobre una secuencia en orden inverso?¶
Usa la función incorporada reversed()
:
for x in reversed(sequence):
... # do something with x ...
Esto no transformará la secuencia original sino que creará una nueva copia en orden inverso por la que se puede iterar.
¿Cómo eliminar duplicados de una lista?¶
Puedes echar un vistazo al recetario de Python para ver una gran discusión mostrando muchas formas de hacer esto:
Si no te preocupa que la lista se reordene la puedes ordenar y, después, y después escanearla desde el final borrando duplicados a medida que avanzas:
if mylist:
mylist.sort()
last = mylist[-1]
for i in range(len(mylist)-2, -1, -1):
if last == mylist[i]:
del mylist[i]
else:
last = mylist[i]
Si todos los elementos de la lista pueden ser usados como claves (por ejemplo son todos hashable) esto será, en general, más rápido
mylist = list(set(mylist))
Esto convierte la lista en un conjunto eliminando, por tanto, los duplicados y, posteriormente, puedes volver a una lista.
Cómo eliminar duplicados de una lista¶
Al igual que con la eliminación de duplicados, una posibilidad es iterar explícitamente a la inversa con una condición de eliminación. Sin embargo, es más fácil y rápido utilizar el reemplazo de sectores con una iteración directa implícita o explícita. Aquí hay tres variaciones.:
mylist[:] = filter(keep_function, mylist)
mylist[:] = (x for x in mylist if keep_condition)
mylist[:] = [x for x in mylist if keep_condition]
Esta comprensión de lista puede ser la más rápida.
¿Cómo se puede hacer un array en Python?¶
Usa una lista:
["this", 1, "is", "an", "array"]
Las listas son equivalentes en complejidad temporal a arrays en C o Pascal; La principal diferencia es que una lista en Python puede contener objetos de diferentes tipos.
The array
module also provides methods for creating arrays of fixed types
with compact representations, but they are slower to index than lists. Also
note that NumPy
and other third party packages define array-like structures with
various characteristics as well.
To get Lisp-style linked lists, you can emulate cons cells using tuples:
lisp_list = ("like", ("this", ("example", None) ) )
If mutability is desired, you could use lists instead of tuples. Here the
analogue of a Lisp car is lisp_list[0]
and the analogue of cdr is
lisp_list[1]
. Only do this if you’re sure you really need to, because it’s
usually a lot slower than using Python lists.
¿Cómo puedo crear una lista multidimensional?¶
Seguramente hayas intentado crear un array multidimensional de la siguiente forma:
>>> A = [[None] * 2] * 3
Esto parece correcto si lo muestras en pantalla:
>>> A
[[None, None], [None, None], [None, None]]
Pero cuando asignas un valor, se muestra en múltiples sitios:
>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]
La razón es que replicar una lista con *
no crea copias, solo crea referencias a los objetos existentes. El *3
crea una lista conteniendo 3 referencias a la misma lista de longitud dos. Cambios a una fila se mostrarán en todas las filas, lo cual, seguramente, no es lo que deseas.
El enfoque recomendado sería crear, primero, una lista de la longitud deseada y, después, rellenar cada elemento con una lista creada en ese momento:
A = [None] * 3
for i in range(3):
A[i] = [None] * 2
Esto genera una lista conteniendo 3 listas distintas de longitud dos. También puedes usar una comprensión de lista:
w, h = 2, 3
A = [[None] * w for i in range(h)]
Or, you can use an extension that provides a matrix datatype; NumPy is the best known.
¿Cómo puedo aplicar un método a una secuencia de objetos?¶
Usa una comprensión de listas:
result = [obj.method() for obj in mylist]
¿Por qué hacer lo siguiente, a_tuple[i] += ['item']
, lanza una excepción cuando la suma funciona?¶
Esto es debido a la combinación del hecho de que un operador de asignación aumentada es un operador de asignación y a la diferencia entre objetos mutables e inmutable en Python.
Esta discusión aplica, en general, cuando los operadores de asignación aumentada se aplican a elementos de una tupla que apuntan a objetos mutables. Pero vamos a usar una lista
y +=
para el ejemplo.
Si escribes:
>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
La razón por la que se produce la excepción debería ser evidente: 1
se añade al objeto a_tuple[0]
que apunta a (1
), creando el objeto resultante, 2
, pero cuando intentamos asignar el resultado del cálculo, 2
, al elemento 0
de la tupla, obtenemos un error debido a que no podemos cambiar el elemento al que apunta la tupla.
En realidad, lo que esta declaración de asignación aumentada está haciendo es, aproximadamente, lo siguiente:
>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
Es la parte de asignación de la operación la que provoca el error, debido a que una tupla es inmutable.
Cuando escribes algo como lo siguiente:
>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
La excepción es un poco más sorprendente e, incluso, más sorprendente es el hecho que aunque hubo un error, la agregación funcionó:
>>> a_tuple[0]
['foo', 'item']
To see why this happens, you need to know that (a) if an object implements an
__iadd__()
magic method, it gets called when the +=
augmented
assignment
is executed, and its return value is what gets used in the assignment statement;
and (b) for lists, __iadd__()
is equivalent to calling extend()
on the list
and returning the list. That’s why we say that for lists, +=
is a
«shorthand» for list.extend()
:
>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]
Esto es equivalente a
>>> result = a_list.__iadd__([1])
>>> a_list = result
El objeto al que apunta a_list ha mutado y el puntero al objeto mutado es asignado de vuelta a a_list
. El resultado final de la asignación no es opción debido a que es un puntero al mismo objeto al que estaba apuntando a_list
pero la asignación sí que ocurre.
Por tanto, en nuestro ejemplo con tupla lo que está pasando es equivalente a:
>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
...
TypeError: 'tuple' object does not support item assignment
The __iadd__()
succeeds, and thus the list is extended, but even though
result
points to the same object that a_tuple[0]
already points to,
that final assignment still results in an error, because tuples are immutable.
Quiero hacer una ordenación compleja: ¿Puedes hacer una transformada Schwartziana (Schwartzian Transform) en Python?¶
La técnica, atribuida a Randal Schwartz, miembro de la comunidad Perl, ordena los elementos de una lista mediante una métrica que mapea cada elemento a su «valor orden». En Python, usa el argumento key
par el método list.sort()
:
Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))
¿Cómo puedo ordenar una lista a partir de valores de otra lista?¶
Las puedes unir en un iterador de tuplas, ordena la lista resultando y después extrae el elemento que deseas.
>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs = sorted(pairs)
>>> pairs
[("I'm", 'else'), ('by', 'sort'), ('sorting', 'to'), ('what', 'something')]
>>> result = [x[1] for x in pairs]
>>> result
['else', 'sort', 'to', 'something']
Objetos¶
¿Qué es una clase?¶
Una clase es un tipo de objeto particular creado mediante la ejecución de la declaración class. Los objetos class se usan como plantillas para crear instancias de objetos que son tanto los datos (atributos) como el código (métodos) específicos para un tipo de dato.
Una clase puede estar basada en una o más clases diferentes, llamadas su(s) clase(s). Hereda los atributos y métodos de sus clases base. Esto permite que se pueda refinar un objeto modelo de forma sucesiva mediante herencia. Puedes tener una clase genérica Mailbox
que proporciona métodos de acceso básico para un buzón de correo y subclases como MboxMailbox
, MaildirMailbox
, OutlookMailbox
que gestionan distintos formatos específicos de buzón de correos.
¿Qué es un método?¶
Un método es una función de un objeto x
que puedes llamar, normalmente, de la forma x.name(arguments...)
. Los métodos se definen como funciones dentro de la definición de la clase:
class C:
def meth(self, arg):
return arg * 2 + self.attribute
¿Qué es self?¶
Self es, básicamente, un nombre que se usa de forma convencional como primer argumento de un método. Un método definido como meth(self, a, b, c)
se le llama como x.meth(a, b, c)
para una instancia x
de la clase es que se definió; el método invocado pensará que se le ha invocado como meth(x, a, b, c)
.
Ver también ¿Por qué debe usarse “self” explícitamente en las definiciones y llamadas de métodos?.
¿Cómo puedo comprobar si un objeto es una instancia de una clase dada o de una subclase de la misma?¶
Use the built-in function isinstance(obj, cls)
. You can
check if an object
is an instance of any of a number of classes by providing a tuple instead of a
single class, e.g. isinstance(obj, (class1, class2, ...))
, and can also
check whether an object is one of Python’s built-in types, e.g.
isinstance(obj, str)
or isinstance(obj, (int, float, complex))
.
Note que isinstance()
también verifica la herencia virtual de una abstract base class. Entonces, la prueba retorna True para una clase registrada incluso si no ha heredado directa o indirectamente de ella. Para verificar «herencia verdadera», escanea el MRO de la clase:
from collections.abc import Mapping
class P:
pass
class C(P):
pass
Mapping.register(P)
>>> c = C()
>>> isinstance(c, C) # direct
True
>>> isinstance(c, P) # indirect
True
>>> isinstance(c, Mapping) # virtual
True
# Actual inheritance chain
>>> type(c).__mro__
(<class 'C'>, <class 'P'>, <class 'object'>)
# Test for "true inheritance"
>>> Mapping in type(c).__mro__
False
Destacar que muchos programas no necesitan usar isinstance()
de forma frecuente en clases definidas por el usuario. Si estás desarrollando clases un mejor estilo orientado a objetos sería el de definir los métodos en las clases que encapsulan un comportamiento en particular en lugar de ir comprobando la clase del objeto e ir haciendo cosas en base a la clase que es. Por ejemplo, si tienes una función que hace lo siguiente:
def search(obj):
if isinstance(obj, Mailbox):
... # code to search a mailbox
elif isinstance(obj, Document):
... # code to search a document
elif ...
Un enfoque más adecuado sería definir un método search()
en todas las clases e invocarlo:
class Mailbox:
def search(self):
... # code to search a mailbox
class Document:
def search(self):
... # code to search a document
obj.search()
¿Qué es la delegación?¶
La delegación es una técnica orientada a objetos (también llamado un patrón de diseño). Digamos que tienes un objeto x
y deseas cambiar el comportamiento de solo uno de sus métodos. Puedes crear una nueva clase que proporciona una nueva implementación del método que te interesa cambiar y delega el resto de métodos al método correspondiente de x
.
Los programadores Python pueden implementar la delegación de forma muy sencilla. Por ejemplo, la siguiente clase implementa una clase que se comporta como un fichero pero convierte todos los datos escritos a mayúsculas:
class UpperOut:
def __init__(self, outfile):
self._outfile = outfile
def write(self, s):
self._outfile.write(s.upper())
def __getattr__(self, name):
return getattr(self._outfile, name)
Here the UpperOut
class redefines the write()
method to convert the
argument string to uppercase before calling the underlying
self._outfile.write()
method. All other methods are delegated to the
underlying self._outfile
object. The delegation is accomplished via the
__getattr__()
method; consult the language reference
for more information about controlling attribute access.
Note that for more general cases delegation can get trickier. When attributes
must be set as well as retrieved, the class must define a __setattr__()
method too, and it must do so carefully. The basic implementation of
__setattr__()
is roughly equivalent to the following:
class X:
...
def __setattr__(self, name, value):
self.__dict__[name] = value
...
Most __setattr__()
implementations must modify
self.__dict__
to store
local state for self without causing an infinite recursion.
¿Cómo invoco a un método definido en una clase base desde una clase derivada que la extiende?¶
Usa la función incorporada super()
:
class Derived(Base):
def meth(self):
super().meth() # calls Base.meth
En el ejemplo, super()
automáticamente determinará la instancia desde la cual ha sido llamada (el valor self`
), busca el method resolution order (MRO) con type(self).__mro__
, y devuelve el siguiente en línea después de Derived
en el MRO: Base
.
¿Cómo puedo organizar mi código para hacer que sea más sencillo modificar la clase base?¶
Puede asignar la clase base a un alias y derivar del alias. Entonces todo lo que tiene que cambiar es el valor asignado al alias. Por cierto, este truco también es útil si desea decidir dinámicamente (por ejemplo, dependiendo de la disponibilidad de recursos) qué clase base usar. Ejemplo:
class Base:
...
BaseAlias = Base
class Derived(BaseAlias):
...
¿Cómo puedo crear datos estáticos de clase y métodos estáticos de clase?¶
Tanto los datos estáticos como los métodos estáticos (en el sentido de C++ o Java) están permitidos en Python.
Para datos estáticos simplemente define un atributo de clase. Para asignar un nuevo valor al atributo debes usar de forma explícita el nombre de la clase en la asignación:
class C:
count = 0 # number of times C.__init__ called
def __init__(self):
C.count = C.count + 1
def getcount(self):
return C.count # or return self.count
c.count
también se refiere a C.count
para cualquier c
de tal forma que se cumpla isinstance(c, C)
, a no ser que c
sea sobreescrita por si misma o por alguna clase contenida en la búsqueda de clases base desde``c.__class__`` hasta C
.
Debes tener cuidado: dentro de un método de C, una asignación como self.count = 42
creará una nueva instancia sin relación con la original que se llamará «count» en el propio diccionario de self
. El reunificar el nombre de datos estáticos de una clase debería llevar, siempre, a especificar la clase tanto si se produce desde dentro de un método como si no:
C.count = 314
Los métodos estáticos son posibles:
class C:
@staticmethod
def static(arg1, arg2, arg3):
# No 'self' parameter!
...
Sin embargo, una forma más directa de obtener el efecto de un método estático sería mediante una simple función a nivel de módulo:
def getcount():
return C.count
Si has estructurado tu código para definir una clase única (o una jerarquía de clases altamente relacionadas) por módulo, esto proporcionará la encapsulación deseada.
¿Como puedo sobrecargar constructores (o métodos) en Python?¶
Esta respuesta es aplicable, en realidad, a todos los métodos pero la pregunta suele surgir primero en el contexto de los constructores.
En C++ deberías escribir
class C {
C() { cout << "No arguments\n"; }
C(int i) { cout << "Argument is " << i << "\n"; }
}
En Python solo debes escribir un único constructor que tenga en cuenta todos los casos usando los argumentos por defecto. Por ejemplo:
class C:
def __init__(self, i=None):
if i is None:
print("No arguments")
else:
print("Argument is", i)
Esto no es totalmente equivalente pero, en la práctica, es muy similar.
Podrías intentar, también una lista de argumentos de longitud variable, por ejemplo
def __init__(self, *args):
...
El mismo enfoque funciona para todas las definiciones de métodos.
Intento usar __spam y obtengo un error sobre _SomeClassName__spam.¶
Nombres de variable con doble guión prefijado se convierten, con una modificación de nombres, para proporcionar una forma simple pero efectiva de definir variables de clase privadas. Cualquier identificador de la forma __spam
(como mínimo dos guiones bajos como prefijo, como máximo un guión bajo como sufijo) se reemplaza con _classname__spam
, donde classname
es el nombre de la clase eliminando cualquier guión bajo prefijado.
Esto no garantiza la privacidad: un usuario externo puede acceder, de forma deliberada y si así lo desea, al atributo «_classname__spam», y los valores privados son visibles en el __dict__
del objeto. Muchos programadores Python no se suelen molestar en usar nombres privados de variables.
Mi clase define __del__ pero no se le invoca cuando borro el objeto.¶
Existen varias razones posibles para que suceda así.
The del
statement does not necessarily call __del__()
– it simply
decrements the object’s reference count, and if this reaches zero
__del__()
is called.
If your data structures contain circular links (e.g. a tree where each child has
a parent reference and each parent has a list of children) the reference counts
will never go back to zero. Once in a while Python runs an algorithm to detect
such cycles, but the garbage collector might run some time after the last
reference to your data structure vanishes, so your __del__()
method may be
called at an inconvenient and random time. This is inconvenient if you’re trying
to reproduce a problem. Worse, the order in which object’s __del__()
methods are executed is arbitrary. You can run gc.collect()
to force a
collection, but there are pathological cases where objects will never be
collected.
Despite the cycle collector, it’s still a good idea to define an explicit
close()
method on objects to be called whenever you’re done with them. The
close()
method can then remove attributes that refer to subobjects. Don’t
call __del__()
directly – __del__()
should call close()
and
close()
should make sure that it can be called more than once for the same
object.
Otra forma de evitar referencias cíclicas sería usando el módulo weakref
, que permite apuntar hacia objetos sin incrementar su conteo de referencias. Las estructuras de datos en árbol, por ejemplo, deberían usar referencias débiles para las referencias del padre y hermanos (¡si es que las necesitan!).
Finally, if your __del__()
method raises an exception, a warning message
is printed to sys.stderr
.
¿Cómo puedo obtener una lista de todas las instancias de una clase dada?¶
Python no hace seguimiento de todas las instancias de una clase (o de los tipos incorporados). Puedes programar el constructor de una clase para que haga seguimiento de todas sus instancias manteniendo una lista de referencias débiles a cada instancia.
¿Por qué el resultado de id()
no parece ser único?¶
La función incorporada id()
devuelve un entero que se garantiza que sea único durante la vida del objeto. Debido a que en CPython esta es la dirección en memoria del objeto, sucede que, frecuentemente, después de que un objeto se elimina de la memoria el siguiente objeto recién creado se localiza en la misma posición en memoria. Esto se puede ver ilustrado en este ejemplo:
>>> id(1000)
13901272
>>> id(2000)
13901272
Las dos ids pertenecen a dos objetos “entero” diferentes que se crean antes y se eliminan inmediatamente después de la ejecución de la invocación a id()
. Para estar seguro que los objetos cuya id quieres examinar siguen vivos crea otra referencia al objeto:
>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296
¿Cuándo puedo fiarme de pruebas de identidad con el operador is?¶
El operador is
verifica la identidad de un objeto. La prueba a is b
es equivalente a id(a) == id(b)
.
La propiedad más importante de una prueba de identidad es que un objeto siempre es idéntico a si mismo, a is a
siempre devuelve True
. Las pruebas de identidad suelen ser más rápidas que pruebas de igualdad. Y a diferencia de las pruebas de igualdad, las pruebas de identidad están garantizadas de devolver un booleano True
o False
.
Sin embargo, las pruebas de identidad solo pueden ser sustituidas por pruebas de igualdad cuando la identidad de objeto está asegurada. Generalmente hay tres circunstancias en las que la identidad está garantizada:
1) Assignments create new names but do not change object identity. After the
assignment new = old
, it is guaranteed that new is old
.
2) Putting an object in a container that stores object references does not
change object identity. After the list assignment s[0] = x
, it is
guaranteed that s[0] is x
.
3) If an object is a singleton, it means that only one instance of that object
can exist. After the assignments a = None
and b = None
, it is
guaranteed that a is b
because None
is a singleton.
En la mayoría de las demás circunstancias, no se recomiendan las pruebas de identidad y las pruebas de igualdad son preferidas. En particular, las pruebas de identidad no deben ser usadas para verificar constantes como int
y str
que no están garantizadas a ser singletons:
>>> a = 1000
>>> b = 500
>>> c = b + 500
>>> a is c
False
>>> a = 'Python'
>>> b = 'Py'
>>> c = b + 'thon'
>>> a is c
False
De la misma manera, nuevas instancias de contenedores mutables nunca son idénticas:
>>> a = []
>>> b = []
>>> a is b
False
En la librería estándar de código, verás varios patrones comunes para usar correctamente pruebas de identidad:
1) As recommended by PEP 8, an identity test is the preferred way to check
for None
. This reads like plain English in code and avoids confusion with
other objects that may have boolean values that evaluate to false.
2) Detecting optional arguments can be tricky when None
is a valid input
value. In those situations, you can create a singleton sentinel object
guaranteed to be distinct from other objects. For example, here is how
to implement a method that behaves like dict.pop()
:
_sentinel = object()
def pop(self, key, default=_sentinel):
if key in self:
value = self[key]
del self[key]
return value
if default is _sentinel:
raise KeyError(key)
return default
3) Container implementations sometimes need to augment equality tests with
identity tests. This prevents the code from being confused by objects such as
float('NaN')
that are not equal to themselves.
Por ejemplo, acá está la implementación de collections.abc.Sequence.__contains__()
:
def __contains__(self, value):
for v in self:
if v is value or v == value:
return True
return False
¿Cómo puede una subclase controlar qué datos se almacenan en una instancia inmutable?¶
When subclassing an immutable type, override the __new__()
method
instead of the __init__()
method. The latter only runs after an
instance is created, which is too late to alter data in an immutable
instance.
Todas estas clases inmutables tienen una firma distinta que su clase padre:
from datetime import date
class FirstOfMonthDate(date):
"Always choose the first day of the month"
def __new__(cls, year, month, day):
return super().__new__(cls, year, month, 1)
class NamedInt(int):
"Allow text names for some numbers"
xlat = {'zero': 0, 'one': 1, 'ten': 10}
def __new__(cls, value):
value = cls.xlat.get(value, value)
return super().__new__(cls, value)
class TitleStr(str):
"Convert str to name suitable for a URL path"
def __new__(cls, s):
s = s.lower().replace(' ', '-')
s = ''.join([c for c in s if c.isalnum() or c == '-'])
return super().__new__(cls, s)
Las clases pueden ser utilizadas así:
>>> FirstOfMonthDate(2012, 2, 14)
FirstOfMonthDate(2012, 2, 1)
>>> NamedInt('ten')
10
>>> NamedInt(20)
20
>>> TitleStr('Blog: Why Python Rocks')
'blog-why-python-rocks'
¿Cómo cacheo llamadas de método?¶
Las dos herramientas principales para cachear métodos son functools.cached_property()
y functools.lru_cache()
. El primero guarda resultados a nivel de instancia y el último a nivel de clase.
La función cached_property sólo funciona con métodos que no acepten argumentos. No crea una referencia a la instancia. El resultado del método cacheado se mantendrá solo mientras que la instancia esté activa.
The advantage is that when an instance is no longer used, the cached method result will be released right away. The disadvantage is that if instances accumulate, so too will the accumulated method results. They can grow without bound.
The lru_cache approach works with methods that have hashable arguments. It creates a reference to the instance unless special efforts are made to pass in weak references.
La ventaja del algoritmo usado menos recientemente es que el cache está limitado por el maxsize especificado. La desventaja es que las instancias se mantienen activas hasta que sean eliminadas del cache por edad o que el cache sea borrado.
Este ejemplo muestra las diversas técnicas:
class Weather:
"Lookup weather information on a government website"
def __init__(self, station_id):
self._station_id = station_id
# The _station_id is private and immutable
def current_temperature(self):
"Latest hourly observation"
# Do not cache this because old results
# can be out of date.
@cached_property
def location(self):
"Return the longitude/latitude coordinates of the station"
# Result only depends on the station_id
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='mm'):
"Rainfall on a given date"
# Depends on the station_id, date, and units.
El ejemplo anterior asume que la station_id nunca cambia. Si los atributos de la instancia relevante son mutables, el método de cached_property no puede funcionar porque no puede detectar cambios en los atributos.
Se puede hacer que el método lru_cache funcione, pero la clase debe definir los métodos __eq__ y __hash__ para que el cache pueda detectar cambios relevantes de atributo:
class Weather:
"Example with a mutable station identifier"
def __init__(self, station_id):
self.station_id = station_id
def change_station(self, station_id):
self.station_id = station_id
def __eq__(self, other):
return self.station_id == other.station_id
def __hash__(self):
return hash(self.station_id)
@lru_cache(maxsize=20)
def historic_rainfall(self, date, units='cm'):
'Rainfall on a given date'
# Depends on the station_id, date, and units.
Módulos¶
¿Cómo creo un fichero .pyc?¶
Cuando se importa un módulo por primera vez (o cuando el código fuente ha cambiado desde que el fichero compilado se creó) un fichero .pyc
que contiene el código compilado se debería crear en la subcarpeta __pycache__
del directorio que contiene al fichero``.py``. El fichero .pyc
tendrá un nombre que empezará con el mismo nombre que el del fichero .py
y terminará con .pyc
, con un componente intermedio que dependerá del binario python
en particular que lo creó. (Ver PEP 3147 para detalles.)
Una razón por la que no se cree un fichero .pyc
podría ser debido a un problema de permisos del directorio que contiene al fichero fuente, lo que significa que el subdirectorio __pycache__
no se puede crear. Esto puede suceder, por ejemplo, si desarrollas como un usuario pero lo ejecutas como otro, como si estuvieras probando en un servidor web.
Hasta que no definas la variable de entorno PYTHONDONTWRITEBYTECODE
, la creación de un fichero .pyc se hará automáticamente si importas un módulo y Python dispone de la habilidad (permisos, espacio libre, etc…) para crear un subdirectorio __pycache__
y escribir un módulo compilado en ese subdirectorio.
La ejecución de un script principal Python no se considera una importación y no se crea el fichero .pyc
. Por ejemplo, Si tienes un módulo principal foo.py
que importa a otro módulo xyz.py
, cuando ejecutas foo
(mediante un comando de la shell python foo.py
), se creará un fichero .pyc
para xyz
porque xyz
ha sido importado, pero no se creará un fichero .pyc
para foo
ya que foo.py
no ha sido importado.
Si necesitas crear un fichero .pyc
también para foo
– es decir, crear un fichero .pyc
para un módulo que no ha sido importado – puedes usar los módulos py_compile
y compileall
.
El módulo py_compile
puede compilar manualmente cualquier módulo. Una forma sería usando la función compile()
de ese módulo de forma interactiva:
>>> import py_compile
>>> py_compile.compile('foo.py')
Esto escribirá .pyc
en el subdirectorio __pycache__
en la misma localización en la que se encuentre foo.py
(o, puedes sobreescribir ese comportamiento con el parámetro opcional cfile
).
Puedes compilar automáticamente todos los ficheros en un directorio o directorios usando el módulo compileall
. Lo puedes hacer desde la línea de comandos ejecutando compileall.py
y proporcionando una ruta al directorio que contiene los ficheros Python a compilar:
python -m compileall .
¿Cómo puedo encontrar el nombre del módulo en uso?¶
Un módulo puede encontrar su propio nombre mirando en la variable global predeterminada __name__
. Si tiene el valor '__main__'
, el programa se está ejecutando como un script. Muchos módulos que se usan, generalmente, importados en otro script proporcionan, además, una interfaz para la línea de comandos o para probarse a si mismos y solo ejecutan código después de comprobar __name__
:
def main():
print('Running test...')
...
if __name__ == '__main__':
main()
¿Cómo podría tener módulos que se importan mutuamente entre ellos?¶
Supón que tienes los siguientes módulos:
foo.py
:
from bar import bar_var
foo_var = 1
bar.py
:
from foo import foo_var
bar_var = 2
El problema es que el intérprete realizará los siguientes pasos:
main importa a
foo
Se crean globals vacíos para foo
foo
se compila y se comienza a ejecutarfoo
importa abar
Se crean globals vacíos para
bar
bar
se compila y se comienza a ejecutarbar
importa afoo
(lo cual es un no-op ya que ya hay un módulo que se llamafoo
)El mecanismo de importado intenta leer
foo_var
de globales defoo
, para establecerbar.foo_var = foo.foo_var
El último paso falla debido a que Python todavía no ha terminado de interpretar a foo
y el diccionario de símbolos global para foo
todavía se encuentra vacío.
Lo mismo ocurre cuando usas import foo
y luego tratas de acceder a foo.foo_var
en un código global.
Existen (al menos) tres posibles soluciones para este problema.
Guido van Rossum recomienda evitar todos los usos de from <module> import ...
, y colocar todo el código dentro de funciones. La inicialización de variables globales y variables de clase debería usar únicamente constantes o funciones incorporadas . Esto significa que todo se referenciará como <module>.<name>
desde un módulo importado.
Jim Roskind sugiere realizar los siguientes pasos en el siguiente orden en cada módulo:
exportar (globals, funciones y clases que no necesitan clases bases importadas)
import
declaracionescódigo activo (incluyendo globals que han sido inicializados desde valores importados).
Van Rossum doesn’t like this approach much because the imports appear in a strange place, but it does work.
Matthias Urlichs recomienda reestructurar tu código de tal forma que un import recursivo no sea necesario.
Estas soluciones no son mutuamente excluyentes.
__import__(“x.y.z”) devuelve <module “x”>; ¿cómo puedo obtener z?¶
Considera, en su lugar, usa la función de conveniencia import_module()
de importlib
:
z = importlib.import_module('x.y.z')
Cuando edito un módulo importado y lo reimporto los cambios no tienen efecto. ¿Por qué sucede esto?¶
Por razones de eficiencia además de por consistencia, Python solo lee el fichero del módulo la primera vez que el módulo se importa. Si no lo hiciera así, un programa escrito en muchos módulos donde cada módulo importa al mismo módulo básico estaría analizando sintácticamente el mismo módulo básico muchas veces. Para forzar una relectura de un módulo que ha sido modificado haz lo siguiente:
import importlib
import modname
importlib.reload(modname)
Alerta: esta técnica no es 100% segura. En particular, los módulos que contienen declaraciones como
from modname import some_objects
continuarán funcionando con la versión antigua de los objetos importados. Si el módulo contiene definiciones de clase, instancias de clase ya existentes no se actualizarán para usar la nueva definición de la clase. Esto podría resultar en el comportamiento paradójico siguiente:
>>> import importlib
>>> import cls
>>> c = cls.C() # Create an instance of C
>>> importlib.reload(cls)
<module 'cls' from 'cls.py'>
>>> isinstance(c, cls.C) # isinstance is false?!?
False
La naturaleza del problema se hace evidente si muestras la «identity» de los objetos clase:
>>> hex(id(c.__class__))
'0x7352a0'
>>> hex(id(cls.C))
'0x4198d0'