Características ocultas de Python

¿Cuáles son las características menos conocidas pero útiles del lenguaje de progtwigción Python?

  • Trate de limitar las respuestas al núcleo de Python.
  • Una característica por respuesta.
  • Dé un ejemplo y una breve descripción de la función, no solo un enlace a la documentación.
  • Etiqueta la característica usando un título como la primera línea.

Enlaces rápidos a las respuestas:

  • Desempaquetado de argumentos
  • Tirantes
  • Operadores de comparación de encadenamiento
  • Decoradores
  • Argumento predeterminado Gotchas / Peligros de argumentos predeterminados mutables
  • Descriptores
  • .get predeterminado de diccionario .get
  • Pruebas de Docstring
  • Sintaxis de corte de elipsis
  • Enumeración
  • Para / si no
  • Función como argumento iter ()
  • Expresiones generadoras
  • import this
  • Intercambio de valor en el lugar
  • Paso a paso de la lista
  • __missing__ objetos __missing__
  • Regex multilínea
  • Formato de cadena con nombre
  • Lista anidada / comprensión de generadores
  • Nuevos tipos en tiempo de ejecución
  • archivos .pth
  • Codificación ROT13
  • Depuración de regex
  • Enviando a generadores
  • Completar pestaña en intérprete interactivo
  • Expresión ternaria
  • try/except/else
  • Desembalaje + función de print()
  • with statement

Encadenamiento de operadores de comparación:

 >>> x = 5 >>> 1 < x < 10 True >>> 10 < x < 20 False >>> x < 10 < x*10 < 100 True >>> 10 > x <= 9 True >>> 5 == x > 4 True 

En caso de que piense que está haciendo 1 < x , que aparece como True , y luego comparando True < 10 , que también es True , entonces no, eso no es realmente lo que sucede (vea el último ejemplo). Realmente se traduce a 1 < x and x < 10 , y x < 10 and 10 < x * 10 and x*10 < 100 , pero con menos escritura y cada término solo se evalúa una vez.

Obtenga el árbol de análisis de expresiones regulares de python para depurar su expresión regular.

Las expresiones regulares son una gran característica de python, pero depurarlas puede ser una molestia, y es muy fácil equivocarse con una expresión regular.

Afortunadamente, python puede imprimir el árbol de análisis de expresiones regulares, pasando la bandera no documentada, experimental y oculta re.DEBUG (en realidad, 128) a re.compile .

 >>> re.compile("^\[font(?:=(?P[-+][0-9]{1,2}))?\](.*?)[/font]", re.DEBUG) at at_beginning literal 91 literal 102 literal 111 literal 110 literal 116 max_repeat 0 1 subpattern None literal 61 subpattern 1 in literal 45 literal 43 max_repeat 1 2 in range (48, 57) literal 93 subpattern 2 min_repeat 0 65535 any None in literal 47 literal 102 literal 111 literal 110 literal 116 

Una vez que entienda la syntax, puede detectar sus errores. Allí podemos ver que olvidé escapar de [] en [/font] .

Por supuesto, puede combinarlo con las banderas que desee, como las expresiones regulares comentadas:

 >>> re.compile(""" ^ # start of a line \[font # the font tag (?:=(?P # optional [font=+size] [-+][0-9]{1,2} # size specification ))? \] # end of tag (.*?) # text between the tags \[/font\] # end of the tag """, re.DEBUG|re.VERBOSE|re.DOTALL) 

enumerar

Envuelva un iterable con enumerar y producirá el elemento junto con su índice.

Por ejemplo:

 >>> a = ['a', 'b', 'c', 'd', 'e'] >>> for index, item in enumerate(a): print index, item ... 0 a 1 b 2 c 3 d 4 e >>> 

Referencias:

  • Tutorial de Python: técnicas de bucle
  • Documentos de Python: funciones incorporadas, enumerate
  • PEP 279

Creando objetos generadores.

Si tú escribes

 x=(n for n in foo if bar(n)) 

Puedes sacar el generador y asignarlo a x. Ahora significa que puedes hacer

 for n in x: 

