Explorando y entendiendo Python a través de fragmentos sorprendentes.
Traducciones: chino 中文 | Vietnamita Tiếng Việt | Español Español | Coreano 한국어 | ruso Русский | alemán alemán | Agregar traducción
Otros modos: Sitio web interactivo | Cuaderno interactivo
Python, al ser un lenguaje de programación de alto nivel bellamente diseñado y basado en intérpretes, nos proporciona muchas funciones para la comodidad del programador. Pero a veces, los resultados de un fragmento de Python pueden no parecer obvios a primera vista.
Aquí hay un proyecto divertido que intenta explicar qué está sucediendo exactamente bajo el capó de algunos fragmentos contrarios a la intuición y características menos conocidas en Python.
Si bien algunos de los ejemplos que ve a continuación pueden no ser WTF en el sentido más verdadero, revelarán algunas de las partes interesantes de Python que quizás desconozca. Me parece una buena forma de aprender los aspectos internos de un lenguaje de programación y creo que a ti también te resultará interesante.
Si es un programador experimentado en Python, puede considerarlo un desafío lograr que la mayoría de ellos sean correctos en el primer intento. Es posible que ya hayas experimentado algunos de ellos antes y ¡tal vez pueda revivir tus viejos y dulces recuerdos! ?
PD: Si eres un lector recurrente, puedes conocer las nuevas modificaciones aquí (los ejemplos marcados con un asterisco son los que se agregaron en la última revisión importante).
Así que aquí vamos...
is
operadoris not ...
no is (not ...)
del
funcionamientogoto
, pero ¿por qué?+=
es más rápidodict
*dict
de instancias hinchadas *Todos los ejemplos están estructurados como se muestra a continuación:
▶ Algún título elegante
# Set up the code. # Preparation for the magic...Salida (versiones de Python):
> >> triggering_statement Some unexpected output(Opcional): una línea que describe el resultado inesperado.
Explicación:
- Breve explicación de lo que está pasando y por qué está pasando.
# Set up code # More examples for further clarification (if necessary)Salida (versiones de Python):
> >> trigger # some example that makes it easy to unveil the magic # some justified output
Nota: Todos los ejemplos se prueban en el intérprete interactivo de Python 3.5.2 y deberían funcionar para todas las versiones de Python a menos que se especifique explícitamente antes del resultado.
En mi opinión, una buena forma de aprovechar al máximo estos ejemplos es leerlos en orden secuencial y para cada ejemplo:
Por alguna razón, el operador "Morsa" de Python 3.8 ( :=
) se ha vuelto bastante popular. Comprobémoslo
1.
# Python version 3.8+
> >> a = "wtf_walrus"
> >> a
'wtf_walrus'
> >> a := "wtf_walrus"
File "<stdin>" , line 1
a := "wtf_walrus"
^
SyntaxError : invalid syntax
>> > ( a := "wtf_walrus" ) # This works though
'wtf_walrus'
> >> a
'wtf_walrus'
2 .
# Python version 3.8+
> >> a = 6 , 9
> >> a
( 6 , 9 )
> >> ( a := 6 , 9 )
( 6 , 9 )
> >> a
6
> >> a , b = 6 , 9 # Typical unpacking
> >> a , b
( 6 , 9 )
>> > ( a , b = 16 , 19 ) # Oops
File "<stdin>" , line 1
( a , b = 16 , 19 )
^
SyntaxError : invalid syntax
>> > ( a , b := 16 , 19 ) # This prints out a weird 3-tuple
( 6 , 16 , 19 )
>> > a # a is still unchanged?
6
>> > b
16
Repaso rápido del operador de morsa
El operador Morsa ( :=
) se introdujo en Python 3.8 y puede ser útil en situaciones en las que desea asignar valores a variables dentro de una expresión.
def some_func ():
# Assume some expensive computation here
# time.sleep(1000)
return 5
# So instead of,
if some_func ():
print ( some_func ()) # Which is bad practice since computation is happening twice
# or
a = some_func ()
if a :
print ( a )
# Now you can concisely write
if a := some_func ():
print ( a )
Salida (> 3.8):
5
5
5
Esto guardó una línea de código e implícitamente impidió invocar some_func
dos veces.
La "expresión de asignación" sin paréntesis (uso del operador morsa) está restringida en el nivel superior, de ahí el SyntaxError
en la declaración a := "wtf_walrus"
del primer fragmento. Al ponerlo entre paréntesis funcionó como se esperaba y se le asignó a
.
Como de costumbre, no se permite el uso de paréntesis en una expresión que contenga el operador =
. De ahí el error de sintaxis en (a, b = 6, 9)
.
La sintaxis del operador Walrus tiene la forma NAME:= expr
, donde NAME
es un identificador válido y expr
es una expresión válida. Por lo tanto, no se admiten el embalaje y desembalaje iterables, lo que significa que
(a := 6, 9)
es equivalente a ((a := 6), 9)
y finalmente (a, 9)
(donde el valor de a
es 6')
> >> ( a := 6 , 9 ) == (( a := 6 ), 9 )
True
> >> x = ( a := 696 , 9 )
> >> x
( 696 , 9 )
> >> x [ 0 ] is a # Both reference same memory location
True
De manera similar, (a, b := 16, 19)
es equivalente a (a, (b := 16), 19)
que no es más que una tupla de 3.
1.
> >> a = "some_string"
> >> id ( a )
140420665652016
> >> id ( "some" + "_" + "string" ) # Notice that both the ids are same.
140420665652016
2.
> >> a = "wtf"
> >> b = "wtf"
> >> a is b
True
> >> a = "wtf!"
> >> b = "wtf!"
> >> a is b
False
3.
> >> a , b = "wtf!" , "wtf!"
> >> a is b # All versions except 3.7.x
True
> >> a = "wtf!" ; b = "wtf!"
> >> a is b # This will print True or False depending on where you're invoking it (python shell / ipython / as a script)
False
# This time in file some_file.py
a = "wtf!"
b = "wtf!"
print ( a is b )
# prints True when the module is invoked!
4.
Salida (<Python3.7)
> >> 'a' * 20 is 'aaaaaaaaaaaaaaaaaaaa'
True
> >> 'a' * 21 is 'aaaaaaaaaaaaaaaaaaaaa'
False
Tiene sentido, ¿verdad?
'wtf'
se internará pero ''.join(['w', 't', 'f'])
no se internará)'wtf!'
no fue internado debido a !
. La implementación de CPython de esta regla se puede encontrar aquí a
y b
están configurados en "wtf!"
En la misma línea, el intérprete de Python crea un nuevo objeto y luego hace referencia a la segunda variable al mismo tiempo. Si lo hace en líneas separadas, no "sabe" que ya existe un "wtf!"
como un objeto (porque "wtf!"
no está implícitamente internado según los hechos mencionados anteriormente). Es una optimización en tiempo de compilación. Esta optimización no se aplica a las versiones 3.7.x de CPython (consulte este problema para obtener más información).a, b = "wtf!", "wtf!"
es una declaración única, mientras que a = "wtf!"; b = "wtf!"
Son dos declaraciones en una sola línea. Esto explica por qué las identidades son diferentes en a = "wtf!"; b = "wtf!"
y también explica por qué son iguales cuando se invocan en some_file.py
'a'*20
se reemplaza por 'aaaaaaaaaaaaaaaaaaaa'
durante la compilación para ahorrar algunos ciclos de reloj durante el tiempo de ejecución. El plegado constante solo ocurre para cadenas que tienen una longitud inferior a 21. (¿Por qué? Imagine el tamaño del archivo .pyc
generado como resultado de la expresión 'a'*10**10
). Aquí está la fuente de implementación para lo mismo. > >> ( False == False ) in [ False ] # makes sense
False
> >> False == ( False in [ False ]) # makes sense
False
> >> False == False in [ False ] # now what?
True
> >> True is False == False
False
> >> False is False is False
True
> >> 1 > 0 < 1
True
> >> ( 1 > 0 ) < 1
False
> >> 1 > ( 0 < 1 )
False
Según https://docs.python.org/3/reference/expressions.html#comparisons
Formalmente, si a, b, c, ..., y, z son expresiones y op1, op2, ..., opN son operadores de comparación, entonces a op1 b op2 c ... y opN z es equivalente a a op1 b y b op2 c y ... y opN z, excepto que cada expresión se evalúa como máximo una vez.
Si bien tal comportamiento puede parecerle tonto en los ejemplos anteriores, es fantástico con cosas como a == b == c
y 0 <= x <= 100
.
False is False is False
es equivalente a (False is False) and (False is False)
True is False == False
es equivalente a (True is False) and (False == False)
y dado que la primera parte de la declaración ( True is False
) se evalúa como False
, la expresión general se evalúa como False
.1 > 0 < 1
es equivalente a (1 > 0) and (0 < 1)
que se evalúa como True
.(1 > 0) < 1
es equivalente a True < 1
y > >> int ( True )
1
> >> True + 1 #not relevant for this example, but just for fun
2
1 < 1
se evalúa como False
is
operadorEl siguiente es un ejemplo muy famoso presente en Internet.
1.
> >> a = 256
> >> b = 256
> >> a is b
True
> >> a = 257
> >> b = 257
> >> a is b
False
2.
> >> a = []
> >> b = []
> >> a is b
False
> >> a = tuple ()
> >> b = tuple ()
> >> a is b
True
3. Salida
> >> a , b = 257 , 257
> >> a is b
True
Salida (Python 3.7.x específicamente)
> >> a , b = 257 , 257
> >> a is b
False
La diferencia entre is
y ==
is
operador comprueba si ambos operandos se refieren al mismo objeto (es decir, comprueba si la identidad de los operandos coincide o no).==
compara los valores de ambos operandos y comprueba si son iguales.is
es para igualdad de referencia y ==
es para igualdad de valores. Un ejemplo para aclarar las cosas, > >> class A : pass
> >> A () is A () # These are two empty objects at two different memory locations.
False
256
es un objeto existente pero 257
no lo es
Cuando inicie Python, se asignarán los números del -5
al 256
. Estos números se utilizan mucho, por lo que tiene sentido tenerlos listos.
Citando de https://docs.python.org/3/c-api/long.html
La implementación actual mantiene una matriz de objetos enteros para todos los números enteros entre -5 y 256, cuando creas un int en ese rango simplemente obtienes una referencia al objeto existente. Por lo tanto, debería ser posible cambiar el valor de 1. Sospecho que el comportamiento de Python, en este caso, no está definido. :-)
> >> id ( 256 )
10922528
> >> a = 256
> >> b = 256
> >> id ( a )
10922528
> >> id ( b )
10922528
> >> id ( 257 )
140084850247312
> >> x = 257
> >> y = 257
> >> id ( x )
140084850247440
> >> id ( y )
140084850247344
Aquí el intérprete no es lo suficientemente inteligente al ejecutar y = 257
para reconocer que ya hemos creado un número entero con el valor 257,
por lo que crea otro objeto en la memoria.
Una optimización similar también se aplica a otros objetos inmutables como las tuplas vacías. Dado que las listas son mutables, es por eso que [] is []
devolverá False
y () is ()
devolverá True
. Esto explica nuestro segundo fragmento. Pasemos al tercero,
Tanto a
como b
se refieren al mismo objeto cuando se inicializan con el mismo valor en la misma línea.
Producción
> >> a , b = 257 , 257
> >> id ( a )
140640774013296
> >> id ( b )
140640774013296
> >> a = 257
> >> b = 257
> >> id ( a )
140640774013392
> >> id ( b )
140640774013488
Cuando a y b se establecen en 257
en la misma línea, el intérprete de Python crea un nuevo objeto y luego hace referencia a la segunda variable al mismo tiempo. Si lo hace en líneas separadas, no "sabe" que ya existe 257
como objeto.
Es una optimización del compilador y se aplica específicamente al entorno interactivo. Cuando ingresa dos líneas en un intérprete en vivo, se compilan por separado y, por lo tanto, se optimizan por separado. Si probara este ejemplo en un archivo .py
, no vería el mismo comportamiento, porque el archivo se compila todo a la vez. Esta optimización no se limita a números enteros, funciona para otros tipos de datos inmutables como cadenas (consulte el ejemplo "Las cadenas son complicadas") y flotantes también.
> >> a , b = 257.0 , 257.0
> >> a is b
True
¿Por qué esto no funcionó para Python 3.7? La razón abstracta es que dichas optimizaciones del compilador son específicas de la implementación (es decir, pueden cambiar según la versión, el sistema operativo, etc.). Todavía estoy averiguando qué cambio de implementación exacto causa el problema; puede consultar este problema para obtener actualizaciones.
1.
some_dict = {}
some_dict [ 5.5 ] = "JavaScript"
some_dict [ 5.0 ] = "Ruby"
some_dict [ 5 ] = "Python"
Producción:
> >> some_dict [ 5.5 ]
"JavaScript"
> >> some_dict [ 5.0 ] # "Python" destroyed the existence of "Ruby"?
"Python"
> >> some_dict [ 5 ]
"Python"
> >> complex_five = 5 + 0j
> >> type ( complex_five )
complex
> >> some_dict [ complex_five ]
"Python"
Entonces, ¿por qué Python está por todos lados?
La unicidad de las claves en un diccionario de Python se debe a la equivalencia , no a la identidad. Entonces, aunque 5
, 5.0
y 5 + 0j
son objetos distintos de diferentes tipos, dado que son iguales, no pueden estar ambos en el mismo dict
(o set
). Tan pronto como inserte cualquiera de ellos, intentar buscar cualquier clave distinta pero equivalente tendrá éxito con el valor asignado original (en lugar de fallar con un KeyError
):
> >> 5 == 5.0 == 5 + 0j
True
> >> 5 is not 5.0 is not 5 + 0j
True
> >> some_dict = {}
> >> some_dict [ 5.0 ] = "Ruby"
> >> 5.0 in some_dict
True
> >> ( 5 in some_dict ) and ( 5 + 0j in some_dict )
True
Esto también se aplica al configurar un elemento. Entonces, cuando haces some_dict[5] = "Python"
, Python encuentra el elemento existente con la clave equivalente 5.0 -> "Ruby"
, sobrescribe su valor en su lugar y deja la clave original como está.
> >> some_dict
{ 5.0 : 'Ruby' }
> >> some_dict [ 5 ] = "Python"
> >> some_dict
{ 5.0 : 'Python' }
Entonces, ¿cómo podemos actualizar la clave a 5
(en lugar de 5.0
)? En realidad, no podemos realizar esta actualización en el lugar, pero lo que podemos hacer es primero eliminar la clave ( del some_dict[5.0]
) y luego configurarla ( some_dict[5]
) para obtener el entero 5
como clave en lugar de flotante. 5.0
, aunque esto debería ser necesario en casos excepcionales.
¿Cómo encontró Python 5
en un diccionario que contiene 5.0
? Python hace esto en tiempo constante sin tener que escanear cada elemento mediante funciones hash. Cuando Python busca una clave foo
en un dict, primero calcula hash(foo)
(que se ejecuta en tiempo constante). Dado que en Python se requiere que los objetos que se comparan iguales también tengan el mismo valor hash (documentos aquí), 5
, 5.0
y 5 + 0j
tengan el mismo valor hash.
> >> 5 == 5.0 == 5 + 0j
True
> >> hash ( 5 ) == hash ( 5.0 ) == hash ( 5 + 0j )
True
Nota: Lo inverso no es necesariamente cierto: los objetos con valores hash iguales pueden ser a su vez desiguales. (Esto provoca lo que se conoce como colisión de hash y degrada el rendimiento de tiempo constante que suele proporcionar el hash).
class WTF :
pass
Producción:
> >> WTF () == WTF () # two different instances can't be equal
False
> >> WTF () is WTF () # identities are also different
False
> >> hash ( WTF ()) == hash ( WTF ()) # hashes _should_ be different as well
True
> >> id ( WTF ()) == id ( WTF ())
True
Cuando se llamó id
, Python creó un objeto de clase WTF
y lo pasó a la función id
. La función id
toma su id
(su ubicación de memoria) y descarta el objeto. El objeto está destruido.
Cuando hacemos esto dos veces seguidas, Python también asigna la misma ubicación de memoria a este segundo objeto. Dado que (en CPython) id
usa la ubicación de la memoria como identificación del objeto, la identificación de los dos objetos es la misma.
Por lo tanto, la identificación del objeto es única solo durante la vida útil del objeto. Después de que se destruye el objeto, o antes de que se cree, otra cosa puede tener la misma identificación.
Pero, ¿por qué el operador is
evaluó como False
? Veamos con este fragmento.
class WTF ( object ):
def __init__ ( self ): print ( "I" )
def __del__ ( self ): print ( "D" )
Producción:
> >> WTF () is WTF ()
I
I
D
D
False
> >> id ( WTF ()) == id ( WTF ())
I
D
I
D
True
Como puedes observar, el orden en el que se destruyen los objetos es lo que marcó la diferencia aquí.
from collections import OrderedDict
dictionary = dict ()
dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
ordered_dict = OrderedDict ()
ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
another_ordered_dict = OrderedDict ()
another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
class DictWithHash ( dict ):
"""
A dict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
class OrderedDictWithHash ( OrderedDict ):
"""
An OrderedDict that also implements __hash__ magic.
"""
__hash__ = lambda self : 0
Producción
> >> dictionary == ordered_dict # If a == b
True
> >> dictionary == another_ordered_dict # and b == c
True
> >> ordered_dict == another_ordered_dict # then why isn't c == a ??
False
# We all know that a set consists of only unique elements,
# let's try making a set of these dictionaries and see what happens...
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
TypeError : unhashable type : 'dict'
# Makes sense since dict don't have __hash__ implemented, let's use
# our wrapper classes.
> >> dictionary = DictWithHash ()
> >> dictionary [ 1 ] = 'a' ; dictionary [ 2 ] = 'b' ;
> >> ordered_dict = OrderedDictWithHash ()
> >> ordered_dict [ 1 ] = 'a' ; ordered_dict [ 2 ] = 'b' ;
> >> another_ordered_dict = OrderedDictWithHash ()
> >> another_ordered_dict [ 2 ] = 'b' ; another_ordered_dict [ 1 ] = 'a' ;
> >> len ({ dictionary , ordered_dict , another_ordered_dict })
1
> >> len ({ ordered_dict , another_ordered_dict , dictionary }) # changing the order
2
¿Qué está pasando aquí?
La razón por la que la igualdad intransitiva no se cumple entre dictionary
, ordered_dict
y another_ordered_dict
es por la forma en que se implementa el método __eq__
en la clase OrderedDict
. De los documentos
Las pruebas de igualdad entre objetos OrderedDict dependen del orden y se implementan como
list(od1.items())==list(od2.items())
. Las pruebas de igualdad entre objetosOrderedDict
y otros objetos Mapping no tienen en cuenta el orden, como los diccionarios normales.
La razón de esta igualdad de comportamiento es que permite sustituir directamente los objetos OrderedDict
en cualquier lugar donde se utilice un diccionario normal.
Bien, entonces, ¿por qué cambiar el orden afectó la longitud del objeto set
generado? La respuesta es únicamente la falta de igualdad intransitiva. Dado que los conjuntos son colecciones "desordenadas" de elementos únicos, el orden en el que se insertan los elementos no debería importar. Pero en este caso sí importa. Analicémoslo un poco,
> >> some_set = set ()
> >> some_set . add ( dictionary ) # these are the mapping objects from the snippets above
> >> ordered_dict in some_set
True
> >> some_set . add ( ordered_dict )
> >> len ( some_set )
1
> >> another_ordered_dict in some_set
True
> >> some_set . add ( another_ordered_dict )
> >> len ( some_set )
1
> >> another_set = set ()
> >> another_set . add ( ordered_dict )
> >> another_ordered_dict in another_set
False
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
> >> dictionary in another_set
True
> >> another_set . add ( another_ordered_dict )
> >> len ( another_set )
2
Entonces, la inconsistencia se debe a que another_ordered_dict in another_set
es False
ordered_dict
ya estaba presente en another_set
y, como se observó antes, ordered_dict == another_ordered_dict
es False
.
def some_func ():
try :
return 'from_try'
finally :
return 'from_finally'
def another_func ():
for _ in range ( 3 ):
try :
continue
finally :
print ( "Finally!" )
def one_more_func (): # A gotcha!
try :
for i in range ( 3 ):
try :
1 / i
except ZeroDivisionError :
# Let's throw it here and handle it outside for loop
raise ZeroDivisionError ( "A trivial divide by zero error" )
finally :
print ( "Iteration" , i )
break
except ZeroDivisionError as e :
print ( "Zero division error occurred" , e )
Producción:
> >> some_func ()
'from_finally'
> >> another_func ()
Finally !
Finally !
Finally !
>> > 1 / 0
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
ZeroDivisionError : division by zero
> >> one_more_func ()
Iteration 0
return
, break
o continue
en el conjunto try
de una declaración "try...finally", la cláusula finally
también se ejecuta al salir.return
ejecutada. Dado que la cláusula finally
siempre se ejecuta, una declaración return
ejecutada en la cláusula finally
siempre será la última que se ejecute.return
o break
, la excepción guardada temporalmente se descarta. some_string = "wtf"
some_dict = {}
for i , some_dict [ i ] in enumerate ( some_string ):
i = 10
Producción:
> >> some_dict # An indexed dict appears.
{ 0 : 'w' , 1 : 't' , 2 : 'f' }
Una declaración for
se define en la gramática de Python como:
for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite]
Donde exprlist
es el objetivo de la asignación. Esto significa que se ejecuta el equivalente de {exprlist} = {next_value}
para cada elemento del iterable. Un ejemplo interesante que ilustra esto:
for i in range ( 4 ):
print ( i )
i = 10
Producción:
0
1
2
3
¿Esperabas que el ciclo se ejecutara solo una vez?
Explicación:
i = 10
nunca afecta las iteraciones del bucle debido a la forma en que funcionan los bucles for en Python. Antes del comienzo de cada iteración, el siguiente elemento proporcionado por el iterador ( range(4)
en este caso) se descomprime y se le asignan las variables de la lista de destino ( i
en este caso). La función enumerate(some_string)
produce un nuevo valor i
(un contador que aumenta) y un carácter de some_string
en cada iteración. Luego establece la clave i
(recién asignada) del diccionario some_dict
para ese carácter. El desenrollado del bucle se puede simplificar como:
> >> i , some_dict [ i ] = ( 0 , 'w' )
> >> i , some_dict [ i ] = ( 1 , 't' )
> >> i , some_dict [ i ] = ( 2 , 'f' )
> >> some_dict
1.
array = [ 1 , 8 , 15 ]
# A typical generator expression
gen = ( x for x in array if array . count ( x ) > 0 )
array = [ 2 , 8 , 22 ]
Producción:
> >> print ( list ( gen )) # Where did the other values go?
[ 8 ]
2.
array_1 = [ 1 , 2 , 3 , 4 ]
gen_1 = ( x for x in array_1 )
array_1 = [ 1 , 2 , 3 , 4 , 5 ]
array_2 = [ 1 , 2 , 3 , 4 ]
gen_2 = ( x for x in array_2 )
array_2 [:] = [ 1 , 2 , 3 , 4 , 5 ]
Producción:
> >> print ( list ( gen_1 ))
[ 1 , 2 , 3 , 4 ]
> >> print ( list ( gen_2 ))
[ 1 , 2 , 3 , 4 , 5 ]
3.
array_3 = [ 1 , 2 , 3 ]
array_4 = [ 10 , 20 , 30 ]
gen = ( i + j for i in array_3 for j in array_4 )
array_3 = [ 4 , 5 , 6 ]
array_4 = [ 400 , 500 , 600 ]
Producción:
> >> print ( list ( gen ))
[ 401 , 501 , 601 , 402 , 502 , 602 , 403 , 503 , 603 ]
En una expresión generadora, la cláusula in
se evalúa en el momento de la declaración, pero la cláusula condicional se evalúa en el tiempo de ejecución.
Entonces, antes del tiempo de ejecución, array
se reasigna a la lista [2, 8, 22]
y, dado que de 1
, 8
y 15
, solo el recuento de 8
es mayor que 0
, el generador solo produce 8
.
Las diferencias en la salida de g1
y g2
en la segunda parte se deben a la forma en que se reasignan los valores de las variables array_1
y array_2
.
En el primer caso, array_1
está vinculado al nuevo objeto [1,2,3,4,5]
y dado que la cláusula in
se evalúa en el momento de la declaración, todavía se refiere al objeto antiguo [1,2,3,4]
(que no se destruye).
En el segundo caso, la asignación de sectores a array_2
actualiza el mismo objeto antiguo [1,2,3,4]
a [1,2,3,4,5]
. Por lo tanto, tanto g2
como array_2
todavía tienen referencia al mismo objeto (que ahora se ha actualizado a [1,2,3,4,5]
).
Bien, siguiendo la lógica discutida hasta ahora, ¿no debería ser el valor de list(gen)
en el tercer fragmento [11, 21, 31, 12, 22, 32, 13, 23, 33]
? (porque array_3
y array_4
se comportarán igual que array_1
). La razón por la cual (solo) se actualizaron los valores array_4
se explica en PEP-289
Solo la expresión for más externa se evalúa inmediatamente, las otras expresiones se difieren hasta que se ejecuta el generador.
is not ...
no is (not ...)
> >> 'something' is not None
True
> >> 'something' is ( not None )
False
is not
es un operador binario único y tiene un comportamiento diferente al uso is
y not
separado.is not
se evalúa como False
si las variables a ambos lados del operador apuntan al mismo objeto y True
en caso contrario.(not None)
se evalúa como True
ya que el valor None
es False
en un contexto booleano, por lo que la expresión se convierte en 'something' is True
. # Let's initialize a row
row = [ "" ] * 3 #row i['', '', '']
# Let's make a board
board = [ row ] * 3
Producción:
> >> board
[[ '' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
> >> board [ 0 ]
[ '' , '' , '' ]
> >> board [ 0 ][ 0 ]
''
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ 'X' , '' , '' ], [ 'X' , '' , '' ]]
No asignamos tres "X"
, ¿verdad?
Cuando inicializamos la variable row
, esta visualización explica lo que sucede en la memoria.
Y cuando el board
se inicializa multiplicando la row
, esto es lo que sucede dentro de la memoria (cada uno de los elementos board[0]
, board[1]
y board[2]
es una referencia a la misma lista referida por row
)
Podemos evitar este escenario aquí si no utilizamos la variable row
para generar board
. (Preguntado en este número).
> >> board = [[ '' ] * 3 for _ in range ( 3 )]
> >> board [ 0 ][ 0 ] = "X"
> >> board
[[ 'X' , '' , '' ], [ '' , '' , '' ], [ '' , '' , '' ]]
funcs = []
results = []
for x in range ( 7 ):
def some_func ():
return x
funcs . append ( some_func )
results . append ( some_func ()) # note the function call here
funcs_results = [ func () for func in funcs ]
Salida (versión Python):
> >> results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> funcs_results
[ 6 , 6 , 6 , 6 , 6 , 6 , 6 ]
Los valores de x
eran diferentes en cada iteración antes de agregar some_func
a funcs
, pero todas las funciones devuelven 6 cuando se evalúan una vez que se completa el ciclo.
> >> powers_of_x = [ lambda x : x ** i for i in range ( 10 )]
> >> [ f ( 2 ) for f in powers_of_x ]
[ 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 , 512 ]
x
en el contexto circundante, en lugar de utilizar el valor de x
en el momento en que se crea la función. Entonces, todas las funciones usan el último valor asignado a la variable para el cálculo. Podemos ver que está usando la x
del contexto circundante (es decir, no una variable local) con: > >> import inspect
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = { 'x' : 6 }, builtins = {}, unbound = set ())
Dado que x
es un valor global, podemos cambiar el valor que las funcs
buscarán y devolverán actualizando x
:
> >> x = 42
> >> [ func () for func in funcs ]
[ 42 , 42 , 42 , 42 , 42 , 42 , 42 ]
x
en ese momento. funcs = []
for x in range ( 7 ):
def some_func ( x = x ):
return x
funcs . append ( some_func )
Producción:
> >> funcs_results = [ func () for func in funcs ]
> >> funcs_results
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
Ya no se utiliza la x
en el ámbito global:
> >> inspect . getclosurevars ( funcs [ 0 ])
ClosureVars ( nonlocals = {}, globals = {}, builtins = {}, unbound = set ())
1.
> >> isinstance ( 3 , int )
True
> >> isinstance ( type , object )
True
> >> isinstance ( object , type )
True
Entonces, ¿cuál es la clase base "definitiva"? Por cierto, hay más confusión,
2.
> >> class A : pass
> >> isinstance ( A , A )
False
> >> isinstance ( type , type )
True
> >> isinstance ( object , object )
True
3.
> >> issubclass ( int , object )
True
> >> issubclass ( type , object )
True
> >> issubclass ( object , type )
False
type
es una metaclase en Python.object
en Python, que incluye clases así como sus objetos (instancias).type
de clase es la metaclase de la clase object
, y cada clase (incluido type
) ha heredado directa o indirectamente del object
.object
y type
. La confusión en los fragmentos anteriores surge porque estamos pensando en estas relaciones ( issubclass
e isinstance
) en términos de clases de Python. La relación entre object
y type
no se puede reproducir en Python puro. Para ser más precisos, las siguientes relaciones no se pueden reproducir en Python puro,object
y type
(ambos son instancias entre sí y de sí mismos) existen en Python debido a las "trampas" en el nivel de implementación.Producción:
> >> from collections . abc import Hashable
> >> issubclass ( list , object )
True
> >> issubclass ( object , Hashable )
True
> >> issubclass ( list , Hashable )
False
Se esperaba que las relaciones de subclase fueran transitivas, ¿verdad? (es decir, si A
es una subclase de B
y B
es una subclase de C
, A
debería ser una subclase de C
)
__subclasscheck__
arbitrario en una metaclase.issubclass(cls, Hashable)
, simplemente busca el método " __hash__
" que no sea Falsey en cls
o cualquier cosa de la que herede.object
se puede controlar, pero list
no se puede controlar, se rompe la relación de transitividad. class SomeClass :
def method ( self ):
pass
@ classmethod
def classm ( cls ):
pass
@ staticmethod
def staticm ():
pass
Producción:
> >> print ( SomeClass . method is SomeClass . method )
True
> >> print ( SomeClass . classm is SomeClass . classm )
False
> >> print ( SomeClass . classm == SomeClass . classm )
True
> >> print ( SomeClass . staticm is SomeClass . staticm )
True
Al acceder classm
dos veces, ¿obtenemos un objeto igual, pero no el mismo ? Veamos qué sucede con las instancias de SomeClass
:
o1 = SomeClass ()
o2 = SomeClass ()
Producción:
> >> print ( o1 . method == o2 . method )
False
> >> print ( o1 . method == o1 . method )
True
> >> print ( o1 . method is o1 . method )
False
> >> print ( o1 . classm is o1 . classm )
False
> >> print ( o1 . classm == o1 . classm == o2 . classm == SomeClass . classm )
True
> >> print ( o1 . staticm is o1 . staticm is o2 . staticm is SomeClass . staticm )
True
Al acceder classm
o method
dos veces, se crean objetos iguales pero no iguales para la misma instancia de SomeClass
.
self
como primer argumento, a pesar de no pasarlo explícitamente). > >> o1 . method
< bound method SomeClass . method of < __main__ . SomeClass object at ... >>
o1.method is o1.method
nunca es verdadero. Sin embargo, acceder a funciones como atributos de clase (a diferencia de instancias) no crea métodos; entonces SomeClass.method is SomeClass.method
es veraz. > >> SomeClass . method
< function SomeClass . method at ... >
classmethod
transforma funciones en métodos de clase. Los métodos de clase son descriptores que, cuando se accede a ellos, crean un objeto de método que vincula la clase (tipo) del objeto, en lugar del objeto en sí. > >> o1 . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
classmethod
crearán un método también cuando se acceda a ellos como atributos de clase (en cuyo caso vinculan la clase, no su tipo). Entonces SomeClass.classm is SomeClass.classm
es falso. > >> SomeClass . classm
< bound method SomeClass . classm of < class '__main__.SomeClass' >>
o1.method == o1.method
es veraz, aunque no es el mismo objeto en la memoria.staticmethod
transforma funciones en un descriptor "no operativo", que devuelve la función tal como está. Nunca se crean objetos de método, por lo que la comparación con is
es veraz. > >> o1 . staticm
< function SomeClass . staticm at ... >
> >> SomeClass . staticm
< function SomeClass . staticm at ... >
self
negativamente. CPython 3.7 lo resolvió introduciendo nuevos códigos de operación que se ocupan de llamar a métodos sin crear objetos de método temporales. Esto se usa solo cuando realmente se llama a la función a la que se accede, por lo que los fragmentos aquí no se ven afectados y aún generan métodos :) > >> all ([ True , True , True ])
True
> >> all ([ True , True , False ])
False
> >> all ([])
True
> >> all ([[]])
False
> >> all ([[[]]])
True
¿Por qué esta alteración Verdadero-Falso?
La implementación de all
las funciones es equivalente a
def all ( iterable ):
for element in iterable :
if not element :
return False
return True
all([])
devuelve True
ya que el iterable está vacío.
all([[]])
devuelve False
porque la matriz pasada tiene un elemento, []
, y en Python, una lista vacía es falsa.
all([[[]]])
y las variantes recursivas superiores siempre son True
. Esto se debe a que el elemento único de la matriz pasada ( [[...]]
) ya no está vacío y las listas con valores son veraces.
Salida (< 3,6):
> >> def f ( x , y ,):
... print ( x , y )
...
> >> def g ( x = 4 , y = 5 ,):
... print ( x , y )
...
> >> def h ( x , ** kwargs ,):
File "<stdin>" , line 1
def h ( x , ** kwargs ,):
^
SyntaxError : invalid syntax
>> > def h ( * args ,):
File "<stdin>" , line 1
def h ( * args ,):
^
SyntaxError : invalid syntax
Producción:
> >> print ( " " " )
"
>> > print ( r""" )
"
>> > print ( r" " )
File "<stdin>" , line 1
print ( r" " )
^
SyntaxError : EOL while scanning string literal
>> > r''' == " \ '"
True
> >> "wt " f"
'wt"f'
r
), las barras invertidas se pasan tal cual junto con el comportamiento de escapar del siguiente carácter. > >> r'wt"f' == 'wt \ "f'
True
> >> print ( repr ( r'wt"f' ))
'wt \ "f'
> >> print ( " n " )
> >> print ( r"\n" )
' \ n'
print(r"")
), la barra invertida escapó de la comilla final, dejando al analizador sin una comilla final (de ahí el SyntaxError
). Es por eso que las barras invertidas no funcionan al final de una cadena sin formato. x = True
y = False
Producción:
> >> not x == y
True
> >> x == not y
File "<input>" , line 1
x == not y
^
SyntaxError : invalid syntax
==
tiene mayor precedencia que el operador not
en Python.not x == y
es equivalente a not (x == y)
que es equivalente a not (True == False)
y finalmente se evalúa como True
.x == not y
genera un SyntaxError
porque se puede pensar que es equivalente a (x == not) y
y no a x == (not y)
lo que podría haber esperado a primera vista.not
fuera parte del operador not in
(porque ambos operadores ==
y not in
tienen la misma precedencia), pero después de no poder encontrar un token in
después del token not
, genera un SyntaxError
.Producción:
> >> print ( 'wtfpython' '' )
wtfpython
> >> print ( "wtfpython" "" )
wtfpython
> >> # The following statements raise `SyntaxError`
>> > # print('''wtfpython')
>> > # print("""wtfpython")
File "<input>" , line 3
print ("""wtfpython")
^
SyntaxError: EOF while scanning triple-quoted string literal
>>> print("wtf" "python")
wtfpython
>>> print("wtf" "") # or "wtf"""
wtf
'''
y """
también son delimitadores de cadenas en Python, lo que provoca un error de sintaxis porque el intérprete de Python esperaba una comilla triple final como delimitador mientras escaneaba la cadena literal con comillas triples encontrada actualmente.1.
# A simple example to count the number of booleans and
# integers in an iterable of mixed data types.
mixed_list = [ False , 1.0 , "some_string" , 3 , True , [], False ]
integers_found_so_far = 0
booleans_found_so_far = 0
for item in mixed_list :
if isinstance ( item , int ):
integers_found_so_far += 1
elif isinstance ( item , bool ):
booleans_found_so_far += 1
Producción:
> >> integers_found_so_far
4
> >> booleans_found_so_far
0
2.
> >> some_bool = True
> >> "wtf" * some_bool
'wtf'
> >> some_bool = False
> >> "wtf" * some_bool
''
3.
def tell_truth ():
True = False
if True == False :
print ( "I have lost faith in truth!" )
Salida (< 3.x):
> >> tell_truth ()
I have lost faith in truth !
bool
es una subclase de int
en Python
> >> issubclass ( bool , int )
True
> >> issubclass ( int , bool )
False
Y así, True
y False
son instancias de int
> >> isinstance ( True , int )
True
> >> isinstance ( False , int )
True
El valor entero de True
es 1
y el de False
es 0
.
> >> int ( True )
1
> >> int ( False )
0
Consulte esta respuesta de StackOverflow para conocer el fundamento de esto.
Inicialmente, Python no tenía ningún tipo bool
(la gente usaba 0 para falso y un valor distinto de cero, como 1 para verdadero). True
, False
y un tipo bool
se agregaron en las versiones 2.x, pero, por compatibilidad con versiones anteriores, True
y False
no se pudieron convertir en constantes. Simplemente eran variables integradas y era posible reasignarlas.
Python 3 era incompatible con versiones anteriores, el problema finalmente se solucionó y, por lo tanto, el último fragmento no funcionará con Python 3.x.
1.
class A :
x = 1
class B ( A ):
pass
class C ( A ):
pass
Producción:
> >> A . x , B . x , C . x
( 1 , 1 , 1 )
>> > B . x = 2
>> > A . x , B . x , C . x
( 1 , 2 , 1 )
>> > A . x = 3
>> > A . x , B . x , C . x # C.x changed, but B.x didn't
( 3 , 2 , 3 )
>> > a = A ()
>> > a . x , A . x
( 3 , 3 )
>> > a . x + = 1
>> > a . x , A . x
( 4 , 3 )
2.
class SomeClass :
some_var = 15
some_list = [ 5 ]
another_list = [ 5 ]
def __init__ ( self , x ):
self . some_var = x + 1
self . some_list = self . some_list + [ x ]
self . another_list += [ x ]
Producción:
> >> some_obj = SomeClass ( 420 )
> >> some_obj . some_list
[ 5 , 420 ]
> >> some_obj . another_list
[ 5 , 420 ]
> >> another_obj = SomeClass ( 111 )
> >> another_obj . some_list
[ 5 , 111 ]
> >> another_obj . another_list
[ 5 , 420 , 111 ]
> >> another_obj . another_list is SomeClass . another_list
True
> >> another_obj . another_list is some_obj . another_list
True
+=
modifica el objeto mutable in situ sin crear un objeto nuevo. Entonces, cambiar el atributo de una instancia afecta a las otras instancias y también al atributo de clase. some_iterable = ( 'a' , 'b' )
def some_func ( val ):
return "something"
Salida (<= 3.7.x):
> >> [ x for x in some_iterable ]
[ 'a' , 'b' ]
> >> [( yield x ) for x in some_iterable ]
< generator object < listcomp > at 0x7f70b0a4ad58 >
> >> list ([( yield x ) for x in some_iterable ])
[ 'a' , 'b' ]
> >> list (( yield x ) for x in some_iterable )
[ 'a' , None , 'b' , None ]
> >> list ( some_func (( yield x )) for x in some_iterable )
[ 'a' , 'something' , 'b' , 'something' ]
yield
de CPython en generadores y comprensiones.yield
dentro y arrojará un SyntaxError
.1.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
yield from range ( x )
Salida (> 3.3):
> >> list ( some_func ( 3 ))
[]
¿A donde se fue el "wtf"
? ¿Se debe a algún efecto especial del yield from
? Validemos eso,
2.
def some_func ( x ):
if x == 3 :
return [ "wtf" ]
else :
for i in range ( x ):
yield i
Producción:
> >> list ( some_func ( 3 ))
[]
El mismo resultado, esto tampoco funcionó.
return
con valores dentro de los generadores (consulte PEP380). Los documentos oficiales dicen que,"...
return expr
en un generador hace queStopIteration(expr)
se genere al salir del generador".
En el caso de some_func(3)
, StopIteration
se genera al principio debido a la declaración return
. La excepción StopIteration
se detecta automáticamente dentro del contenedor list(...)
y el bucle for
. Por lo tanto, los dos fragmentos anteriores dan como resultado una lista vacía.
Para obtener ["wtf"]
del generador some_func
necesitamos detectar la excepción StopIteration
,
try :
next ( some_func ( 3 ))
except StopIteration as e :
some_string = e . value
> >> some_string
[ "wtf" ]
1.
a = float ( 'inf' )
b = float ( 'nan' )
c = float ( '-iNf' ) # These strings are case-insensitive
d = float ( 'nan' )
Producción:
> >> a
inf
> >> b
nan
> >> c
- inf
> >> float ( 'some_other_string' )
ValueError : could not convert string to float : some_other_string
> >> a == - c # inf==inf
True
> >> None == None # None == None
True
> >> b == d # but nan!=nan
False
> >> 50 / a
0.0
> >> a / a
nan
> >> 23 + b
nan
2.
> >> x = float ( 'nan' )
> >> y = x / x
> >> y is y # identity holds
True
> >> y == y # equality fails of y
False
> >> [ y ] == [ y ] # but the equality succeeds for the list containing y
True
'inf'
y 'nan'
son cadenas especiales (no distinguen entre mayúsculas y minúsculas) que, cuando se encasillan explícitamente en tipo float
, se utilizan para representar el "infinito" matemático y "no un número", respectivamente.
Dado que según los estándares IEEE NaN != NaN
, obedecer esta regla rompe el supuesto de reflexividad de un elemento de colección en Python, es decir, si x
es parte de una list
similar a una colección, las implementaciones como la comparación se basan en el supuesto de que x == x
. Debido a esta suposición, la identidad se compara primero (ya que es más rápido) mientras se comparan dos elementos, y los valores se comparan solo cuando las identidades no coinciden. El siguiente fragmento aclarará las cosas.
> >> x = float ( 'nan' )
> >> x == x , [ x ] == [ x ]
( False , True )
> >> y = float ( 'nan' )
> >> y == y , [ y ] == [ y ]
( False , True )
> >> x == y , [ x ] == [ y ]
( False , False )
Como las identidades de x
e y
son diferentes, se consideran los valores, que también son diferentes; por lo tanto, la comparación devuelve False
esta vez.
Lectura interesante: Reflexividad y otros pilares de la civilización.
Esto puede parecer trivial si sabes cómo funcionan las referencias en Python.
some_tuple = ( "A" , "tuple" , "with" , "values" )
another_tuple = ([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 ])
Producción:
> >> some_tuple [ 2 ] = "change this"
TypeError : 'tuple' object does not support item assignment
> >> another_tuple [ 2 ]. append ( 1000 ) #This throws no error
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 ])
> >> another_tuple [ 2 ] += [ 99 , 999 ]
TypeError : 'tuple' object does not support item assignment
> >> another_tuple
([ 1 , 2 ], [ 3 , 4 ], [ 5 , 6 , 1000 , 99 , 999 ])
Pero pensé que las tuplas eran inmutables...
Citando de https://docs.python.org/3/reference/datamodel.html
Secuencias inmutables Un objeto de un tipo de secuencia inmutable no puede cambiar una vez creado. (Si el objeto contiene referencias a otros objetos, estos otros objetos pueden ser mutables y pueden modificarse; sin embargo, la colección de objetos a los que hace referencia directamente un objeto inmutable no puede cambiar).
El operador +=
cambia la lista in situ. La asignación de elementos no funciona, pero cuando se produce la excepción, el elemento ya se ha modificado.
También hay una explicación en las preguntas frecuentes oficiales de Python.
e = 7
try :
raise Exception ()
except Exception as e :
pass
Salida (Python 2.x):
> >> print ( e )
# prints nothing
Salida (Python 3.x):
> >> print ( e )
NameError : name 'e' is not defined
Fuente: https://docs.python.org/3/reference/compound_stmts.html#except
Cuando se ha asignado una excepción usando as
target, se borra al final de la cláusula except
. Esto es como si
except E as N :
foo
fue traducido a
except E as N :
try :
foo
finally :
del N
Esto significa que a la excepción se le debe asignar un nombre diferente para poder hacer referencia a ella después de la cláusula except. Las excepciones se borran porque, con el rastreo adjunto, forman un ciclo de referencia con el marco de la pila, manteniendo vivos todos los locales en ese marco hasta que se produzca la siguiente recolección de basura.
Las cláusulas no tienen alcance en Python. Todo en el ejemplo está presente en el mismo ámbito y la variable e
se eliminó debido a la ejecución de la cláusula except
. No ocurre lo mismo con las funciones que tienen sus ámbitos internos separados. El siguiente ejemplo ilustra esto:
def f ( x ):
del ( x )
print ( x )
x = 5
y = [ 5 , 4 , 3 ]
Producción:
> >> f ( x )
UnboundLocalError : local variable 'x' referenced before assignment
>> > f ( y )
UnboundLocalError : local variable 'x' referenced before assignment
>> > x
5
> >> y
[ 5 , 4 , 3 ]
En Python 2.x, el nombre de la variable e
se asigna a la instancia Exception()
, por lo que cuando intenta imprimir, no imprime nada.
Salida (python 2.x):
> >> e
Exception ()
> >> print e
# Nothing is printed!
class SomeClass ( str ):
pass
some_dict = { 's' : 42 }
Producción:
> >> type ( list ( some_dict . keys ())[ 0 ])
str
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict # expected: Two different keys-value pairs
{ 's' : 40 }
> >> type ( list ( some_dict . keys ())[ 0 ])
str
Tanto el objeto s
como el hash "s"
de la cadena al mismo valor porque SomeClass
hereda el método __hash__
de la clase str
.
SomeClass("s") == "s"
se evalúa de True
porque SomeClass
también hereda el método __eq__
de la clase str
.
Dado que ambos objetos hash al mismo valor y son iguales, están representados por la misma clave en el diccionario.
Para el comportamiento deseado, podemos redefinir el método __eq__
en SomeClass
class SomeClass ( str ):
def __eq__ ( self , other ):
return (
type ( self ) is SomeClass
and type ( other ) is SomeClass
and super (). __eq__ ( other )
)
# When we define a custom __eq__, Python stops automatically inheriting the
# __hash__ method, so we need to define it as well
__hash__ = str . __hash__
some_dict = { 's' : 42 }
Producción:
> >> s = SomeClass ( 's' )
> >> some_dict [ s ] = 40
> >> some_dict
{ 's' : 40 , 's' : 42 }
> >> keys = list ( some_dict . keys ())
> >> type ( keys [ 0 ]), type ( keys [ 1 ])
( __main__ . SomeClass , str )
a , b = a [ b ] = {}, 5
Producción:
> >> a
{ 5 : ({...}, 5 )}
(target_list "=")+ (expression_list | yield_expression)
Una declaración de asignación evalúa la lista de expresiones (recuerde que esta puede ser una sola expresión o una lista separada por comas, esta última produce una tupla) y asigna el objeto único resultante a cada una de las listas de destino, de izquierda a derecha.
El +
en (target_list "=")+
significa que puede haber una o más listas de destino. En este caso, las listas de objetivos son a, b
y a[b]
(tenga en cuenta que la lista de expresiones es exactamente una, que en nuestro caso es {}, 5
).
Después de evaluar la lista de expresiones, su valor se desempaquete a las listas de destino de izquierda a derecha . Entonces, en nuestro caso, primero la {}, 5
Tuple se desempaquetan a a, b
y ahora tenemos a = {}
y b = 5
.
a
ahora se asigna a {}
, que es un objeto mutable.
La segunda lista de objetivos es a[b]
(puede esperar que esto arroje un error porque tanto a
como b
no se han definido en las declaraciones antes. Pero recuerde, acabamos de asignar a
a {}
y b
a 5
).
Ahora, estamos configurando la clave 5
en el diccionario a la tupla ({}, 5)
creando una referencia circular (el {...}
en la salida se refiere al mismo objeto al que a
ya está haciendo referencia). Otro ejemplo más simple de referencia circular podría ser
> >> some_list = some_list [ 0 ] = [ 0 ]
> >> some_list
[[...]]
> >> some_list [ 0 ]
[[...]]
> >> some_list is some_list [ 0 ]
True
> >> some_list [ 0 ][ 0 ][ 0 ][ 0 ][ 0 ][ 0 ] == some_list
True
Similar es el caso en nuestro ejemplo ( a[b][0]
es el mismo objeto que a
)
Entonces, para resumir, puede dividir el ejemplo para
a , b = {}, 5
a [ b ] = a , b
Y la referencia circular puede justificarse por el hecho de que a[b][0]
es el mismo objeto que a
> >> a [ b ][ 0 ] is a
True
> >> # Python 3.10.6
>> > int ( "2" * 5432 )
> >> # Python 3.10.8
>> > int ( "2" * 5432 )
Producción:
> >> # Python 3.10.6
222222222222222222222222222222222222222222222222222222222222222. ..
>> > # Python 3.10.8
Traceback ( most recent call last ):
...
ValueError : Exceeds the limit ( 4300 ) for integer string conversion :
value has 5432 digits ; use sys . set_int_max_str_digits ()
to increase the limit .
Esta llamada a int()
funciona bien en Python 3.10.6 y plantea un ValueError en Python 3.10.8. Tenga en cuenta que Python todavía puede funcionar con enteros grandes. El error solo se plantea al convertir entre enteros y cadenas.
Afortunadamente, puede aumentar el límite para el número permitido de dígitos cuando espera que una operación lo exceda. Para hacer esto, puede usar uno de los siguientes:
Consulte la documentación para obtener más detalles sobre cómo cambiar el límite predeterminado si espera que su código exceda este valor.
x = { 0 : None }
for i in x :
del x [ i ]
x [ i + 1 ] = None
print ( i )
Salida (Python 2.7- Python 3.5):
0
1
2
3
4
5
6
7
Sí, funciona exactamente por ocho veces y se detiene.
RuntimeError: dictionary keys changed during iteration
si intenta hacer esto.del
class SomeClass :
def __del__ ( self ):
print ( "Deleted!" )
Salida: 1.
> >> x = SomeClass ()
> >> y = x
> >> del x # this should print "Deleted!"
> >> del y
Deleted !
Phew, eliminado por fin. Es posible que haya adivinado lo que salvó __del__
de ser llamado en nuestro primer intento de eliminar x
. Agreguemos más giros al ejemplo.
2.
> >> x = SomeClass ()
> >> y = x
> >> del x
> >> y # check if y exists
< __main__ . SomeClass instance at 0x7f98a1a67fc8 >
> >> del y # Like previously, this should print "Deleted!"
> >> globals () # oh, it didn't. Let's check all our global variables and confirm
Deleted !
{ '__builtins__' : < module '__builtin__' ( built - in ) > , 'SomeClass' : < class __main__ . SomeClass at 0x7f98a1a5f668 > , '__package__' : None , '__name__' : '__main__' , '__doc__' : None }
Bien, ¿ahora está eliminado?
del x
no llama directamente x.__del__()
.del x
, Python elimina el nombre x
del alcance actual y disminuye en 1 El recuento de referencia del objeto x
referenciado. __del__()
se llama solo cuando el recuento de referencia del objeto alcanza cero.__del__()
no se llamó porque la declaración anterior ( >>> y
) en el intérprete interactivo creó otra referencia al mismo objeto (específicamente, la variable mágica _
que hace referencia al valor de resultado del último no None
expresión en el repl), evitando así que el recuento de referencias alcance cero cuando se encontró del y
.globals
(o realmente, ejecutar cualquier cosa que no tenga un resultado no None
) causó _
para hacer referencia al nuevo resultado, dejando caer la referencia existente. Ahora el recuento de referencias alcanzó las 0 y podemos ver "¡eliminado!" siendo imprimido (¡finalmente!).1.
a = 1
def some_func ():
return a
def another_func ():
a += 1
return a
2.
def some_closure_func ():
a = 1
def some_inner_func ():
return a
return some_inner_func ()
def another_closure_func ():
a = 1
def another_inner_func ():
a += 1
return a
return another_inner_func ()
Producción:
> >> some_func ()
1
> >> another_func ()
UnboundLocalError : local variable 'a' referenced before assignment
>> > some_closure_func ()
1
> >> another_closure_func ()
UnboundLocalError : local variable 'a' referenced before assignment
Cuando realiza una tarea a una variable en alcance, se vuelve local para ese alcance. Entonces, a
se vuelve local para el alcance de another_func
, pero no se ha inicializado previamente en el mismo alcance, lo que arroja un error.
Para modificar la variable de alcance externo a
en another_func
, tenemos que usar la palabra clave global
.
def another_func ()
global a
a += 1
return a
Producción:
> >> another_func ()
2
En another_closure_func
, a
se vuelve local para el alcance de another_inner_func
, pero no se ha inicializado previamente en el mismo alcance, por lo que arroja un error.
Para modificar la variable de alcance externo a
en another_inner_func
, use la palabra clave nonlocal
. La declaración no local se usa para referirse a variables definidas en el alcance externo más cercano (excluyendo el global).
def another_func ():
a = 1
def another_inner_func ():
nonlocal a
a += 1
return a
return another_inner_func ()
Producción:
> >> another_func ()
2
Las palabras clave global
y nonlocal
le dicen al intérprete de Python que no declare nuevas variables y las busque en los alcances externos correspondientes.
Lea esta guía corta pero increíble para obtener más información sobre cómo funcionan los espacios de nombres y la resolución de alcance en Python.
list_1 = [ 1 , 2 , 3 , 4 ]
list_2 = [ 1 , 2 , 3 , 4 ]
list_3 = [ 1 , 2 , 3 , 4 ]
list_4 = [ 1 , 2 , 3 , 4 ]
for idx , item in enumerate ( list_1 ):
del item
for idx , item in enumerate ( list_2 ):
list_2 . remove ( item )
for idx , item in enumerate ( list_3 [:]):
list_3 . remove ( item )
for idx , item in enumerate ( list_4 ):
list_4 . pop ( idx )
Producción:
> >> list_1
[ 1 , 2 , 3 , 4 ]
> >> list_2
[ 2 , 4 ]
> >> list_3
[]
> >> list_4
[ 2 , 4 ]
¿Puedes adivinar por qué la salida es [2, 4]
?
Nunca es una buena idea cambiar el objeto por el que estás exitando. La forma correcta de hacerlo es iterar sobre una copia del objeto en su lugar, y list_3[:]
hace exactamente eso.
> >> some_list = [ 1 , 2 , 3 , 4 ]
> >> id ( some_list )
139798789457608
> >> id ( some_list [:]) # Notice that python creates new object for sliced list.
139798779601192
Diferencia entre del
, remove
y pop
:
del var_name
simplemente elimina el enlace del var_name
del espacio de nombres local o global (por eso la list_1
no se ve afectada).remove
elimina el primer valor coincidente, no un índice específico, aumenta ValueError
si no se encuentra el valor.pop
elimina el elemento en un índice específico y lo devuelve, aumenta IndexError
si se especifica un índice no válido. ¿Por qué la salida es [2, 4]
?
1
de list_2
o list_4
, el contenido de las listas ahora es [2, 3, 4]
. Los elementos restantes se desplazan hacia abajo, es decir, 2
está en el índice 0 y 3
está en el índice 1. Dado que la próxima iteración verá el índice 1 (que es el 3
), el 2
se omite por completo. Algo similar sucederá con cada elemento alternativo en la secuencia de la lista. > >> numbers = list ( range ( 7 ))
> >> numbers
[ 0 , 1 , 2 , 3 , 4 , 5 , 6 ]
> >> first_three , remaining = numbers [: 3 ], numbers [ 3 :]
> >> first_three , remaining
([ 0 , 1 , 2 ], [ 3 , 4 , 5 , 6 ])
>> > numbers_iter = iter ( numbers )
>> > list ( zip ( numbers_iter , first_three ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
# so far so good, let's zip the remaining
>> > list ( zip ( numbers_iter , remaining ))
[( 4 , 3 ), ( 5 , 4 ), ( 6 , 5 )]
¿Dónde pasó el elemento 3
de la lista numbers
?
def zip ( * iterables ):
sentinel = object ()
iterators = [ iter ( it ) for it in iterables ]
while iterators :
result = []
for it in iterators :
elem = next ( it , sentinel )
if elem is sentinel : return
result . append ( elem )
yield tuple ( result )
result
llamando a la next
función y se detiene cuando se agota cualquiera de los iterables.result
. Eso es lo que sucedió con 3
en numbers_iter
.zip
sería, > >> numbers = list ( range ( 7 ))
> >> numbers_iter = iter ( numbers )
> >> list ( zip ( first_three , numbers_iter ))
[( 0 , 0 ), ( 1 , 1 ), ( 2 , 2 )]
> >> list ( zip ( remaining , numbers_iter ))
[( 3 , 3 ), ( 4 , 4 ), ( 5 , 5 ), ( 6 , 6 )]
1.
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
Producción:
6 : for x inside loop
6 : x in global
Pero x
nunca se definió fuera del alcance de For Loop ...
2.
# This time let's initialize x first
x = - 1
for x in range ( 7 ):
if x == 6 :
print ( x , ': for x inside loop' )
print ( x , ': x in global' )
Producción:
6 : for x inside loop
6 : x in global
3.
Salida (python 2.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
4
Salida (Python 3.x):
> >> x = 1
> >> print ([ x for x in range ( 5 )])
[ 0 , 1 , 2 , 3 , 4 ]
> >> print ( x )
1
En Python, los bucles usan el alcance en el que existen y deja su variable de bucle definido. Esto también se aplica si definimos explícitamente la variable for-loop en el espacio de nombres global antes. En este caso, se volverá a ser la variable existente.
Las diferencias en la salida de los intérpretes de Python 2.x y Python 3.x para el ejemplo de comprensión de la lista pueden explicarse siguiendo el cambio documentado en lo nuevo en Python 3.0 ChangeLog:
"Las comprensiones de la lista ya no admiten el formulario sintáctico
[... for var in item1, item2, ...]
. Use[... for var in (item1, item2, ...)]
en su lugar. Además, tenga en cuenta esa lista Las comprensiones tienen una semántica diferente: están más cerca del azúcar sintáctico para una expresión del generador dentro de un constructorlist()
, y en particular, las variables de control del bucle ya no se filtran en el alcance circundante ".
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Producción:
> >> some_func ()
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' ]
> >> some_func ([])
[ 'some_string' ]
> >> some_func ()
[ 'some_string' , 'some_string' , 'some_string' ]
Los argumentos mutables predeterminados de las funciones en Python no se inicializan realmente cada vez que llama a la función. En cambio, el valor asignado recientemente se usa como el valor predeterminado. Cuando pasamos explícitamente []
a some_func
como argumento, no se utilizó el valor predeterminado de la variable default_arg
, por lo que la función volvió como se esperaba.
def some_func ( default_arg = []):
default_arg . append ( "some_string" )
return default_arg
Producción:
> >> some_func . __defaults__ #This will show the default argument values for the function
([],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' ],)
> >> some_func ()
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
> >> some_func ([])
> >> some_func . __defaults__
([ 'some_string' , 'some_string' ],)
Una práctica común para evitar errores debido a argumentos mutables es asignar None
como valor predeterminado y luego verificar si se pasa algún valor a la función correspondiente a ese argumento. Ejemplo:
def some_func ( default_arg = None ):
if default_arg is None :
default_arg = []
default_arg . append ( "some_string" )
return default_arg
some_list = [ 1 , 2 , 3 ]
try :
# This should raise an ``IndexError``
print ( some_list [ 4 ])
except IndexError , ValueError :
print ( "Caught!" )
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except IndexError , ValueError :
print ( "Caught again!" )
Salida (python 2.x):
Caught !
ValueError : list . remove ( x ): x not in list
Salida (Python 3.x):
File "<input>" , line 3
except IndexError , ValueError :
^
SyntaxError : invalid syntax
Para agregar múltiples excepciones a la cláusula excepto, debe pasarlas como tupla entre paréntesis como el primer argumento. El segundo argumento es un nombre opcional, que cuando se suministra unirá la instancia de excepción que se ha planteado. Ejemplo,
some_list = [ 1 , 2 , 3 ]
try :
# This should raise a ``ValueError``
some_list . remove ( 4 )
except ( IndexError , ValueError ), e :
print ( "Caught again!" )
print ( e )
Salida (python 2.x):
Caught again!
list.remove(x): x not in list
Salida (Python 3.x):
File "<input>" , line 4
except ( IndexError , ValueError ), e :
^
IndentationError : unindent does not match any outer indentation level
Separar la excepción de la variable con una coma está en desuso y no funciona en Python 3; La forma correcta es usar as
. Ejemplo,
some_list = [ 1 , 2 , 3 ]
try :
some_list . remove ( 4 )
except ( IndexError , ValueError ) as e :
print ( "Caught again!" )
print ( e )
Producción:
Caught again!
list.remove(x): x not in list
1.
a = [ 1 , 2 , 3 , 4 ]
b = a
a = a + [ 5 , 6 , 7 , 8 ]
Producción:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 ]
2.
a = [ 1 , 2 , 3 , 4 ]
b = a
a += [ 5 , 6 , 7 , 8 ]
Producción:
> >> a
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
> >> b
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ]
a += b
no siempre se comporta de la misma manera que a = a + b
. Las clases pueden implementar el op=
operadores de manera diferente, y las listas hacen esto.
La expresión a = a + [5,6,7,8]
genera una nueva lista y establece la referencia de a
a esa nueva lista, dejando b
sin cambios.
La expresión a += [5,6,7,8]
en realidad se asigna a una función de "extender" que funciona en la lista de tal manera que a
y b
todavía apuntan a la misma lista que se ha modificado en el lugar.
1.
x = 5
class SomeClass :
x = 17
y = ( x for i in range ( 10 ))
Producción:
> >> list ( SomeClass . y )[ 0 ]
5
2.
x = 5
class SomeClass :
x = 17
y = [ x for i in range ( 10 )]
Salida (python 2.x):
> >> SomeClass . y [ 0 ]
17
Salida (Python 3.x):
> >> SomeClass . y [ 0 ]
5
Implementemos una función ingenua para obtener el elemento medio de una lista:
def get_middle ( some_list ):
mid_index = round ( len ( some_list ) / 2 )
return some_list [ mid_index - 1 ]
Python 3.x:
> >> get_middle ([ 1 ]) # looks good
1
> >> get_middle ([ 1 , 2 , 3 ]) # looks good
2
> >> get_middle ([ 1 , 2 , 3 , 4 , 5 ]) # huh?
2
> >> len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 # good
2.5
> >> round ( len ([ 1 , 2 , 3 , 4 , 5 ]) / 2 ) # why?
2
Parece que Python redondeó 2.5 a 2.
round()
utiliza el redondeo del banquero donde .5 fracciones se redondean al número par más cercano: > >> round ( 0.5 )
0
> >> round ( 1.5 )
2
> >> round ( 2.5 )
2
> >> import numpy # numpy does the same
> >> numpy . round ( 0.5 )
0.0
> >> numpy . round ( 1.5 )
2.0
> >> numpy . round ( 2.5 )
2.0
get_middle([1])
solo regresó 1 porque el índice fue round(0.5) - 1 = 0 - 1 = -1
, devolviendo el último elemento en la lista.No he conocido ni una sola experiencia en pitonista hasta la fecha que no se ha encontrado con uno o más de los siguientes escenarios,
1.
x , y = ( 0 , 1 ) if True else None , None
Producción:
> >> x , y # expected (0, 1)
(( 0 , 1 ), None )
2.
t = ( 'one' , 'two' )
for i in t :
print ( i )
t = ( 'one' )
for i in t :
print ( i )
t = ()
print ( t )
Producción:
one
two
o
n
e
tuple ()
3.
ten_words_list = [
"some",
"very",
"big",
"list",
"that"
"consists",
"of",
"exactly",
"ten",
"words"
]
Producción
> >> len ( ten_words_list )
9
4. No afirmar con suficiente fuerza
a = "python"
b = "javascript"
Producción:
# An assert statement with an assertion failure message.
> >> assert ( a == b , "Both languages are different" )
# No AssertionError is raised
5.
some_list = [ 1 , 2 , 3 ]
some_dict = {
"key_1" : 1 ,
"key_2" : 2 ,
"key_3" : 3
}
some_list = some_list . append ( 4 )
some_dict = some_dict . update ({ "key_4" : 4 })
Producción:
> >> print ( some_list )
None
> >> print ( some_dict )
None
6.
def some_recursive_func ( a ):
if a [ 0 ] == 0 :
return
a [ 0 ] -= 1
some_recursive_func ( a )
return a
def similar_recursive_func ( a ):
if a == 0 :
return a
a -= 1
similar_recursive_func ( a )
return a
Producción:
> >> some_recursive_func ([ 5 , 0 ])
[ 0 , 0 ]
> >> similar_recursive_func ( 5 )
4
Para 1, la declaración correcta para el comportamiento esperado es x, y = (0, 1) if True else (None, None)
.
Para 2, la declaración correcta para el comportamiento esperado es t = ('one',)
o t = 'one',
(coma faltante) de lo contrario el intérprete considera que t
es un str
e itera sobre su carácter por carácter.
()
es un token especial y denota tuple
vacía.
En 3, como ya habrás descubierto, hay una coma faltante después del quinto elemento ( "that"
) en la lista. Entonces, por la concatenación literal de cadena implícita,
> >> ten_words_list
[ 'some' , 'very' , 'big' , 'list' , 'thatconsists' , 'of' , 'exactly' , 'ten' , 'words' ]
No se planteó AssertionError
en el cuarto fragmento porque en lugar de afirmar la expresión individual a == b
, estamos afirmando entera tupla. El siguiente fragmento aclarará las cosas,
> >> a = "python"
> >> b = "javascript"
> >> assert a == b
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError
>> > assert ( a == b , "Values are not equal" )
< stdin > : 1 : SyntaxWarning : assertion is always true , perhaps remove parentheses ?
>> > assert a == b , "Values are not equal"
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AssertionError : Values are not equal
En cuanto al quinto fragmento, la mayoría de los métodos que modifican los elementos de secuencia/objetos de mapeo como list.append
, dict.update
, list.sort
, etc. Modifique los objetos en el lugar y no devuelva None
. La razón detrás de esto es mejorar el rendimiento evitando hacer una copia del objeto si la operación se puede hacer en el lugar (referida desde aquí).
El último se debe ser bastante obvio, el objeto mutable (como list
) puede alterarse en la función, y la reasignación de una inmutable ( a -= 1
) no es una alteración del valor.
Ser consciente de estos nitpicks puede ahorrarle horas de esfuerzo de depuración a largo plazo.
> >> 'a' . split ()
[ 'a' ]
# is same as
> >> 'a' . split ( ' ' )
[ 'a' ]
# but
> >> len ( '' . split ())
0
# isn't the same as
> >> len ( '' . split ( ' ' ))
1
' '
, pero según los documentosSi no se especifica SEP o no es
None
, se aplica un algoritmo de división diferente: las ejecuciones de espacios en blanco consecutivos se consideran un solo separador, y el resultado no contendrá cadenas vacías al comienzo o al final si la cadena tiene un espacio blanco líder o posterior. En consecuencia, dividir una cadena vacía o una cadena que consiste en solo espacios en blanco con un separador None Devuelve[]
. Si se da SEP, los delimitadores consecutivos no se agrupan y se considera que delimitan cadenas vacías (por ejemplo,'1,,2'.split(',')
devuelve['1', '', '2']
). Dividir una cadena vacía con un separador especificado devuelve['']
.
> >> ' a ' . split ( ' ' )
[ '' , 'a' , '' ]
> >> ' a ' . split ()
[ 'a' ]
> >> '' . split ( ' ' )
[ '' ]
# File: module.py
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Producción
> >> from module import *
> >> some_weird_name_func_ ()
"works!"
> >> _another_weird_name_func ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name '_another_weird_name_func' is not defined
A menudo es aconsejable no usar importaciones de comodín. La primera razón obvia de esto es que, en las importaciones de comodín, los nombres con un guión inferior no se importan. Esto puede conducir a errores durante el tiempo de ejecución.
Si hubiéramos usado from ... import a, b, c
, el NameError
anterior no habría ocurrido.
> >> from module import some_weird_name_func_ , _another_weird_name_func
> >> _another_weird_name_func ()
works !
Si realmente desea usar importaciones de comodín, entonces tendrá que definir la lista __all__
en su módulo que contendrá una lista de objetos públicos que estarán disponibles cuando realicemos importaciones de comodín.
__all__ = [ '_another_weird_name_func' ]
def some_weird_name_func_ ():
print ( "works!" )
def _another_weird_name_func ():
print ( "works!" )
Producción
> >> _another_weird_name_func ()
"works!"
> >> some_weird_name_func_ ()
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'some_weird_name_func_' is not defined
> >> x = 7 , 8 , 9
> >> sorted ( x ) == x
False
> >> sorted ( x ) == sorted ( x )
True
> >> y = reversed ( x )
> >> sorted ( y ) == sorted ( y )
False
El método sorted
siempre devuelve una lista, y la comparación de listas y tuplas siempre devuelve False
en Python.
> >> [] == tuple ()
False
> >> x = 7 , 8 , 9
> >> type ( x ), type ( sorted ( x ))
( tuple , list )
A diferencia de sorted
, el método reversed
devuelve un iterador. ¿Por qué? Debido a que la clasificación requiere que el iterador se modifique en el lugar o use un contenedor adicional (una lista), mientras que la inversión puede simplemente funcionar iterando desde el último índice hasta el primero.
Entonces, durante la comparación sorted(y) == sorted(y)
, la primera llamada a sorted()
consumirá el iterador y
, y la siguiente llamada solo devolverá una lista vacía.
> >> x = 7 , 8 , 9
> >> y = reversed ( x )
> >> sorted ( y ), sorted ( y )
([ 7 , 8 , 9 ], [])
from datetime import datetime
midnight = datetime ( 2018 , 1 , 1 , 0 , 0 )
midnight_time = midnight . time ()
noon = datetime ( 2018 , 1 , 1 , 12 , 0 )
noon_time = noon . time ()
if midnight_time :
print ( "Time at midnight is" , midnight_time )
if noon_time :
print ( "Time at noon is" , noon_time )
Salida (<3.5):
( 'Time at noon is' , datetime . time ( 12 , 0 ))
El tiempo de medianoche no está impreso.
Antes de Python 3.5, el valor booleano para el objeto datetime.time
se consideraba False
si representaba la medianoche en UTC. Es propenso a errores cuando se usa if obj:
sintaxis para verificar si el obj
es nulo o algún equivalente de "vacío".
Esta sección contiene algunas cosas menos conocidas e interesantes sobre Python de las que la mayoría de los principiantes como yo desconocen (bueno, ya no).
Bueno, aquí tienes
import antigravity
Salida: sshh ... es un súper secreto.
antigravity
es uno de los pocos huevos de Pascua liberados por los desarrolladores de Python.import antigravity
abre un navegador web que señala el clásico cómic XKCD sobre Python.goto
, pero ¿por qué? from goto import goto , label
for i in range ( 9 ):
for j in range ( 9 ):
for k in range ( 9 ):
print ( "I am trapped, please rescue!" )
if k == 2 :
goto . breakout # breaking out from a deeply nested loop
label . breakout
print ( "Freedom!" )
Salida (Python 2.3):
I am trapped , please rescue !
I am trapped , please rescue !
Freedom !
goto
en Python como una broma de April Fool el 1 de abril de 2004.goto
no está presente en Python.Si eres una de las personas a las que no le gusta usar Whitespace en Python para denotar ámbitos, puedes usar el estilo C {} importando,
from __future__ import braces
Producción:
File "some_file.py" , line 1
from __future__ import braces
SyntaxError : not a chance
¿Tirantes? ¡De ninguna manera! Si crees que es decepcionante, usa Java. De acuerdo, otra cosa sorprendente, ¿puedes encontrar dónde está el SyntaxError
planteado en el código del módulo __future__
?
__future__
se usa normalmente para proporcionar características de futuras versiones de Python. Sin embargo, el "futuro" en este contexto específico es irónico.future.c
.future.c
antes de tratarla como una declaración de importación normal.Salida (Python 3.x)
> >> from __future__ import barry_as_FLUFL
> >> "Ruby" != "Python" # there's no doubt about it
File "some_file.py" , line 1
"Ruby" != "Python"
^
SyntaxError : invalid syntax
>> > "Ruby" <> "Python"
True
Ahí vamos.
Esto es relevante para PEP-401 publicado el 1 de abril de 2009 (ahora sabes, lo que significa).
Citando del PEP-401
Reconocido que el operador de desigualdad! = En Python 3.0 fue un error horrible y inductor de la pinza, el Flufl reinstala el operador de diamantes <> como la única ortografía.
Había más cosas que el tío Barry tuvo que compartir en el PEP; Puedes leerlos aquí.
Funciona bien en un entorno interactivo, pero elevará un SyntaxError
cuando se ejecute a través del archivo Python (ver este problema). Sin embargo, puede envolver la declaración dentro de una eval
o compile
para que funcione,
from __future__ import barry_as_FLUFL
print ( eval ( '"Ruby" <> "Python"' ))
import this
Espera, ¿qué es esto ? this
es amor ❤️
Producción:
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
¡Es el Zen de Python!
> >> love = this
> >> this is love
True
> >> love is True
False
> >> love is False
False
> >> love is not True or False
True
> >> love is not True or False ; love is love # Love is complicated
True
this
módulo en Python es un huevo de Pascua para el Zen de Python (PEP 20).love is not True or False; love is love
, irónico pero se explica por sí mismo (si no, consulte los ejemplos relacionados con is
y is not
operadores). La cláusula else
para bucles. Un ejemplo típico podría ser:
def does_exists_num ( l , to_find ):
for num in l :
if num == to_find :
print ( "Exists!" )
break
else :
print ( "Does not exist" )
Producción:
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> does_exists_num ( some_list , 4 )
Exists !
>> > does_exists_num ( some_list , - 1 )
Does not exist
La cláusula else
en manejo de excepciones. Un ejemplo,
try :
pass
except :
print ( "Exception occurred!!!" )
else :
print ( "Try block executed successfully..." )
Producción:
Try block executed successfully ...
else
después de un bucle se ejecuta solo cuando no hay break
explícita después de todas las iteraciones. Puedes pensar en ello como una cláusula de "nobreak".else
Cláusula después de un bloque de try también se llama "cláusula de finalización", ya que alcanzar la cláusula else
en una declaración try
significa que el bloque de try realmente se completó con éxito. def some_func ():
Ellipsis
Producción
> >> some_func ()
# No output, No Error
> >> SomeRandomString
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
NameError : name 'SomeRandomString' is not defined
> >> Ellipsis
Ellipsis
Ellipsis
es un objeto incorporado a nivel mundial que es equivalente a ...
> >> ...
Ellipsis
pass
) > >> import numpy as np
> >> three_dimensional_array = np . arange ( 8 ). reshape ( 2 , 2 , 2 )
array ([
[
[ 0 , 1 ],
[ 2 , 3 ]
],
[
[ 4 , 5 ],
[ 6 , 7 ]
]
])
three_dimensional_array
es una matriz de matrices de matrices. Digamos que queremos imprimir el segundo elemento (índice 1
) de todas las matrices más interiores, podemos usar elipsis para evitar todas las dimensiones anteriores > >> three_dimensional_array [:,:, 1 ]
array ([[ 1 , 3 ],
[ 5 , 7 ]])
> >> three_dimensional_array [..., 1 ] # using Ellipsis.
array ([[ 1 , 3 ],
[ 5 , 7 ]])
n_dimensional_array[firs_dim_slice, ..., last_dim_slice]
)(Callable[..., int]
o Tuple[str, ...]
))La ortografía está destinada. Por favor, no envíe un parche para esto.
Salida (Python 3.x):
> >> infinity = float ( 'infinity' )
> >> hash ( infinity )
314159
> >> hash ( float ( '-inf' ))
- 314159
float('-inf')
es "-10⁵ x π" en Python 3, mientras que "-10⁵ Xe" en Python 2.1.
class Yo ( object ):
def __init__ ( self ):
self . __honey = True
self . bro = True
Producción:
> >> Yo (). bro
True
> >> Yo (). __honey
AttributeError : 'Yo' object has no attribute '__honey'
> >> Yo (). _Yo__honey
True
2.
class Yo ( object ):
def __init__ ( self ):
# Let's try something symmetrical this time
self . __honey__ = True
self . bro = True
Producción:
> >> Yo (). bro
True
> >> Yo (). _Yo__honey__
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'Yo' object has no attribute '_Yo__honey__'
¿Por qué Yo()._Yo__honey
funcionó?
3.
_A__variable = "Some value"
class A ( object ):
def some_func ( self ):
return __variable # not initialized anywhere yet
Producción:
> >> A (). __variable
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
AttributeError : 'A' object has no attribute '__variable'
> >> A (). some_func ()
'Some value'
__
(doble subrayador, también conocido como "dunder") y no termina con más de un subrayador posterior al agregar _NameOfTheClass
al frente.__honey
en el primer fragmento, tuvimos que agregar _Yo
al frente, lo que evitaría conflictos con el mismo atributo de nombre definido en cualquier otra clase.__variable
en el return __variable
se destrozó a _A__variable
, que también es el nombre de la variable que declaramos en el alcance externo.Producción:
> >> value = 11
> >> valuе = 32
> >> value
11
¿Qué?
Nota: La forma más fácil de reproducir esto es simplemente copiar las declaraciones del fragmento anterior y pegarlas en su archivo/shell.
Algunos personajes no occidentales se ven idénticos a las letras en el alfabeto inglés, pero son considerados distintos por el intérprete.
> >> ord ( 'е' ) # cyrillic 'e' (Ye)
1077
> >> ord ( 'e' ) # latin 'e', as used in English and typed using standard keyboard
101
> >> 'е' == 'e'
False
> >> value = 42 # latin e
> >> valuе = 23 # cyrillic 'e', Python 2.x interpreter would raise a `SyntaxError` here
> >> value
42
La función ord()
incorporada devuelve el punto de código Unicode de un personaje, y diferentes posiciones de código de 'e' y latín 'e' cirílicos justifican el comportamiento del ejemplo anterior.
# `pip install numpy` first.
import numpy as np
def energy_send ( x ):
# Initializing a numpy array
np . array ([ float ( x )])
def energy_receive ():
# Return an empty numpy array
return np . empty ((), dtype = np . float ). tolist ()
Producción:
> >> energy_send ( 123.456 )
> >> energy_receive ()
123.456
¿Dónde está el Premio Nobel?
energy_send
no se devuelve, de modo que el espacio de memoria sea libre de reiniciar.numpy.empty()
Devuelve la siguiente ranura de memoria libre sin reinicializarla. Este lugar de memoria resulta ser el mismo que fue liberado (generalmente, pero no siempre). def square ( x ):
"""
A simple function to calculate the square of a number by addition.
"""
sum_so_far = 0
for counter in range ( x ):
sum_so_far = sum_so_far + x
return sum_so_far
Salida (python 2.x):
> >> square ( 10 )
10
¿No debería ser 100?
Nota: Si no puede reproducir esto, intente ejecutar el archivo mixed_tabs_and_spaces.py a través del shell.
¡No mezcle pestañas y espacios! El personaje que acaba de retorno anterior es una "pestaña", y el código está sangrado por múltiples de "4 espacios" en otras partes del ejemplo.
Así es como Python maneja pestañas:
Primero, las pestañas se reemplazan (de izquierda a derecha) por uno a ocho espacios de modo que el número total de caracteres hasta e incluyendo el reemplazo es un múltiplo de ocho <...>
Entonces, la "pestaña" en la última línea de función square
se reemplaza con ocho espacios, y se encuentra en el bucle.
Python 3 tiene la amabilidad de lanzar un error para tales casos automáticamente.
Salida (Python 3.x):
TabError : inconsistent use of tabs and spaces in indentation
+=
es más rápido # using "+", three strings:
> >> timeit . timeit ( "s1 = s1 + s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.25748300552368164
# using "+=", three strings:
> >> timeit . timeit ( "s1 += s2 + s3" , setup = "s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000" , number = 100 )
0.012188911437988281
+=
es más rápido que +
para concatenar más de dos cadenas porque la primera cadena (ejemplo, s1
para s1 += s2 + s3
) no se destruye al calcular la cadena completa. def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s += "xyz"
assert len ( s ) == 3 * iters
def add_bytes_with_plus ( iters ):
s = b""
for i in range ( iters ):
s += b"xyz"
assert len ( s ) == 3 * iters
def add_string_with_format ( iters ):
fs = "{}" * iters
s = fs . format ( * ([ "xyz" ] * iters ))
assert len ( s ) == 3 * iters
def add_string_with_join ( iters ):
l = []
for i in range ( iters ):
l . append ( "xyz" )
s = "" . join ( l )
assert len ( s ) == 3 * iters
def convert_list_to_string ( l , iters ):
s = "" . join ( l )
assert len ( s ) == 3 * iters
Producción:
# Executed in ipython shell using %timeit for better readability of results.
# You can also use the timeit module in normal python shell/scriptm=, example usage below
# timeit.timeit('add_string_with_plus(10000)', number=1000, globals=globals())
> >> NUM_ITERS = 1000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS )
124 µs ± 4.73 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS )
211 µs ± 10.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS )
61 µs ± 2.18 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS )
117 µs ± 3.21 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS )
10.1 µs ± 1.06 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
Aumentemos el número de iteraciones por un factor de 10.
> >> NUM_ITERS = 10000
> >> % timeit - n1000 add_string_with_plus ( NUM_ITERS ) # Linear increase in execution time
1.26 ms ± 76.8 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_bytes_with_plus ( NUM_ITERS ) # Quadratic increase
6.82 ms ± 134 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_format ( NUM_ITERS ) # Linear increase
645 µs ± 24.5 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n1000 add_string_with_join ( NUM_ITERS ) # Linear increase
1.17 ms ± 7.25 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> l = [ "xyz" ] * NUM_ITERS
> >> % timeit - n1000 convert_list_to_string ( l , NUM_ITERS ) # Linear increase
86.3 µs ± 2 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
Puede leer más sobre Timeit o %TimeIt en estos enlaces. Se utilizan para medir el tiempo de ejecución de las piezas del código.
No use +
para generar cadenas largas: en Python, str
es inmutable, por lo que las cuerdas izquierda y derecha deben copiarse en la nueva cadena para cada par de concatenaciones. Si concatena cuatro cadenas de longitud 10, estará copiando (10+10)+((10+10) +10)+(((10+10) +10) +10) = 90 caracteres en lugar de solo 40 personajes. Las cosas empeoran cuadráticamente a medida que aumenta el número y el tamaño de la cadena (justificado con los tiempos de ejecución de la función add_bytes_with_plus
)
Por lo tanto, se aconseja usar .format.
o %
sintaxis (sin embargo, son ligeramente más lentos que +
para cadenas muy cortas).
O mejor, si ya tiene contenido disponible en forma de un objeto iterable, use ''.join(iterable_object)
que es mucho más rápido.
A diferencia de add_bytes_with_plus
debido a las optimizaciones +=
discutidas en el ejemplo anterior, add_string_with_plus
no mostró un aumento cuadrático en el tiempo de ejecución. Si la declaración hubiera sido s = s + "x" + "y" + "z"
en lugar de s += "xyz"
, el aumento habría sido cuadrático.
def add_string_with_plus ( iters ):
s = ""
for i in range ( iters ):
s = s + "x" + "y" + "z"
assert len ( s ) == 3 * iters
> >> % timeit - n100 add_string_with_plus ( 1000 )
388 µs ± 22.4 µs per loop ( mean ± std . dev . of 7 runs , 1000 loops each )
> >> % timeit - n100 add_string_with_plus ( 10000 ) # Quadratic increase in execution time
9 ms ± 298 µs per loop ( mean ± std . dev . of 7 runs , 100 loops each )
Muchas formas de formatear y crear una cadena gigante están en contraste con el Zen de Python, según lo cual,
Debe haber una, y preferiblemente solo una, una forma obvia de hacerlo.
dict
* some_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
another_dict = { str ( i ): 1 for i in range ( 1_000_000 )}
Producción:
> >> % timeit some_dict [ '5' ]
28.6 ns ± 0.115 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> some_dict [ 1 ] = 1
> >> % timeit some_dict [ '5' ]
37.2 ns ± 0.265 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> % timeit another_dict [ '5' ]
28.5 ns ± 0.142 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
> >> another_dict [ 1 ] # Trying to access a key that doesn't exist
Traceback ( most recent call last ):
File "<stdin>" , line 1 , in < module >
KeyError : 1
> >> % timeit another_dict [ '5' ]
38.5 ns ± 0.0913 ns per loop ( mean ± std . dev . of 7 runs , 10000000 loops each )
¿Por qué se están volviendo las mismas búsquedas más lentas?
str
, int
, cualquier objeto ...), y una especializada para el caso común de diccionarios compuestos de teclas str
-solo.lookdict_unicode
en la fuente de Cpython) sabe que todas las claves existentes (incluida la clave de búsqueda) son cadenas, y utiliza la comparación de cadenas más rápida y simple para comparar las claves, en lugar de llamar al método __eq__
.dict
con una tecla no str
, se modifica para que las búsquedas futuras usen la función genérica.dict
particular, y la clave ni siquiera tiene que existir en el diccionario. Es por eso que intentar una búsqueda fallida tiene el mismo efecto.dict
s * import sys
class SomeClass :
def __init__ ( self ):
self . some_attr1 = 1
self . some_attr2 = 2
self . some_attr3 = 3
self . some_attr4 = 4
def dict_size ( o ):
return sys . getsizeof ( o . __dict__ )
Salida: (Python 3.8, otras versiones de Python 3 pueden variar un poco)
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104
> >> dict_size ( o2 )
104
> >> del o1 . some_attr1
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
> >> dict_size ( o1 )
232
Intentemos de nuevo ... en un nuevo intérprete:
> >> o1 = SomeClass ()
> >> o2 = SomeClass ()
> >> dict_size ( o1 )
104 # as expected
> >> o1 . some_attr5 = 5
> >> o1 . some_attr6 = 6
> >> dict_size ( o1 )
360
> >> dict_size ( o2 )
272
> >> o3 = SomeClass ()
> >> dict_size ( o3 )
232
¿Qué hace que esos diccionarios sean hinchados? ¿Y por qué también están hinchados los objetos recién creados?
__init__
de la primera instancia creada, sin causar una "Unshare"). Si existen múltiples instancias cuando ocurre un cambio de tamaño, el intercambio de clave está deshabilitado para todas las instancias futuras de la misma clase: Cpython no puede decir si sus instancias están utilizando el mismo conjunto de atributos y decide rescatar al intentar compartir su llaves.__init__
! join()
es una operación de cadena en lugar de la operación de lista. (algo contradictorio a primer uso)
Explicación: Si join()
es un método en una cadena, entonces puede funcionar en cualquier iterable (lista, tupla, iteradores). Si fuera un método en una lista, tendría que ser implementado por separado por cada tipo. Además, no tiene mucho sentido colocar un método específico de cadena en una API de objeto list
genérica.
Pocas declaraciones de aspecto extraño pero semánticamente correcto:
[] = ()
es una declaración semánticamente correcta (desempacar una tuple
vacía en una list
vacía)'a'[0][0][0][0][0]
también es semánticamente correcto, porque Python no tiene un tipo de datos de caracteres como otros idiomas ramificados desde C. Entonces, seleccionar un solo carácter de una cadena devuelve un Cadena de un solo personaje.3 --0-- 5 == 8
y --5 == 5
son declaraciones semánticamente correctas y se evalúan como True
. Dado que a
es un número, ++a
y --a
son declaraciones válidas de Python, pero no se comportan de la misma manera en comparación con declaraciones similares en idiomas como C, C ++ o Java.
> >> a = 5
> >> a
5
> >> + + a
5
> >> - - a
5
Explicación:
++
en la gramática de Python. En realidad son dos operadores +
.++a
analiza como +(+a)
que se traduce en a
. Del mismo modo, la salida de la declaración --a
puede justificarse.Debe ser consciente del operador de morsa en Python. Pero, ¿alguna vez has oído hablar del operador de invaderos espaciales ?
> >> a = 42
> >> a -= - 1
> >> a
43
Se usa como operador de incrementación alternativa, junto con otro
> >> a += + 1
> >> a
> >> 44
Explicación: Esta broma proviene del tweet de Raymond Hettinger. El operador de Space Invader es en realidad solo un a -= (-1)
malformado. Que es equivalente a a = a - (- 1)
. Similar para el caso a += (+ 1)
.
Python tiene un operador de implicación Converse indocumentado.
> >> False ** False == True
True
> >> False ** True == False
True
> >> True ** False == True
True
> >> True ** True == True
True
Explicación: Si reemplaza False
y True
por 0 y 1 y hace las matemáticas, la tabla de verdad es equivalente a un operador de implicación contraria. (Fuente)
Como estamos hablando de operadores, también hay @
operador para la multiplicación de matriz (no se preocupe, esta vez es de verdad).
> >> import numpy as np
> >> np . array ([ 2 , 2 , 2 ]) @ np . array ([ 7 , 8 , 8 ])
46
Explicación: El operador @
se agregó en Python 3.5 teniendo en cuenta la comunidad científica. Cualquier objeto puede sobrecargar el método mágico __matmul__
para definir el comportamiento de este operador.
Desde Python 3.8 en adelante, puede usar una sintaxis típica de F-String como f'{some_var=}
para una depuración rápida. Ejemplo,
> >> some_string = "wtfpython"
> >> f' { some_string = } '
"some_string='wtfpython'"
Python usa 2 bytes para el almacenamiento variable local en funciones. En teoría, esto significa que solo 65536 variables pueden definirse en una función. Sin embargo, Python tiene una solución práctica integrada que se puede usar para almacenar más de 2^16 nombres de variables. El siguiente código demuestra lo que sucede en la pila cuando se definen más de 65536 variables locales (Advertencia: este código imprime alrededor de 2^18 líneas de texto, ¡así que prepárate!)::
import dis
exec ( """
def f():
""" + """
""" . join ([ "X" + str ( x ) + "=" + str ( x ) for x in range ( 65539 )]))
f ()
print ( dis . dis ( f ))
Múltiples hilos de Python no ejecutarán su código Python simultáneamente (¡sí, lo escuchaste bien!). Puede parecer intuitivo generar varios hilos y dejar que ejecuten su código de Python simultáneamente, pero, debido al bloqueo del intérprete global en Python, todo lo que está haciendo es hacer que sus hilos se ejecuten en el mismo giro central a su vez. Los hilos de Python son buenos para las tareas unidas a IO, pero para lograr la paralelización real en Python para tareas unidas a CPU, es posible que desee usar el módulo de multiprocesamiento de Python.
A veces, el método print
podría no imprimir valores de inmediato. Por ejemplo,
# File some_file.py
import time
print ( "wtfpython" , end = "_" )
time . sleep ( 3 )
Esto imprimirá el wtfpython
después de 3 segundos debido al argumento end
porque el búfer de salida se descarga después de encontrar n
o cuando el programa termina la ejecución. Podemos obligar al búfer a lavar pasando el argumento flush=True
.
Lista de corte con los índices fuera de los límites no arroja errores
> >> some_list = [ 1 , 2 , 3 , 4 , 5 ]
> >> some_list [ 111 :]
[]
Cortar un iterable no siempre crea un nuevo objeto. Por ejemplo,
> >> some_str = "wtfpython"
> >> some_list = [ 'w' , 't' , 'f' , 'p' , 'y' , 't' , 'h' , 'o' , 'n' ]
> >> some_list is some_list [:] # False expected because a new object is created.
False
> >> some_str is some_str [:] # True because strings are immutable, so making a new object is of not much use.
True
int('١٢٣٤٥٦٧٨٩')
Devuelve 123456789
en Python 3. En Python, los caracteres decimales incluyen caracteres dígitos y todos los caracteres que pueden usarse para formar números de radio decimal, por ejemplo, U+0660, dígito árabe-indic cero. Aquí hay una historia interesante relacionada con este comportamiento de Python.
Puede separar literales numéricos con subrayos (para una mejor legibilidad) de Python 3 en adelante.
> >> six_million = 6_000_000
> >> six_million
6000000
> >> hex_address = 0xF00D_CAFE
> >> hex_address
4027435774
'abc'.count('') == 4
. Aquí hay una implementación aproximada del método count
, que dejaría las cosas más claras
def count ( s , sub ):
result = 0
for i in range ( len ( s ) + 1 - len ( sub )):
result += ( s [ i : i + len ( sub )] == sub )
return result
El comportamiento se debe a la coincidencia de la subcadena vacía ( ''
) con rebanadas de longitud 0 en la cadena original.
Algunas formas en que puedes contribuir a wtfpython,
Consulte Contriping.MD para más detalles. Siéntase libre de crear un nuevo tema para discutir cosas.
PD: No se comunique con solicitudes de retroceso, no se agregarán enlaces a menos que sean muy relevantes para el proyecto.
La idea y el diseño de esta colección se inspiraron inicialmente en el increíble proyecto WTFJS de Denys Dovhan. El abrumador apoyo de Pythonistas le dio la forma en la que se encuentra en este momento.
© Satwik Kansal
If you like wtfpython, you can use these quick links to share it with your friends,
Gorjeo | Linkedin | Facebook
I've received a few requests for the pdf (and epub) version of wtfpython. You can add your details here to get them as soon as they are finished.
¡Eso es todo amigos! For upcoming content like this, you can add your email here.