La ventaja de esto es que no necesita almacenamiento intermedio, que necesitaría si lo hiciera

 x = [n for n in foo if bar(n)] 

En algunos casos esto puede llevar a una aceleración significativa.

Puede adjuntar muchas declaraciones if al final del generador, básicamente replicando nesteds para bucles:

 >>> n = ((a,b) for a in range(0,2) for b in range(4,6)) >>> for i in n: ... print i (0, 4) (0, 5) (1, 4) (1, 5) 

iter () puede tomar un argumento llamable

Por ejemplo:

 def seek_next_line(f): for c in iter(lambda: f.read(1),'\n'): pass 

La función iter(callable, until_value) llama repetidamente a callable y produce su resultado hasta que se devuelve el until_value hasta.

Tenga cuidado con los argumentos por defecto mutables

 >>> def foo(x=[]): ... x.append(1) ... print x ... >>> foo() [1] >>> foo() [1, 1] >>> foo() [1, 1, 1] 

En su lugar, debe usar un valor de centinela que indique “no dado” y reemplazarlo con el mutable que desea como predeterminado:

 >>> def foo(x=None): ... if x is None: ... x = [] ... x.append(1) ... print x >>> foo() [1] >>> foo() [1] 

Envío de valores a funciones de generador . Por ejemplo teniendo esta función:

 def mygen(): """Yield 5 until something else is passed back via send()""" a = 5 while True: f = (yield a) #yield a and possibly get f in return if f is not None: a = f #store the new value 

Usted puede:

 >>> g = mygen() >>> g.next() 5 >>> g.next() 5 >>> g.send(7) #we send this back to the generator 7 >>> g.next() #now it will yield 7 until we send something else 7 

Si no le gusta usar espacios en blanco para denotar ámbitos, puede usar el estilo C {} emitiendo:

 from __future__ import braces 

El argumento paso en operadores de corte. Por ejemplo:

 a = [1,2,3,4,5] >>> a[::2] # iterate over the whole list in 2-increments [1,3,5] 

El caso especial x[::-1] es un modismo útil para ‘x invertido’.

 >>> a[::-1] [5,4,3,2,1] 

Decoradores

Los decoradores permiten envolver una función o método en otra función que puede agregar funcionalidad, modificar argumentos o resultados, etc. Usted escribe a los decoradores una línea sobre la definición de la función, comenzando con un signo “en” (@).

El ejemplo muestra un decorador print_args que imprime los argumentos de la función decorada antes de llamarla:

 >>> def print_args(function): >>> def wrapper(*args, **kwargs): >>> print 'Arguments:', args, kwargs >>> return function(*args, **kwargs) >>> return wrapper >>> @print_args >>> def write(text): >>> print text >>> write('foo') Arguments: ('foo',) {} foo 

La syntax de for … else (ver http://docs.python.org/ref/for.html )

 for i in foo: if i == 0: break else: print("i was never 0") 

El bloque “else” se ejecutará normalmente al final del bucle for, a menos que se llame a break.

El código anterior podría ser emulado de la siguiente manera:

 found = False for i in foo: if i == 0: found = True break if not found: print("i was never 0") 

A partir de 2.5, los dicts tienen un método especial __missing__ que se invoca para los elementos que faltan:

 >>> class MyDict(dict): ... def __missing__(self, key): ... self[key] = rv = [] ... return rv ... >>> m = MyDict() >>> m["foo"].append(1) >>> m["foo"].append(2) >>> dict(m) {'foo': [1, 2]} 

También hay una subclase dict en collections llamadas defaultdict que hace prácticamente lo mismo pero llama a una función sin argumentos para elementos que no existen:

 >>> from collections import defaultdict >>> m = defaultdict(list) >>> m["foo"].append(1) >>> m["foo"].append(2) >>> dict(m) {'foo': [1, 2]} 

Recomiendo convertir dichos dicts a dicts regulares antes de pasarlos a funciones que no esperan tales subclases. Una gran cantidad de código utiliza d[a_key] y captura KeyErrors para verificar si existe un elemento que agregaría un nuevo elemento al dict.

Intercambio de valor en el lugar

 >>> a = 10 >>> b = 5 >>> a, b (10, 5) >>> a, b = b, a >>> a, b (5, 10) 

El lado derecho de la asignación es una expresión que crea una tupla nueva. El lado izquierdo de la asignación desempaqueta inmediatamente esa tupla (no referenciada) a los nombres a y b .

Después de la asignación, la nueva tupla no tiene referencia y está marcada para la recolección de basura, y los valores vinculados a y b se han intercambiado.

Como se señaló en la sección de tutoriales de Python sobre estructuras de datos ,

Tenga en cuenta que la asignación múltiple es solo una combinación de empaquetamiento de tuplas y desempaquetado de secuencias.

Expresiones regulares legibles

En Python, puede dividir una expresión regular en varias líneas, nombrar sus coincidencias e insertar comentarios.

Ejemplo de syntax detallada (desde Dive into Python ):

 >>> pattern = """ ... ^ # beginning of string ... M{0,4} # thousands - 0 to 4 M's ... (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), ... # or 500-800 (D, followed by 0 to 3 C's) ... (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), ... # or 50-80 (L, followed by 0 to 3 X's) ... (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), ... # or 5-8 (V, followed by 0 to 3 I's) ... $ # end of string ... """ >>> re.search(pattern, 'M', re.VERBOSE) 

Ejemplo de nombres de coincidencias (del CÓMO de expresiones regulares )

 >>> p = re.compile(r'(?P\b\w+\b)') >>> m = p.search( '(((( Lots of punctuation )))' ) >>> m.group('word') 'Lots' 

También puede escribir verbalmente una expresión regular sin usar re.VERBOSE gracias a la concatenación de cadenas literales.

 >>> pattern = ( ... "^" # beginning of string ... "M{0,4}" # thousands - 0 to 4 M's ... "(CM|CD|D?C{0,3})" # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), ... # or 500-800 (D, followed by 0 to 3 C's) ... "(XC|XL|L?X{0,3})" # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), ... # or 50-80 (L, followed by 0 to 3 X's) ... "(IX|IV|V?I{0,3})" # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), ... # or 5-8 (V, followed by 0 to 3 I's) ... "$" # end of string ... ) >>> print pattern "^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$" 

Función desempaquetando argumento

Puede descomprimir una lista o un diccionario como argumentos de función utilizando * y ** .

Por ejemplo:

 def draw_point(x, y): # do some magic point_foo = (3, 4) point_bar = {'y': 3, 'x': 2} draw_point(*point_foo) draw_point(**point_bar) 

Atajo muy útil ya que las listas, tuplas y dados son ampliamente utilizados como contenedores.

ROT13 es una encoding válida para el código fuente, cuando se utiliza la statement de encoding correcta en la parte superior del archivo de código:

 #!/usr/bin/env python # -*- coding: rot13 -*- cevag "Uryyb fgnpxbiresybj!".rapbqr("rot13") 

Creando nuevos tipos de forma totalmente dinámica.

 >>> NewType = type("NewType", (object,), {"x": "hello"}) >>> n = NewType() >>> nx "hello" 

que es exactamente lo mismo que

 >>> class NewType(object): >>> x = "hello" >>> n = NewType() >>> nx "hello" 

Probablemente no sea lo más útil, pero es bueno saberlo.

Edición : el nombre fijo del nuevo tipo, debe ser NewType para ser exactamente lo mismo que con class statement de class .

Editar : Ajustó el título para describir con mayor precisión la característica.

Gestores de contexto y la statement ” with

Introducido en PEP 343 , un administrador de contexto es un objeto que actúa como un contexto de tiempo de ejecución para un conjunto de declaraciones.

Dado que la función utiliza nuevas palabras clave, se presenta gradualmente: está disponible en Python 2.5 a través de la directiva __future__ . Python 2.6 y superior (incluido Python 3) lo tiene disponible de forma predeterminada.

He usado mucho la statement “con” porque creo que es una construcción muy útil, aquí hay una demostración rápida:

 from __future__ import with_statement with open('foo.txt', 'w') as f: f.write('hello!') 

Lo que está sucediendo aquí detrás de la escena, es que la statement “with” llama a los __enter__ especiales __enter__ y __exit__ en el objeto de archivo. Los detalles de la excepción también se pasan a __exit__ si se __exit__ una excepción desde el cuerpo de la statement, lo que permite que la gestión de excepciones ocurra allí.

Lo que esto hace por usted en este caso particular es que garantiza que el archivo se cierra cuando la ejecución queda fuera del scope del conjunto de aplicaciones, independientemente de si esto ocurre normalmente o si se produjo una excepción. Es básicamente una forma de abstraer el código común de manejo de excepciones.

Otros casos de uso comunes para esto incluyen el locking con subprocesos y transacciones de base de datos.

Los diccionarios tienen un método get ()

Los diccionarios tienen un método ‘get ()’. Si lo hace d [‘clave’] y la clave no está allí, obtendrá una excepción. Si lo hace d.get (‘clave’), obtendrá de nuevo Ninguno si ‘clave’ no está allí. Puede agregar un segundo argumento para recuperar ese elemento en lugar de Ninguno, por ejemplo: d.get (‘clave’, 0).

Es genial para cosas como sumr números:

sum[value] = sum.get(value, 0) + 1

Descriptors

They’re the magic behind a whole bunch of core Python features.

When you use dotted access to look up a member (eg, xy), Python first looks for the member in the instance dictionary. If it’s not found, it looks for it in the class dictionary. If it finds it in the class dictionary, and the object implements the descriptor protocol, instead of just returning it, Python executes it. A descriptor is any class that implements the __get__ , __set__ , or __delete__ methods.

Here’s how you’d implement your own (read-only) version of property using descriptors:

 class Property(object): def __init__(self, fget): self.fget = fget def __get__(self, obj, type): if obj is None: return self return self.fget(obj) 

and you’d use it just like the built-in property():

 class MyClass(object): @Property def foo(self): return "Foo!" 

Descriptors are used in Python to implement properties, bound methods, static methods, class methods and slots, amongst other things. Understanding them makes it easy to see why a lot of things that previously looked like Python ‘quirks’ are the way they are.

Raymond Hettinger has an excellent tutorial that does a much better job of describing them than I do.

Conditional Assignment

 x = 3 if (y == 1) else 2 

It does exactly what it sounds like: “assign 3 to x if y is 1, otherwise assign 2 to x”. Note that the parens are not necessary, but I like them for readability. You can also chain it if you have something more complicated:

 x = 3 if (y == 1) else 2 if (y == -1) else 1 

Though at a certain point, it goes a little too far.

Note that you can use if … else in any expression. Por ejemplo:

 (func1 if y == 1 else func2)(arg1, arg2) 

Here func1 will be called if y is 1 and func2, otherwise. In both cases the corresponding function will be called with arguments arg1 and arg2.

Analogously, the following is also valid:

 x = (class1 if y == 1 else class2)(arg1, arg2) 

where class1 and class2 are two classes.

Doctest : documentation and unit-testing at the same time.

Example extracted from the Python documentation:

 def factorial(n): """Return the factorial of n, an exact integer >= 0. If the result is small enough to fit in an int, return an int. Else return a long. >>> [factorial(n) for n in range(6)] [1, 1, 2, 6, 24, 120] >>> factorial(-1) Traceback (most recent call last): ... ValueError: n must be >= 0 Factorials of floats are OK, but the float must be an exact integer: """ import math if not n >= 0: raise ValueError("n must be >= 0") if math.floor(n) != n: raise ValueError("n must be exact integer") if n+1 == n: # catch a value like 1e300 raise OverflowError("n too large") result = 1 factor = 2 while factor <= n: result *= factor factor += 1 return result def _test(): import doctest doctest.testmod() if __name__ == "__main__": _test() 

Named formatting

% -formatting takes a dictionary (also applies %i/%s etc. validation).

 >>> print "The %(foo)s is %(bar)i." % {'foo': 'answer', 'bar':42} The answer is 42. >>> foo, bar = 'question', 123 >>> print "The %(foo)s is %(bar)i." % locals() The question is 123. 

And since locals() is also a dictionary, you can simply pass that as a dict and have % -substitions from your local variables. I think this is frowned upon, but simplifies things..

New Style Formatting

 >>> print("The {foo} is {bar}".format(foo='answer', bar=42)) 

To add more python modules (espcially 3rd party ones), most people seem to use PYTHONPATH environment variables or they add symlinks or directories in their site-packages directories. Another way, is to use *.pth files. Here’s the official python doc’s explanation:

“The most convenient way [to modify python’s search path] is to add a path configuration file to a directory that’s already on Python’s path, usually to the …/site-packages/ directory. Path configuration files have an extension of .pth, and each line must contain a single path that will be appended to sys.path. (Because the new paths are appended to sys.path, modules in the added directories will not override standard modules. This means you can’t use this mechanism for installing fixed versions of standard modules.)”

Exception else clause:

 try: put_4000000000_volts_through_it(parrot) except Voom: print "'E's pining!" else: print "This parrot is no more!" finally: end_sketch() 

The use of the else clause is better than adding additional code to the try clause because it avoids accidentally catching an exception that wasn’t raised by the code being protected by the try … except statement.

See http://docs.python.org/tut/node10.html

Re-raising exceptions :

 # Python 2 syntax try: some_operation() except SomeError, e: if is_fatal(e): raise handle_nonfatal(e) # Python 3 syntax try: some_operation() except SomeError as e: if is_fatal(e): raise handle_nonfatal(e) 

The ‘raise’ statement with no arguments inside an error handler tells Python to re-raise the exception with the original traceback intact , allowing you to say “oh, sorry, sorry, I didn’t mean to catch that, sorry, sorry.”

If you wish to print, store or fiddle with the original traceback, you can get it with sys.exc_info(), and printing it like Python would is done with the ‘traceback’ module.

Main messages 🙂

 import this # btw look at this module's source :) 

De-cyphered :

El zen de Python, por Tim Peters

Lo bello es mejor que lo feo.
Explícito es mejor que implícito.
Lo simple es mejor que lo complejo.
Complejo es mejor que complicado.
Plano es mejor que nested.
Lo escaso es mejor que lo denso.
La legibilidad cuenta.
Los casos especiales no son lo suficientemente especiales para romper las reglas.
Aunque la practicidad supera a la pureza.
Errors should never pass silently.
A menos que sea explícitamente silenciado.
Ante la ambigüedad, rechace la tentación de adivinar. Debe haber una, y preferiblemente solo una, obvia forma de hacerlo.
Aunque esa forma puede no ser obvia al principio a menos que seas holandés.
Ahora es mejor que nunca.
Aunque nunca es a menudo mejor que ahora.
Si la implementación es difícil de explicar, es una mala idea.
Si la implementación es fácil de explicar, puede ser una buena idea.
Los espacios de nombres son una gran idea, ¡hagamos más de ellos!

Interactive Interpreter Tab Completion

 try: import readline except ImportError: print "Unable to load readline module." else: import rlcompleter readline.parse_and_bind("tab: complete") >>> class myclass: ... def function(self): ... print "my function" ... >>> class_instance = myclass() >>> class_instance. class_instance.__class__ class_instance.__module__ class_instance.__doc__ class_instance.function >>> class_instance.function() 

You will also have to set a PYTHONSTARTUP environment variable.

Nested list comprehensions and generator expressions:

 [(i,j) for i in range(3) for j in range(i) ] ((i,j) for i in range(4) for j in range(i) ) 

These can replace huge chunks of nested-loop code.

Operator overloading for the set builtin:

 >>> a = set([1,2,3,4]) >>> b = set([3,4,5,6]) >>> a | b # Union {1, 2, 3, 4, 5, 6} >>> a & b # Intersection {3, 4} >>> a < b # Subset False >>> a - b # Difference {1, 2} >>> a ^ b # Symmetric Difference {1, 2, 5, 6} 

More detail from the standard library reference: Set Types