python para todos repasa aspectos esenciales de
TRANSCRIPT
Python para todos es un tutorial básico del lenguaje de programación Pythonescrito por Raúl González Duque y distribuido gratuitamente bajo licenciaCreativeCommons.Adecuadoparacualquiernivel,Pythonparatodosrepasaaspectosesencialesdeeste lenguaje: tipos básicos, control de flujo, funciones, orientación a objetos,programaciónfuncional,excepciones,entrada/salida,etcétera.La introducción te permitirá conocer las características principales de Python,cómoinstalarloyporquéhacerlo.
RaúlGonzálezDuque
Pythonparatodos
ePubr1.0Titivillus10.12.2017
Títulooriginal:PythonparatodosRaúlGonzálezDuque,2008Imagendeportada:IanChien
Editordigital:TitivillusePubbaser1.2
PythonparatodosporRaúlGonzálezDuque
Este libro se distribuye bajo una licencia Creative Commons Reconocimiento2.5España.Ustedeslibrede:
copiar,distribuirycomunicarpúblicamentelaobra
hacerobrasderivadas
Bajolascondicionessiguientes:
Reconocimiento.Debereconocerydarcréditoalautororiginal(RaúlGonzálezDuque)
Puede descargar la versiónmás reciente de este libro gratuitamente en lawebhttp://mundogeek.net/tutorial-python/
LaimagendeportadaesunafotografíadeunapitónverdedelaespecieMoreliaviridis cuyo autor es Ian Chien. La fotografía está licenciada bajo CreativeCommonsAttributionShareAlike2.0
INTRODUCCIÓN
¿QuéesPython?Python es un lenguaje de programación creado por Guido van Rossum aprincipios de los años 90 cuyo nombre está inspirado en el grupo de cómicosingleses “MontyPython”.Esun lenguaje similar aPerl, pero conuna sintaxismuylimpiayquefavoreceuncódigolegible.Se trata de un lenguaje interpretado o de script, con tipado dinámico,fuertementetipado,multiplataformayorientadoaobjetos.
LenguajeinterpretadoodescriptUn lenguaje interpretado o de script es aquel que se ejecuta utilizando unprograma intermedio llamado intérprete, en lugar de compilar el código alenguaje máquina que pueda comprender y ejecutar directamente unacomputadora(lenguajescompilados).Laventajade los lenguajescompiladosesquesuejecuciónesmásrápida.Sinembargoloslenguajesinterpretadossonmásflexiblesymásportables.
Python tiene, no obstante, muchas de las características de los lenguajescompilados, por lo que se podría decir que es semi interpretado. En Python,comoenJavaymuchosotroslenguajes,elcódigofuentesetraduceaunpseudocódigo máquina intermedio llamado bytecode la primera vez que se ejecuta,generando archivos .pyc o .pyo (bytecode optimizado), que son los que seejecutaránensucesivasocasiones.
TipadodinámicoLacaracterísticadetipadodinámicoserefiereaquenoesnecesariodeclarareltipo de dato que va a contener una determinada variable, sino que su tipo sedeterminaráentiempodeejecuciónsegúneltipodelvaloralqueseasigne,yeltipodeestavariablepuedecambiarsiseleasignaunvalordeotrotipo.
FuertementetipadoNosepermitetrataraunavariablecomosifueradeuntipodistintoalquetiene,es necesario convertir de forma explícita dicha variable al nuevo tipopreviamente. Por ejemplo, si tenemos una variable que contiene un texto(variabledetipocadenaostring)nopodremostratarlacomounnúmero(sumarlacadena“9”yelnúmero8).Enotroslenguajeseltipodelavariablecambiaríapara adaptarse al comportamiento esperado, aunque esto es más propenso aerrores.
MultiplataformaEl intérprete de Python está disponible en multitud de plataformas (UNIX,Solaris,Linux,DOS,Windows,OS/2,MacOS,etc.)porloquesinoutilizamoslibreríasespecíficasdecadaplataformanuestroprogramapodrácorrerentodosestossistemassingrandescambios.
OrientadoaobjetosLa orientación a objetos es un paradigma de programación en el que losconceptosdelmundorealrelevantesparanuestroproblemasetrasladanaclasesyobjetosennuestroprograma.Laejecucióndelprogramaconsisteenunaseriedeinteraccionesentrelosobjetos.Pythontambiénpermite laprogramaciónimperativa,programaciónfuncionalyprogramaciónorientadaaaspectos.
¿PorquéPython?Pythonesun lenguajeque todoelmundodebería conocer.Su sintaxis simple,claray sencilla; el tipadodinámico, el gestordememoria, lagran cantidaddelibreríasdisponiblesylapotenciadellenguaje,entreotros,hacenquedesarrollarunaaplicaciónenPythonseasencillo,muyrápidoy,loqueesmásimportante,divertido.La sintaxis de Python es tan sencilla y cercana al lenguaje natural que losprogramaselaboradosenPythonparecenpseudocódigo.Porestemotivosetrataademásdeunodelosmejoreslenguajesparacomenzaraprogramar.Pythonnoesadecuadosinembargopara laprogramacióndebajoniveloparaaplicacionesenlasqueelrendimientoseacrítico.Algunos casos de éxito en el uso de Python son Google, Yahoo, la NASA,IndustriasLight&Magic, y todas las distribucionesLinux, en las quePythoncadavezrepresentauntantoporcientomayordelosprogramasdisponibles.
InstalacióndePythonExisten varias implementaciones distintas de Python: CPython, Jython,IronPython,PyPy,etc.CPythones lamásutilizada, lamás rápiday lamásmadura.Cuando lagentehabladePythonnormalmenteserefiereaestaimplementación.EnestecasotantoelintérpretecomolosmódulosestánescritosenC.JythoneslaimplementaciónenJavadePython,mientrasqueIronPythonessucontrapartida en C# (.NET). Su interés estriba en que utilizando estasimplementaciones se pueden utilizar todas las librerías disponibles para losprogramadoresdeJavay.NET.PyPy, por último, como habréis adivinado por el nombre, se trata de unaimplementaciónenPythondePython.CPythonestáinstaladopordefectoenlamayorpartedelasdistribucionesLinuxyenlasúltimasversionesdeMacOS.Paracomprobarsiestáinstaladoabreunaterminalyescribepython.Siestáinstaladoseiniciarálaconsolainteractivade
Pythonyobtendremosalgoparecidoalosiguiente:Python2.5.1(r251:54863,May22007,16:56:35)
[GCC4.1.2(Ubuntu4.1.2-0ubuntu4)]onlinux2
Type“help”,“copyright”,“credits”or“license”formoreinformation.
>>>
LaprimeralíneanosindicalaversióndePythonquetenemosinstalada.Alfinalpodemos ver el prompt (>>>) que nos indica que el intérprete está esperandocódigodelusuario.Podemossalirescribiendoexit(),opulsandoControl+D.Sinotemuestraalgoparecidonotepreocupes,instalarPythonesmuysencillo.PuedesdescargarlaversióncorrespondienteatusistemaoperativodesdelawebdePython,enhttp://www.python.org/download/.ExisteninstaladoresparaWindowsyMacOS.SiutilizasLinuxesmuyprobableque puedas instalarlo usando la herramienta de gestión de paquetes de tudistribución,aunquetambiénpodemosdescargarlaaplicacióncompiladadesdelawebdePython.
HerramientasbásicasExistendosformasdeejecutarcódigoPython.Podemosescribirlíneasdecódigoen el intérprete y obtener una respuesta del intérprete para cada línea (sesióninteractiva)obienpodemosescribirelcódigodeunprogramaenunarchivodetextoyejecutarlo.A la hora de realizar una sesión interactiva os aconsejo instalar y utilizariPython, en lugar de la consola interactiva de Python. Se puede encontrar enhttp://ipython.scipy.org/. iPython cuenta con características añadidas muyinteresantes, como el autocompletado o el operador ?. (para activar lacaracterísticadeautocompletadoenWindowsesnecesarioinstalarPyReadline,quepuededescargarsedesdehttp://ipython.scipy.org/moin/PyReadline/Intro)Lafuncióndeautocompletadoselanzapulsandoeltabulador.SiescribimosfiypulsamosTabnosmostraráunalistadelosobjetosquecomienzanconfi(file,filteryfinally).Siescribimosfile.ypulsamosTabnosmostraráunalistadelosmétodosypropiedadesdelobjetofile.
Eloperador?nosmuestrainformaciónsobrelosobjetos.Seutilizaañadiendoelsímbolodeinterrogaciónalfinaldelnombredelobjetodelcualqueremosmásinformación.Porejemplo:
In[3]:str?
Type:type
BaseClass:
StringForm:
Namespace:Pythonbuiltin
Docstring:
str(object)->string
Returnanicestringrepresentationoftheobject.
Iftheargumentisastring,thereturnvalueisthesameobject.
En el campo de IDEs y editores de código gratuitos PyDEV(http://pydev.sourceforge.net/) se alza como cabeza de serie. PyDEV es unplugin para Eclipse que permite utilizar este IDE multiplataforma paraprogramarenPython.Cuenta conautocompletadodecódigo (con informaciónsobre cada elemento), resaltadode sintaxis, undepuradorgráfico, resaltadodeerrores,exploradordeclases,formateodelcódigo,refactorización,etc.Sindudaeslaopciónmáscompleta,sobretodosiinstalamoslasextensionescomerciales,aunquenecesitadeunacantidadimportantedememoriaynoesdeltodoestable.Otras opciones gratuitas a considerar son SPE o Stani’s Python Editor(http://sourceforge.net/projects/spe/), Eric (http://die-offenbachs.de/eric/), BOAConstructor(http://boa-constructor.sourceforge.net/)oinclusoemacsovim.Si no te importa desembolsar algo de dinero, Komodo(http://www.activestate.com/komodo_ide/) y Wing IDE(http://www.wingware.com/) son tambiénmuy buenas opciones, conmontonesde características interesantes, como PyDEV, pero mucho más estables yrobustos. Además, si desarrollas software libre no comercial puedes contactarconWingWareyobtener,conunpocodesuerte,unalicenciagratuitaparaWingIDEProfessional:)
MIPRIMERPROGRAMAENPYTHON
Como comentábamos en el capítulo anterior existen dos formas de ejecutarcódigoPython,bienenunasesióninteractiva(líneaalínea)conelintérprete,obiendelaformahabitual,escribiendoelcódigoenunarchivodecódigofuenteyejecutándolo.ElprimerprogramaquevamosaescribirenPythoneselclásicoHolaMundo,yenestelenguajeestansimplecomo:
print“HolaMundo”
Vamosaprobarloprimeroenel intérprete.Ejecutapythono ipythonsegún tuspreferencias, escribe la línea anterior y pulsa Enter. El intérprete responderámostrandoenlaconsolaeltextoHolaMundo.Vamos ahora a crearun archivode texto con el código anterior, de formaquepudiéramos distribuir nuestro pequeño gran programa entre nuestros amigos.AbretueditordetextopreferidoobienelIDEquehayaselegidoycopialalínea
anterior.Guárdalocomohola.py,porejemplo.Ejecutar esteprogramaes tan sencillo como indicarle el nombredel archivo aejecutaralintérpretedePython
pythonhola.py
perovamosavercómosimplificarloaúnmás.Si utilizas Windows los archivos .py ya estarán asociados al intérprete dePython, por lo que basta hacer doble clic sobre el archivo para ejecutar elprograma.Sinembargocomoesteprogramanohacemásqueimprimiruntextoenlaconsola, laejecuciónesdemasiadorápidaparapoderverlosiquiera.Pararemediarlo,vamosaañadirunanueva líneaqueespere laentradadedatosporpartedelusuario.
print“HolaMundo”
raw_input()
De esta forma se mostrará una consola con el texto Hola Mundo hasta quepulsemosEnter.Si utilizas Linux (u otro Unix) para conseguir este comportamiento, es decir,paraqueelsistemaoperativoabraelarchivo.pyconelintérpreteadecuado,esnecesarioañadirunanuevalíneaalprincipiodelarchivo:
#!/usr/bin/python
print“HolaMundo”
raw_input()
A esta línea se le conoce en el mundo Unix como shebang, hashbang osharpbang.Elpardecaracteres#!indicaalsistemaoperativoquedichoscriptsedebe ejecutar utilizando el intérprete especificado a continuación. De esto sedesprende, evidentemente, que si esta no es la ruta en la que está instaladonuestrointérpretedePython,esnecesariocambiarla.Otraopciónesutilizarelprogramaenv(deenvironment,entorno)parapreguntaralsistemaporlarutaalintérpretedePython,deformaquenuestrosusuariosnotengan ningún problema si se diera el caso de que el programa no estuvierainstaladoendicharuta:
#!/usr/bin/envpython
print“HolaMundo”
raw_input()
Por supuesto además de añadir el shebang, tendremos que dar permisos deejecuciónalprograma.
chmod+xhola.py
Ylisto,sihacemosdobleclicelprogramaseejecutará,mostrandounaconsolaconeltextoHolaMundo,comoenelcasodeWindows.Tambiénpodríamoscorrer elprogramadesde la consola comosi trataradeunejecutablecualquiera:
./hola.py
TIPOSBÁSICOSEnPythonlostiposbásicossedividenen:
Números,comopuedenser3(entero),15.57(decomaflotante)o7+5j(complejos)Cadenasdetexto,como“HolaMundo”Valoresbooleanos:True(cierto)yFalse(falso).
Vamosacrearunpardevariablesamododeejemplo.Unadetipocadenayunadetipoentero:
#estoesunacadena
c=“HolaMundo”
#yestoesunentero
e=23
#podemoscomprobarloconlafuncióntype
type(c)
type(e)
ComoveisenPython,adiferenciademuchosotros lenguajes,nosedeclaraeltipodelavariablealcrearla.EnJava,porejemplo,escribiríamos:
Stringc=“HolaMundo”;
inte=23;
Este pequeño ejemplo también nos ha servido para presentar los comentariosinlineenPython:cadenasdetextoquecomienzanconelcarácter#yquePythonignora totalmente.Haymás tipos de comentarios, de los que hablaremosmásadelante.
NúmerosComo decíamos, en Python se pueden representar números enteros, reales ycomplejos.
EnterosLosnúmerosenterossonaquellosnúmerospositivosonegativosquenotienendecimales(ademásdelcero).EnPythonsepuedenrepresentarmedianteeltipoint(deinteger,entero)oeltipolong(largo).Laúnicadiferenciaesqueeltipolongpermitealmacenarnúmerosmásgrandes.Esaconsejablenoutilizareltipolongamenosqueseanecesario,paranomalgastarmemoria.EltipointdePythonseimplementaabajonivelmedianteuntipolongdeC.YdadoquePythonutilizaCpordebajo,comoC,yadiferenciadeJava,elrangodelosvaloresquepuederepresentardependedelaplataforma.EnlamayorpartedelasmáquinasellongdeCsealmacenautilizando32bits,es decir, mediante el uso de una variable de tipo int de Python podemos
almacenarnúmerosde-231a231-1,o loquees lomismo,de-2.147.483.648a2.147.483.647. En plataformas de 64 bits, el rango es de-9.223.372.036.854.775.808hasta9.223.372.036.854.775.807.El tipo long de Python permite almacenar números de cualquier precisión,estandolimitadossoloporlamemoriadisponibleenlamáquina.Alasignarunnúmeroaunavariableestapasaráatenertipoint,amenosqueelnúmeroseatangrandecomopararequerirelusodeltipolong.
#type(entero)devolveríaint
entero=23
También podemos indicar a Python que un número se almacene usando longañadiendounaLalfinal:
#type(entero)devolveríalong
entero=23L
Elliteralqueseasignaa lavariable tambiénsepuedeexpresarcomounoctal,anteponiendouncero:
#027octal=23enbase10
entero=027
obienenhexadecimal,anteponiendoun0x:#0x17hexadecimal=23enbase10
entero=0x17
RealesLos números reales son los que tienen decimales. En Python se expresanmedianteeltipofloat.Enotros lenguajesdeprogramación,comoC, tenemostambiéneltipodouble,similarafloatperodemayorprecisión(double=dobleprecisión).Python,sinembargo,implementasutipofloatabajonivelmedianteunavariabledetipodoubledeC,esdecir,utilizando64bits, luegoenPythonsiempreseutilizadobleprecisión,yenconcretosesigueelestándarIEEE754:1bitparaelsigno,11paraelexponente,y52paralamantisa.Estosignificaquelos valores que podemos representar van desde ±2,2250738585072020×10-308hasta±1,7976931348623157×10308.Lamayorpartedeloslenguajesdeprogramaciónsiguenelmismoesquemaparalarepresentacióninterna.Perocomomuchossabréisestatienesuslimitaciones,impuestasporelhardware.PoresodesdePython2.4contamostambiénconunnuevo tipoDecimal, para el caso de que se necesite representar fracciones deformamásprecisa.Sinembargoestetipoestáfueradelalcancedeestetutorial,y
sólo es necesario para el ámbito de la programación científica y otrosrelacionados.Paraaplicacionesnormalespodéisutilizareltipofloatsinmiedo,comohavenidohaciéndosedesdehaceaños,aunqueteniendoencuentaquelosnúmerosencoma flotanteno sonprecisos (ni enesteni enotros lenguajesdeprogramación).Para representar un número real en Python se escribe primero la parte entera,seguidodeunpuntoyporúltimolapartedecimal.
real=0.2703
Tambiénsepuedeutilizarnotacióncientífica,yañadirunae(deexponente)paraindicarunexponenteenbase10.Porejemplo:
real=0.1e-3
seríaequivalentea0.1×10-3=0.1×0.001=0.0001
ComplejosLosnúmeroscomplejossonaquellosquetienenparteimaginaria.Sinoconocíasdesuexistencia,esmásqueprobablequenuncalovayasanecesitar,porloquepuedes saltarte este apartado tranquilamente. De hecho la mayor parte delenguajesdeprogramación carecende este tipo, aunque seamuyutilizadoporingenierosycientíficosengeneral.Enelcasodequenecesitéisutilizarnúmeroscomplejos,osimplementetengáiscuriosidad, os diré que este tipo, llamado complex en Python, también sealmacenausandocomaflotante,debidoaqueestosnúmerossonunaextensiónde los números reales. En concreto se almacena en una estructura de C,compuesta por dos variables de tipo double, sirviendo una de ellas paraalmacenarlaparterealylaotraparalaparteimaginaria.LosnúmeroscomplejosenPythonserepresentandelasiguienteforma:
complejo=2.1+7.8j
OperadoresVeamosahoraquépodemoshacerconnuestrosnúmerosusandolosoperadorespordefecto.Paraoperacionesmáscomplejaspodemosrecurriralmódulomath.
Operadoresaritméticos
Operador Descripción Ejemplo
+ Suma r=3+2#res5
- Resta r=4-7#res-3
- Negación r=-7#res-7
* Multiplicación r=2*6#res12
** Exponente r=2**6#res64
/ División r=3.5/2#res1.75
// Divisiónentera r=3.5//2#res1.0% Módulo r=7%2#res1
Puedequetengáisdudassobrecómofuncionaeloperadordemódulo,ycuálesladiferenciaentredivisiónydivisiónentera.Eloperadordemódulonohaceotracosaquedevolvernoselrestodeladivisiónentre los dos operandos. En el ejemplo, 7/2 sería 3, con 1 de resto, luego elmóduloes1.La diferencia entre división y división entera no es otra que la que indica sunombre.Enladivisiónelresultadoquesedevuelveesunnúmeroreal,mientrasqueenladivisiónenteraelresultadoquesedevuelveessololaparteentera.No obstante hay que tener en cuenta que si utilizamos dos operandos enteros,Python determinará que queremos que la variable resultado también sea unentero,porloqueelresultadode,porejemplo,3/2y3//2seríaelmismo:1.Si quisiéramos obtener los decimales necesitaríamos que almenos uno de losoperandosfueraunnúmeroreal,bienindicandolosdecimales
r=3.0/2
obienutilizandolafunciónfloat(noesnecesarioquesepáisloquesignificaeltérminofunción,niquerecordéisestaforma,loveremosunpocomásadelante):
r=float(3)/2
Estoesasíporquecuandosemezclantiposdenúmeros,Pythonconviertetodoslosoperandosaltipomáscomplejodeentrelostiposdelosoperandos.
OperadoresaniveldebitSinoconocéisestosoperadoresespocoprobablequevayáisanecesitarlos,porloquepodéisobviarestaparte.Siaúnasítenéiscuriosidadosdiréqueestossonoperadoresqueactúansobrelasrepresentacionesenbinariodelosoperandos.
Porejemplo,siveisunaoperacióncomo3&2,loqueestáisviendoesunandbitabitentrelosnúmerosbinarios11y10(lasrepresentacionesenbinariode3y2).Eloperadorand(&),delinglés“y”,devuelve1sielprimerbitoperandoes1yelsegundobitoperandoes1.Sedevuelve0encasocontrario.Elresultadodeaplicarandbitabita11y10seríaentonceselnúmerobinario10,oloqueeslomismo,2endecimal(elprimerdígitoes1paraambascifras,mientrasqueelsegundoes1sóloparaunadeellas).El operadoror (|), del inglés “o”, devuelve 1 si el primer operando es 1oelsegundooperandoes1.Paraelrestodecasossedevuelve0.Eloperadorxoruorexclusivo(^)devuelve1siunodelosoperandoses1yelotronoloes.Eloperadornot(~),delinglés“no”,sirveparanegarunoaunocadabit;esdecir,sieloperandoes0,cambiaa1ysies1,cambiaa0.Porúltimolosoperadoresdedesplazamiento(<<y>>)sirvenparadesplazarlosbitsnposicioneshacialaizquierdaoladerecha.
Operador Descripción Ejemplo& and r=3&2#res2
| or r=3|2#res3
^ xor r=3^2#res1
~ not r=~3#res-4
<< Desplazamientoizq. r=3<<1#res6>> Desplazamientoder. r=3>>1#res1
CadenasLascadenasnosonmásquetextoencerradoentrecomillassimples(‘cadena’)odobles(“cadena”).Dentrodelascomillassepuedenañadircaracteresespecialesescapándoloscon\,como\n,elcarácterdenuevalínea,o\t,eldetabulación.Una cadena puede estar precedida por el carácteru o el carácterr, los cualesindican, respectivamente, que se trata de una cadena que utiliza codificaciónUnicodeyunacadenaraw(delinglés,cruda).Lascadenasrawsedistinguendelasnormalesenqueloscaracteresescapadosmediantelabarrainvertida(\)nose sustituyen por sus contrapartidas. Esto es especialmente útil, por ejemplo,paralasexpresionesregulares,comoveremosenelcapítulocorrespondiente.
unicode=u“äóè”
raw=r“\n”
Tambiénesposibleencerrarunacadenaentretriplescomillas(simplesodobles).De esta forma podremos escribir el texto en varias líneas, y al imprimir lacadena,serespetaránlossaltosdelíneaqueintrodujimossintenerquerecurriralcarácter\n,asícomolascomillassintenerqueescaparlas.
triple=“““primeralinea
estoseveraenotralinea”””
Lascadenas tambiénadmitenoperadorescomo+,que funciona realizandounaconcatenacióndelascadenasutilizadascomooperandosy*,enlaqueserepitela cadena tantas veces como lo indique el número utilizado como segundooperando.
a=“uno”
b=“dos”
c=a+b#ces“unodos”
c=a*3#ces“unounouno”
BooleanosComo decíamos al comienzo del capítulo una variable de tipo booleano sólopuede tener dos valores: True (cierto) y False (falso). Estos valores sonespecialmenteimportantesparalasexpresionescondicionalesylosbucles,comoveremosmásadelante.Enrealidadeltipobool(eltipodelosbooleanos)esunasubclasedeltipoint.Puedequeestonotengamuchosentidoparatisinoconoceslostérminosdelaorientación a objetos, que veremos más adelante, aunque tampoco es nadaimportante.Estos son los distintos tipos de operadores con los que podemos trabajar convaloresbooleanos,losllamadosoperadoreslógicosocondicionales:
Operador Descripción Ejemploand ¿secumpleayb? r=TrueandFalse#resFalseor ¿secumpleaob? r=TrueorFalse#resTruenot Noa r=notTrue#resFalse
Los valores booleanos son además el resultado de expresiones que utilizanoperadoresrelacionales(comparacionesentrevalores):
Operador Descripción Ejemplo== ¿sonigualesayb? r=5==3#resFalse
!= ¿sondistintosayb? r=5!=3#resTrue
< ¿esamenorqueb? r=5<3#resFalse
> ¿esamayorqueb? r=5>3#resTrue
<= ¿esamenoroigualqueb? r=5<=5#resTrue>= ¿esamayoroigualqueb? r=5>=3#resTrue
COLECCIONESEn el capítulo anterior vimos algunos tipos básicos, como los números, lascadenas de texto y los booleanos. En esta lección veremos algunos tipos decoleccionesdedatos:listas,tuplasydiccionarios.
ListasLa lista es un tipo de colección ordenada. Sería equivalente a lo que en otroslenguajesseconoceporarrays,ovectores.Laslistaspuedencontenercualquiertipodedato:números,cadenas,booleanos,…ytambiénlistas.Crear una lista es tan sencillo como indicar entre corchetes, y separados porcomas,losvaloresquequeremosincluirenlalista:
l=[22,True,“unalista”,[1,2]]
Podemosaccederacadaunodeloselementosdelalistaescribiendoelnombredelalistaeindicandoelíndicedelelementoentrecorchetes.Tenencuentasinembargoqueelíndicedelprimerelementodelalistaes0,yno1:l=[11,False]mi_var=l[0]#mi_varvale11
Siqueremosaccederaunelementodeunalistaincluidadentrodeotralista
tendremos que utilizar dos veces este operador, primero para indicar a quéposicióndelalistaexteriorqueremosacceder,yelsegundoparaseleccionarelelementodelalistainterior:
l=[“unalista”,[1,2]]mi_var=l[1][0]#mi_varvale1Tambiénpodemosutilizaresteoperadorparamodificarunelementode la
listasilocolocamosenlaparteizquierdadeunaasignación:l=[22,True]l[0]=99#Conestolvaldrá[99,True]Elusodeloscorchetesparaaccederymodificarloselementosdeunalista
es común enmuchos lenguajes, pero Python nos depara varias sorpresasmuyagradables.
Una curiosidad sobre el operador [] de Python es que podemos utilizartambiénnúmerosnegativos.Siseutilizaunnúmeronegativocomoíndice,estosetraduceenqueelíndiceempiezaacontardesdeelfinal,hacialaizquierda;esdecir, con [-1] accederíamos al último elemento de la lista, con [-2] alpenúltimo,con[-3],alantepenúltimo,yasísucesivamente.
Otra cosa inusual es lo que en Python se conoce como slicing oparticionado,yqueconsisteenampliarestemecanismoparapermitirseleccionarporcionesdelalista.Sienlugardeunnúmeroescribimosdosnúmerosinicioyfin separados por dos puntos (inicio:fin) Python interpretará que queremosuna listaquevayadesde laposicióninicio a la posiciónfin, sin incluir esteúltimo. Si escribimos tres números (inicio:fin:salto) en lugar de dos, elterceroseutilizaparadeterminarcadacuantasposicionesañadirunelementoalalista.
l=[99,True,“unalista”,[1,2]]
mi_var=l[0:2]#mi_varvale[99,True]
mi_var=l[0:4:2]#mi_varvale[99,“unalista”]
Los números negativos también se pueden utilizar en un slicing, con elmismocomportamientoquesecomentóanteriormente.
Hayquemencionarasímismoquenoesnecesarioindicarelprincipioyelfinaldelslicing,sinoque,siestosseomiten,seusaránpordefectolasposicionesdeinicioyfindelalista,respectivamente:
l=[99,True,“unalista”]mi_var=l[1:]#mi_varvale[True,“unalista”]mi_var=l[:2]#mi_varvale[99,True]mi_var=l[:]#mi_varvale[99,True,“unalista”]mi_var=l[::2]#mi_varvale[99,“unalista”]
Tambiénpodemosutilizarestemecanismoparamodificarlalista:l=[99,True,“unalista”,[1,2]]l[0:2]=[0,1]#lvale[0,1,“unalista”,[1,2]]pudiendo incluso modificar el tamaño de la lista si la lista de la parte
derechadelaasignacióntieneuntamañomenoromayorqueeldelaseleccióndelaparteizquierdadelaasignación:l[0:2]=[False]#lvale[False,“unalista”,[1,2]]
En todo caso las listas ofrecen mecanismos más cómodos para sermodificadas a través de las funciones de la clase correspondiente, aunque noveremosestosmecanismoshastamásadelante,despuésdeexplicar loquesonlasclases,losobjetosylasfunciones.
TuplasTodo lo que hemos explicado sobre las listas se aplica también a las tuplas, aexcepcióndelaformadedefinirla,paraloqueseutilizanparéntesisenlugardecorchetes.
t=(1,2,True,“python”)
En realidad el constructor de la tupla es la coma, no el paréntesis, pero elintérpretemuestralosparéntesis,ynosotrosdeberíamosutilizarlos,porclaridad.
>>>t=1,2,3
>>>type(t)
type“tuple”
Ademáshayquetenerencuentaqueesnecesarioañadirunacomaparatuplasdeunsoloelemento,paradiferenciarlodeunelementoentreparéntesis.
>>>t=(1)
>>>type(t)
type“int”
>>>t=(1,)
>>>type(t)
type“tuple”
Parareferirnosaelementosdeunatupla,comoenunalista,seusaeloperador[]:
mi_var=t[0]#mi_vares1
mi_var=t[0:2]#mi_vares(1,2)
Podemosutilizareloperador[]debidoaque las tuplas,al igualque las listas,formanpartedeuntipodeobjetosllamadossecuencias.Permitirmeunpequeño incisopara indicarosque lascadenasde texto tambiénsonsecuencias,porloquenoosextrañaráquepodamoshacercosascomoestas:
c=“holamundo”
c[0]#h
c[5:]#mundo
c[::3]#hauo
Volviendo al tema de las tuplas, su diferencia con las listas estriba en que lastuplas no poseen estosmecanismos demodificación a través de funciones tanútilesdelosquehablábamosalfinaldelaanteriorsección.Además son inmutables, es decir, sus valores no se puedenmodificar una vezcreada;ytienenuntamañofijo.Acambiodeestaslimitacioneslastuplassonmás“ligeras”quelaslistas,porloquesielusoquelevamosadaraunacolecciónesmuybásico,puedesutilizartuplasenlugardelistasyahorrarmemoria.
DiccionariosLosdiccionarios,tambiénllamadosmatricesasociativas,debensunombreaqueson colecciones que relacionan una clave y un valor. Por ejemplo, veamos undiccionariodepelículasydirectores:d={“LoveActually”:“RichardCurtis”,“KillBill”:“Tarantino”,“Amélie”:“Jean-PierreJeunet”}
El primer valor se trata de la clave y el segundo del valor asociado a laclave.Comoclavepodemosutilizarcualquiervalor inmutable:podríamosusarnúmeros,cadenas,booleanos,tuplas,…peronolistasodiccionarios,dadoquesonmutables.Esto es así porque losdiccionarios se implementan como tablashash, y a la hora de introducir un nuevo par clave-valor en el diccionario secalcula el hash de la clave para después poder encontrar la entradacorrespondienterápidamente.Sisemodificaraelobjetoclavedespuésdehabersidointroducidoeneldiccionario,evidentemente,suhashtambiéncambiaríaynopodríaserencontrado.
Ladiferenciaprincipalentrelosdiccionariosylaslistasolastuplasesquea los valores almacenados en un diccionario se les accede no por su índice,porque de hecho no tienen orden, sino por su clave, utilizando de nuevo eloperador[].
d[“LoveActually”]#devuelve“RichardCurtis”
Aligualqueenlistasytuplastambiénsepuedeutilizaresteoperadorparareasignarvalores.
d[“KillBill”]=“QuentinTarantino”
Sin embargo en este caso no se puede utilizar slicing, entre otras cosasporque los diccionarios no son secuencias, si no mappings (mapeados,asociaciones).
CONTROLDEFLUJOEnestalecciónvamosaverloscondicionalesylosbucles.
SentenciascondicionalesSi un programa no fuera más que una lista de órdenes a ejecutar de formasecuencial, una por una, no tendría mucha utilidad. Los condicionales nospermitencomprobarcondicionesyhacerquenuestroprogramasecomportedeuna formau otra, que ejecute un fragmentode códigou otro, dependiendodeestacondición.AquíesdondecobransuimportanciaeltipobooleanoylosoperadoreslógicosyrelacionalesqueaprendimosenelcapítulosobrelostiposbásicosdePython.
ifLa forma más simple de un estamento condicional es un if (del inglés si)seguido de la condición a evaluar, dos puntos (:) y en la siguiente línea eindentado,elcódigoaejecutarencasodequesecumpladichacondición.
fav=“mundogeek.net”
#si(if)favesiguala“mundogeek.net”
iffav==“mundogeek.net”:
print“Tienesbuengusto!”
print“Gracias”
Comoveisesbastantesencillo.Esosi,asegurarosdequeindentáiselcódigotalcualsehahechoenelejemplo,esdecir,asegurarosdepulsarTabulaciónantesde lasdosórdenesprint,dadoqueestaes la formadePythondesaberquevuestra intenciónes ladeque losdosprintseejecutensóloenelcasodequesecumplalacondición,ynoladequese imprima laprimeracadenasisecumple lacondicióny laotrasiempre,cosaqueseexpresaríaasí:iffav==“mundogeek.net”:
print“Tienesbuengusto!”print“Gracias”
En otros lenguajes de programación los bloques de código se determinanencerrándolos entre llaves, y el indentarlos no se tratamás que de una buenaprácticaparaqueseamássencilloseguirelflujodelprogramaconunsologolpedevista.Porejemplo,elcódigoanteriorexpresadoenJavaseríaalgoasí:
Stringfav=“mundogeek.net”;if(fav.equals(“mundogeek.net”)){System.out.println(“Tienes buen gusto!”);
System.out.println(“Gracias”);}Sin embargo, como ya hemos comentado, en Python se trata de una
obligación,ynodeunaelección.Deestaformaseobligaalosprogramadoresaindentarsucódigoparaqueseamássencillodeleer:)if…else
Vamosaverahorauncondicionalalgomáscomplicado.¿Quéharíamossiquisiéramos que se ejecutaran unas ciertas órdenes en el caso de que lacondiciónnosecumpliera?Sindudapodríamosañadirotroifquetuvieracomocondiciónlanegacióndelprimero:
iffav==“mundogeek.net”:print“Tienesbuengusto!”print“Gracias”
iffav!=“mundogeek.net”:print“Vaya,quelástima”peroelcondicionaltieneunasegundaconstrucciónmuchomásútil:iffav==“mundogeek.net”:
print“Tienesbuengusto!”print“Gracias”
else:print“Vaya,quelástima”
Vemosquelasegundacondiciónsepuedesustituirconunelse(delinglés:
sino,encasocontrario).Sileemoselcódigovemosquetienebastantesentido:“sifavesigualamundogeek.net,imprimeestoyesto,sino,imprimeestootro”.
if…elif…elif…elseTodavíaquedaunaconstrucciónmásquever,queeslaquehaceusodelelif.
ifnumero<0:
print“Negativo”
elifnumero>0:
print“Positivo”
else:
print“Cero”
elif es una contracción deelse if, por lo tantoelifnumero>0 puedeleersecomo“sino, sinumeroesmayorque0”.Esdecir,primeroseevalúa lacondicióndelif.Siescierta,seejecutasucódigoysecontinúaejecutandoelcódigoposterioralcondicional;sinosecumple,seevalúalacondicióndelelif.Sisecumplelacondicióndelelifseejecutasucódigoysecontinuaejecutandoel código posterior al condicional; si no se cumple y haymás de un elif secontinúaconelsiguienteenordendeaparición.Sinosecumplelacondicióndelifnideningunodeloselif,seejecutaelcódigodelelse.
AifCelseBTambiénexisteunaconstrucciónsimilaraloperador? deotros lenguajes,
que no es más que una forma compacta de expresar un if else. En estaconstrucciónseevalúaelpredicadoCysedevuelveAsisecumpleoBsinosecumple:AifCelseB.Veamosunejemplo:var=“par”if(num%2==0)else“impar”
Y eso es todo. Si conocéis otros lenguajes de programación puede queesperarais que os hablara ahora del switch, pero en Python no existe estaconstrucción,quepodría emularseconun simplediccionario, asíquepasemosdirectamentealosbucles.
BuclesMientras que los condicionales nos permiten ejecutar distintos fragmentos decódigodependiendodeciertascondiciones,losbuclesnospermitenejecutarunmismofragmentodecódigounciertonúmerodeveces,mientrassecumplaunadeterminadacondición.
whileElbuclewhile (mientras) ejecuta un fragmentode códigomientras se cumplaunacondición.
edad=0
whileedad<18:
edad=edad+1
print“Felicidades,tienes“+str(edad)
Lavariableedadcomienzavaliendo0.Comolacondicióndequeedadesmenorque18escierta(0esmenorque18),seentraenelbucle.Seaumentaedaden1y se imprime el mensaje informando de que el usuario ha cumplido un año.Recordad que el operador + para las cadenas funciona concatenando ambas
cadenas.Esnecesarioutilizar lafunciónstr(destring,cadena)paracrearunacadena a partir del número, dado que no podemos concatenar números ycadenas,peroyacomentaremosestoymuchomásenpróximoscapítulos.Ahorasevuelveaevaluar lacondición,y1siguesiendomenorque18,porloquesevuelveaejecutarelcódigoqueaumentalaedadenunañoeimprimelaedadenlapantalla.Elbuclecontinuaráejecutándosehastaqueedadseaiguala18, momento en el cual la condición dejará de cumplirse y el programacontinuaríaejecutandolasinstruccionessiguientesalbucle.Ahora imaginemos que se nos olvidara escribir la instrucción que aumenta laedad. En ese caso nunca se llegaría a la condición de que edad fuese igual omayor que 18, siempre sería 0, y el bucle continuaría indefinidamenteescribiendoenpantallaHascumplido0.Estoesloqueseconocecomounbucleinfinito.Sin embargohay situaciones en las queunbucle infinito es útil. Por ejemplo,veamosunpequeñoprogramaque repite todo loqueelusuariodigahastaqueescribaadios.
whileTrue:
entrada=raw_input(“>“)
ifentrada==“adios”:
break
else:
printentrada
Para obtener lo que el usuario escriba en pantalla utilizamos la funciónraw_input. No es necesario que sepáis qué es una función ni cómo funcionaexactamente,simplementeaceptadporahoraqueencadaiteracióndelbuclelavariableentradacontendráloqueelusuarioescribióhastapulsarEnter.Comprobamosentoncessiloqueescribióelusuariofueadios,encuyocasoseejecutalaordenbreakosieracualquierotracosa,encuyocasoseimprimeenpantallaloqueelusuarioescribió.Lapalabraclavebreak(romper)saledelbucleenelqueestamos.Estebuclesepodríahaberescritotambién,noobstante,delasiguienteforma:
salir=False
whilenotsalir:
entrada=raw_input()
ifentrada==“adios”:
salir=True
else:
printentrada
peronoshaservidoparavercómofuncionabreak.
Otrapalabraclavequenospodemosencontrardentrodelosbuclesescontinue(continuar).Comohabréisadivinadonohaceotracosaquepasardirectamentealasiguienteiteracióndelbucle.
edad=0
whileedad<18:
edad=edad+1
ifedad%2==0:
continue
print“Felicidades,tienes“+str(edad)
Como veis esta es una pequeña modificación de nuestro programa defelicitaciones.Enestaocasiónhemosañadidounifquecompruebasilaedadespar, en cuyo caso saltamos a la próxima iteración en lugar de imprimir elmensaje. Es decir, con esta modificación el programa sólo imprimiríafelicitacionescuandolaedadfueraimpar.
for…inAlosquehayáistenidoexperienciapreviaconsegúnquelenguajesestebucleosvaasorprendergratamente.EnPythonforseutilizacomounaformagenéricadeiterarsobreunasecuencia.Ycomotalintentafacilitarsuusoparaestefin.EsteeselaspectodeunbucleforenPython:
secuencia=[“uno”,“dos”,“tres”]
forelementoinsecuencia:
printelemento
ComohemosdicholosforseutilizanenPythonpararecorrersecuencias,porloquevamosautilizaruntiposecuencia,comoeslalista,paranuestroejemplo.Leamoslacabeceradelbuclecomosidelenguajenaturalsetratara:“paracadaelementoensecuencia”.Yestoesexactamenteloquehaceelbucle:paracadaelementoquetengamosenlasecuencia,ejecutaestaslíneasdecódigo.Lo que hace la cabecera del bucle es obtener el siguiente elemento de lasecuenciasecuencia y almacenarlo en una variable de nombreelemento. Poresta razón en la primera iteración del bucle elemento valdrá “uno”, en lasegunda“dos”,yenlatercera“tres”.Fácilysencillo.En C o C++, por ejemplo, lo que habríamos hecho sería iterar sobre lasposiciones,ynosobreloselementos:
intmi_array[]={1,2,3,4,5};
inti;
for(i=0;i<5;i++){
printf(“%d\n”,mi_array[i]);
}
Esdecir,tendríamosunbucleforquefueraaumentandounavariableiencadaiteración,desde0altamañodelasecuencia,yutilizaríamosestavariableamododeíndiceparaobtenercadaelementoeimprimirlo.ComoveiselenfoquedePythonesmásnaturaleintuitivo.Pero,¿quéocurresiquisiéramosutilizarelforcomosiestuviéramosenCoenJava, por ejemplo, para imprimir los números de 30 a 50?No os preocupéis,porquenonecesitaríaiscrearunalistayañadirunoaunolosnúmerosdel30al50.Pythonproporcionaunafunciónllamadarange(rango)quepermitegeneraruna lista que vaya desde el primer número que le indiquemos al segundo. Loveremos después de ver al fin a qué se refiere ese término tan recurrente: lasfunciones.
FUNCIONESUnafunciónesunfragmentodecódigoconunnombreasociadoquerealizaunaseriede tareasydevuelveunvalor.A los fragmentosdecódigoque tienenunnombreasociadoynodevuelvenvaloresselessuelellamarprocedimientos.EnPython no existen los procedimientos, ya que cuando el programador noespecifica un valor de retorno la función devuelve el valor None (nada),equivalentealnulldeJava.Ademásdeayudarnosaprogramarydepurardividiendoelprogramaenparteslasfuncionestambiénpermitenreutilizarcódigo.EnPythonlasfuncionessedeclarandelasiguienteforma:
defmi_funcion(param1,param2):
printparam1
printparam2
Esdecir,lapalabraclavedefseguidadelnombredelafunciónyentreparéntesislosargumentosseparadosporcomas.Acontinuación,enotralínea,indentadoydespués de los dos puntos tendríamos las líneas de código que conforman el
códigoaejecutarporlafunción.Tambiénpodemosencontrarnosconunacadenadetextocomoprimeralíneadelcuerpo de la función. Estas cadenas se conocen con el nombre de docstring(cadena de documentación) y sirven, como su nombre indica, a modo dedocumentacióndelafunción.
defmi_funcion(param1,param2):
“““Estafuncionimprimelosdosvalorespasados
comoparametros”””
printparam1
printparam2
Estoesloqueimprimeeloperador?deiPythonolafunciónhelpdellenguajeparaproporcionarunaayudasobreelusoyutilidaddelasfunciones.Todoslosobjetos pueden tener docstrings, no solo las funciones, como veremos másadelante.Volviendoaladeclaracióndefunciones,esimportanteaclararquealdeclararlafunciónloúnicoquehacemosesasociarunnombrealfragmentodecódigoqueconforma la función, de forma que podamos ejecutar dicho códigomás tardereferenciándoloporsunombre.Esdecir,alahoradeescribirestaslíneasnoseejecutalafunción.Parallamaralafunción(ejecutarsucódigo)seescribiría:
mi_funcion(“hola”,2)
Es decir, el nombre de la función a la que queremos llamar seguido de losvaloresquequeramospasarcomoparámetrosentreparéntesis.Laasociacióndelos parámetros y los valores pasados a la función se hace normalmente deizquierda aderecha: comoaparam1 lehemosdadounvalor“hola” yparam2vale2,mi_funcionimprimiríaholaenunalínea,yacontinuación2.Sin embargo también es posible modificar el orden de los parámetros siindicamoselnombredelparámetroalqueasociarelvaloralahoradellamaralafunción:
mi_funcion(param2=2,param1=“hola”)
Elnúmerodevaloresquesepasancomoparámetroalllamaralafuncióntieneque coincidir con el número de parámetros que la función acepta según ladeclaracióndelafunción.EncasocontrarioPythonsequejará:
>>>mi_funcion(“hola”)
Traceback(mostrecentcalllast):
File“<stdin>”,line1,in<module>
TypeError:mi_funcion()takesexactly2arguments(1given)
También es posible, no obstante, definir funciones con un número variable deargumentos,obienasignarvalorespordefectoalosparámetrosparaelcasode
quenoseindiqueningúnvalorparaeseparámetroalllamaralafunción.Losvalorespordefectoparalosparámetrossedefinensituandounsignoigualdespuésdelnombredelparámetroyacontinuaciónelvalorpordefecto:
defimprimir(texto,veces=1):
printveces*texto
Enel ejemplo anterior si no indicamosunvalor para el segundoparámetro seimprimiráunasolavezlacadenaquelepasamoscomoprimerparámetro:
>>>imprimir(“hola”)
hola
siseleindicaotrovalor,seráesteelqueseutilice:>>>imprimir(“hola”,2)
holahola
Para definir funciones con un número variable de argumentos colocamos unúltimoparámetroparalafuncióncuyonombredebeprecedersedeunsigno*:
defvarios(param1,param2,*otros):
forvalinotros:
printval
varios(1,2)
varios(1,2,3)
varios(1,2,3,4)
Estasintaxisfuncionacreandounatupla(denombreotrosenelejemplo)enlaque se almacenan los valores de todos los parámetros extra pasados comoargumento.Paralaprimerallamada,varios(1,2),latuplaotrosestaríavacíadadoqueno sehanpasadomásparámetrosque losdosdefinidospordefecto,porlotantonoseimprimiríanada.Enlasegundallamadaotrosvaldría(3,),yenlatercera(3,4).Tambiénsepuedeprecederelnombredelúltimoparámetrocon**,encuyocasoenlugardeunatuplaseutilizaríaundiccionario.Lasclavesdeestediccionarioserían los nombres de los parámetros indicados al llamar a la función y losvaloresdeldiccionario,losvaloresasociadosaestosparámetros.En el siguiente ejemplo se utiliza la función items de los diccionarios, quedevuelveunalistaconsuselementos,paraimprimirlosparámetrosquecontieneeldiccionario.
defvarios(param1,param2,**otros):
foriinotros.items():
printi
varios(1,2,tercero=3)
Losqueconozcáisalgúnotrolenguajedeprogramaciónosestaréispreguntando
sienPythonalpasarunavariablecomoargumentodeunafunciónestassepasanpor referencia o por valor. En el paso por referencia lo que se pasa comoargumento es una referencia o puntero a la variable, es decir, la dirección dememoriaenlaqueseencuentraelcontenidodelavariable,ynoelcontenidoensi.Enelpasoporvalor,porelcontrario, loquesepasacomoargumentoeselvalorqueconteníalavariable.Ladiferenciaentreambosestribaenqueenelpasoporvalorloscambiosquesehagansobreelparámetronosevenfueradelafunción,dadoquelosargumentosde la función son variables locales a la función que contienen los valoresindicadosporlasvariablesquesepasaroncomoargumento.Esdecir,enrealidadloqueselepasaalafunciónsoncopiasdelosvaloresynolasvariablesensi.Siquisiéramosmodificarelvalordeunodelosargumentosyqueestoscambiosse reflejaran fuera de la función tendríamos que pasar el parámetro porreferencia.En C los argumentos de las funciones se pasan por valor, aunque se puedesimularelpasoporreferenciausandopunteros.EnJavatambiénseusapasoporvalor,aunqueparalasvariablesquesonobjetosloquesehaceespasarporvalorlareferenciaalobjeto,porloqueenrealidadparecepasoporreferencia.EnPythontambiénseutilizaelpasoporvalordereferenciasaobjetos,comoenJava,aunqueenelcasodePython,adiferenciadeJava,todoesunobjeto(paraserexactosloqueocurreenrealidadesquealobjetoseleasignaotraetiquetaonombreenelespaciodenombreslocaldelafunción).SinembargonotodosloscambiosquehagamosalosparámetrosdentrodeunafunciónPythonsereflejaránfueradeesta,yaquehayquetenerencuentaqueenPythonexistenobjetos inmutables,como las tuplas,por loquesi intentáramosmodificarunatuplapasadacomoparámetroloqueocurriríaenrealidadesquesecrearíaunanueva instancia,por loque loscambiosnoseveríanfuerade lafunción.Veamosunpequeñoprogramaparademostrarlo.Enesteejemplosehaceusodelmétodo append de las listas. Un método no es más que una función queperteneceaunobjeto,enestecasoaunalista;yappend,enconcreto,sirveparaañadirunelementoaunalista.
deff(x,y):
x=x+3
y.append(23)
printx,y
x=22
y=[22]
f(x,y)
printx,y
Elresultadodelaejecucióndeesteprogramasería25[22,23]
22[22,23]
Como vemos la variable x no conserva los cambios una vez salimos de lafunciónporquelosenterossoninmutablesenPython.Sinembargolavariableysilosconserva,porquelaslistassonmutables.Enresumen:losvaloresmutablessecomportancomopasoporreferencia,ylosinmutablescomopasoporvalor.Conesto terminamos todo lo relacionadocon losparámetrosde las funciones.Veamosporúltimocómodevolvervalores,paraloqueseutilizalapalabraclavereturn:
defsumar(x,y):
returnx+y
printsumar(3,2)
Comovemosestafuncióntansencillanohaceotracosaquesumarlosvalorespasadoscomoparámetroydevolverelresultadocomovalorderetorno.Tambiénpodríamospasarvariosvaloresqueretornarareturn.
deff(x,y):
returnx*2,y*2
a,b=f(1,2)
Sin embargo esto no quiere decir que las funciones Python puedan devolvervariosvalores, loqueocurreenrealidadesquePythoncreaunatuplaalvuelocuyos elementos son los valores a retornar, y esta única variable es la que sedevuelve.
ORIENTACIÓNAOBJETOSEn el capítulo de introducción ya comentábamos que Python es un lenguajemultiparadigmaenelquesepodíatrabajarconprogramaciónestructurada,comoveníamos haciendo hasta ahora, o con programación orientada a objetos oprogramaciónfuncional.LaProgramaciónOrientadaaObjetos(POOuOOPsegúnsussiglaseninglés)es un paradigma de programación en el que los conceptos del mundo realrelevantesparanuestroproblemasemodelanatravésdeclasesyobjetos,yenelquenuestroprogramaconsisteenunaseriedeinteraccionesentreestosobjetos.
ClasesyobjetosParaentenderesteparadigmaprimerotenemosquecomprenderquéesunaclasey qué es un objeto. Un objeto es una entidad que agrupa un estado y unafuncionalidadrelacionadas.Elestadodelobjetosedefinea travésdevariablesllamadas atributos, mientras que la funcionalidad se modela a través defuncionesalasqueselesconoceconelnombredemétodosdelobjeto.Unejemplodeobjetopodríaseruncoche,enelquetendríamosatributoscomolamarca,elnúmerodepuertasoeltipodecarburanteymétodoscomoarrancaryparar.Obiencualquierotracombinacióndeatributosymétodossegúnloquefuerarelevanteparanuestroprograma.Unaclase,porotrolado,noesmásqueunaplantillagenéricaapartirdelacualinstanciar los objetos; plantilla que es la que define qué atributos y métodostendránlosobjetosdeesaclase.Volviendoanuestroejemplo:enelmundorealexisteunconjuntodeobjetosalos que llamamos coches y que tienenun conjunto de atributos comunes y un
comportamientocomún,estoesaloquellamamosclase.Sinembargo,micochenoesigualqueelcochedemivecino,yaunquepertenecenalamismaclasedeobjetos,sonobjetosdistintos.En Python las clases se definen mediante la palabra clave class seguida delnombredelaclase,dospuntos(:)yacontinuación,indentado,elcuerpodelaclase.Comoenelcasodelasfunciones,silaprimeralíneadelcuerposetratadeuna cadena de texto, esta será la cadena de documentación de la clase odocstring.
classCoche:
“““Abstraccióndelosobjetoscoche.”””
def__init__(self,gasolina):
self.gasolina=gasolina
print“Tenemos”,gasolina,“litros”
defarrancar(self):
ifself.gasolina>0:
print“Arranca”
else:
print“Noarranca”
defconducir(self):
ifself.gasolina>0:
self.gasolina-=1
print“Quedan”,self.gasolina,“litros”
else:
print“Nosemueve”
Loprimeroquellamalaatenciónenelejemploanterioreselnombretancuriosoquetieneelmétodo__init__.Estenombreesunaconvenciónynouncapricho.Elmétodo__init__,conunadoblebarrabajaalprincipioyfinaldelnombre,seejecutajustodespuésdecrearunnuevoobjetoapartirdelaclase,procesoqueseconoceconelnombredeinstanciación.Elmétodo__init__sirve,comosugieresunombre,pararealizarcualquierprocesodeinicializaciónqueseanecesario.Comovemoselprimerparámetrode__init__ydelrestodemétodosdelaclaseessiempreself.EstaesunaideainspiradaenModula-3ysirveparareferirsealobjetoactual.Estemecanismoesnecesarioparapoderaccederalosatributosymétodosdelobjetodiferenciando,porejemplo,unavariablelocalmi_vardeunatributodelobjetoself.mi_var.Si volvemos al método __init__ de nuestra clase Coche veremos cómo seutilizaselfparaasignaralatributogasolinadelobjeto(self.gasolina)elvalorque el programador especificó para el parámetro gasolina. El parámetrogasolinasedestruyealfinaldelafunción,mientrasqueelatributogasolinaseconserva(ypuedeseraccedido)mientraselobjetoviva.Para crear un objeto se escribiría el nombre de la clase seguido de cualquier
parámetro que sea necesario entre paréntesis. Estos parámetros son los que sepasaránalmétodo__init__,quecomodecíamoseselmétodoquesellamaalinstanciarlaclase.
mi_coche=Coche(3)
Ospreguntareisentoncescómoesposiblequealahoradecrearnuestroprimerobjetopasemosunsoloparámetroa__init__,elnúmero3,cuandoladefiniciónde la función indica claramente que precisa de dos parámetros (self ygasolina).EstoesasíporquePythonpasaelprimerargumento(lareferenciaalobjetoquesecrea)automáticamente.Ahoraqueyahemoscreadonuestroobjeto,podemosaccederasusatributosymétodosmediantelasintaxisobjeto.atributoyobjeto.metodo():
>>>printmi_coche.gasolina
3
>>>mi_coche.arrancar()
Arranca
>>>mi_coche.conducir()
Quedan2litros
>>>mi_coche.conducir()
Quedan1litros
>>>mi_coche.conducir()
Quedan0litros
>>>mi_coche.conducir()
Nosemueve
>>>mi_coche.arrancar()
Noarranca
>>>printmi_coche.gasolina
0
ComoúltimoapunterecordarqueenPython,comoyasecomentóenrepetidasocasiones anteriormente, todo son objetos. Las cadenas, por ejemplo, tienenmétodoscomoupper(),quedevuelveeltextoenmayúsculasocount(sub),quedevuelveelnúmerodevecesqueseencontrólacadenasubeneltexto.
HerenciaHay tres conceptos que son básicos para cualquier lenguaje de programaciónorientadoaobjetos:elencapsulamiento,laherenciayelpolimorfismo.En un lenguaje orientado a objetos cuando hacemos que una clase (subclase)herede de otra clase (superclase) estamos haciendo que la subclase contengatodos los atributos ymétodos que tenía la superclase. No obstante al acto deheredardeunaclasetambiénselellamaamenudo“extenderunaclase”.Supongamosque queremosmodelar los instrumentosmusicales de unabanda,tendremosentoncesunaclaseGuitarra,unaclaseBateria,unaclaseBajo,etc.Cada una de estas clases tendrá una serie de atributos ymétodos, pero ocurreque,porelmerohechodeserinstrumentosmusicales,estasclasescompartiránmuchosdesusatributosymétodos;unejemploseríaelmétodotocar().EsmássencillocrearuntipodeobjetoInstrumentoconlosatributosymétodoscomunes e indicar al programa que Guitarra, Bateria y Bajo son tipos deinstrumentos,haciendoqueheredendeInstrumento.
Paraindicarqueunaclaseheredadeotrasecolocaelnombredelaclasedelaqueseheredaentreparéntesisdespuésdelnombredelaclase:
classInstrumento:
def__init__(self,precio):
self.precio=preciodeftocar(self):print“Estamostocandomusica”
defromper(self):
print“Esolopagastu”
print“Son”,self.precio,“$$$”
classBateria(Instrumento):pass
classGuitarra(Instrumento):pass
Como Bateria y Guitarra heredan de Instrumento, ambos tienen unmétodotocar()yunmétodoromper(),yse inicializanpasandounparámetroprecio. Pero, ¿qué ocurriría si quisiéramos especificar un nuevo parámetrotipo_cuerda a la hora de crear un objeto Guitarra?Bastaría con escribir unnuevométodo__init__ para la claseGuitarra que se ejecutaría en lugar del__init__ de Instrumento. Esto es lo que se conoce como sobreescribirmétodos.
Ahorabien,puedeocurrirenalgunoscasosquenecesitemossobreescribirun método de la clase padre, pero que en ese método queramos ejecutar elmétodo de la clase padre porque nuestro nuevo método no necesite más queejecutarunpardenuevasinstruccionesextra.EnesecasousaríamoslasintaxisSuperClase.metodo(self,args)parallamaralmétododeigualnombredelaclasepadre.Porejemplo,parallamaralmétodo__init__deInstrumentodesdeGuitarrausaríamosInstrumento.__init__(self,precio)
Observadqueenestecasosiesnecesarioespecificarelparámetroself.
HerenciamúltipleEn Python, a diferencia de otros lenguajes como Java o C#, se permite laherenciamúltiple,esdecir,unaclasepuedeheredardevariasclasesalavez.Porejemplo, podríamos tener una clase Cocodrilo que heredara de la claseTerrestre,conmétodoscomocaminar()yatributoscomovelocidad_caminary de la clase Acuatico, con métodos como nadar() y atributos comovelocidad_nadar. Basta con enumerar las clases de las que se heredaseparándolasporcomas:classCocodrilo(Terrestre,Acuatico):pass
Enelcasodequealgunadelasclasespadretuvieranmétodosconelmismonombreynúmerodeparámetroslasclasessobreescribiríanlaimplementacióndelosmétodosdelasclasesmásasuderechaenladefinición.
Enelsiguienteejemplo,comoTerrestreseencuentramásalaizquierda,seríaladefinicióndedesplazardeestaclaselaqueprevalecería,yporlotantosillamamos al método desplazar de un objeto de tipo Cocodrilo lo que seimprimiríasería“Elanimalanda”.
classTerrestre:
defdesplazar(self):print“Elanimalanda”
classAcuatico:defdesplazar(self):print“Elanimalnada”
classCocodrilo(Terrestre,Acuatico):passc=Cocodrilo()c.desplazar()
PolimorfismoLapalabrapolimorfismo,delgriegopolymorphos(variasformas),serefierealahabilidaddeobjetosdedistintasclasesderesponderalmismomensaje.Estosepuedeconseguir a travésde laherencia:unobjetodeunaclasederivadaes almismotiempounobjetodelaclasepadre,deformaqueallídondeserequiereunobjetodelaclasepadretambiénsepuedeutilizarunodelaclasehija.Python,alserdetipadodinámico,noimponerestriccionesalostiposqueselepuedenpasaraunafunción,porejemplo,másalládequeelobjetosecomportecomo se espera: si se va a llamar a un método f() del objeto pasado comoparámetro, por ejemplo, evidentemente el objeto tendrá que contar con esemétodo.Poresemotivo,adiferenciadelenguajesdetipadoestáticocomoJavaoC++,elpolimorfismoenPythonnoesdegranimportancia.En ocasiones también se utiliza el término polimorfismo para referirse a lasobrecargademétodos,términoquesedefinecomolacapacidaddellenguajededeterminarquémétodoejecutardeentrevariosmétodosconigualnombresegúnel tipo o número de los parámetros que se le pasa. En Python no existe
sobrecargademétodos (elúltimométodosobreescribiría la implementacióndelos anteriores), aunque se puede conseguir un comportamiento similarrecurriendo a funciones con valores por defecto para los parámetros o a lasintaxis *params o **params explicada en el capítulo sobre las funciones enPython,obienusandodecoradores(mecanismoqueveremosmásadelante).
EncapsulaciónLa encapsulación se refiere a impedir el acceso a determinados métodos yatributosdelosobjetosestableciendoasíquépuedeutilizarsedesdefueradelaclase.Esto se consigue en otros lenguajes de programación como Java utilizandomodificadoresdeaccesoquedefinensicualquierapuedeaccederaesafunciónovariable(public)osiestárestringidoelaccesoalapropiaclase(private).EnPythonnoexistenlosmodificadoresdeacceso,yloquesesuelehaceresqueel acceso a una variable o función viene determinado por su nombre: si elnombrecomienzacondosguionesbajos(ynoterminatambiéncondosguionesbajos) se tratadeunavariableo funciónprivada,encasocontrarioespública.Los métodos cuyo nombre comienza y termina con dos guiones bajos sonmétodos especiales que Python llama automáticamente bajo ciertascircunstancias,comoveremosalfinaldelcapítulo.Enelsiguienteejemplosóloseimprimirálacadenacorrespondientealmétodo
publico(), mientras que al intentar llamar al método __privado() Pythonlanzaráunaexcepciónquejándosedequenoexiste(evidentementeexiste,peronolopodemosverporqueesprivado).
classEjemplo:
defpublico(self):
print“Publico”
def__privado(self):print“Privado”
ej=Ejemplo()ej.publico()
ej.__privado()
Estemecanismosebasaenquelosnombresquecomienzanconundobleguiónbajo se renombran para incluir el nombre de la clase (característica que seconoceconelnombredenamemangling).Estoimplicaqueelmétodooatributonoesrealmenteprivado,ypodemosaccederaélmedianteunapequeñatrampa:ej._Ejemplo__privado() En ocasiones también puede suceder que queramospermitirelaccesoaalgúnatributodenuestroobjeto,peroqueesteseproduzcadeformacontrolada.Paraestopodemosescribirmétodoscuyoúnicocometidosea este, métodos que normalmente, por convención, tienen nombres comogetVariableysetVariable;deahíqueseconozcantambiénconelnombredegettersysetters.
classFecha():
def__init__(self):self.__dia=1
defgetDia(self):returnself.__diadefsetDia(self,dia):
ifdia>0anddia<31:self.__dia=diaelse:
print“Error”
mi_fecha=Fecha()mi_fecha.setDia(33)Estosepodríasimplificarmediantepropiedades,queabstraenalusuariodel
hecho de que se está utilizando métodos entre bambalinas para obtener ymodificarlosvaloresdelatributo:
classFecha(object):
def__init__(self):self.__dia=1
defgetDia(self):returnself.__diadefsetDia(self,dia):
ifdia>0anddia<31:self.__dia=diaelse:
print“Error”
dia=property(getDia,setDia)
mi_fecha=Fecha()mi_fecha.dia=33
Clasesde“nuevo-estilo”En el ejemplo anterior os habrá llamado la atención el hecho de que la claseFechaderivedeobject.Larazóndeestoesqueparapoderusarpropiedadeslaclasetienequeserde“nuevo-estilo”,clasesenriquecidasintroducidasenPython2.2 que serán el estándar enPython3.0 pero que aún conviven con las clases“clásicas” por razones de retrocompatibilidad. Además de las propiedades lasclasesdenuevoestiloañadenotrasfuncionalidadescomodescriptoresométodosestáticos.Paraqueunaclaseseadenuevoestiloesnecesario,porahora,queextiendaunaclase de nuevo-estilo. En el caso de que no sea necesario heredar elcomportamientooelestadodeningunaclase,comoennuestroejemploanterior,sepuedeheredardeobject, que esunobjetovacíoque sirve comobaseparatodaslasclasesdenuevoestilo.Ladiferenciaprincipalentrelasclasesantiguasylasdenuevoestiloconsisteenquealahoradecrearunanuevaclaseanteriormentenosedefiníarealmenteunnuevotipo,sinoquetodoslosobjetoscreadosapartirdeclases,fueranestaslas
clasesquefueran,erandetipoinstance.
MétodosespecialesYa vimos al principio del artículo el uso delmétodo __init__. Existen otrosmétodos con significados especiales, cuyos nombres siempre comienzan yterminancondosguionesbajos.Acontinuaciónselistanalgunosespecialmenteútiles.
__init__(self,args)Métodollamadodespuésdecrearelobjetopara
realizartareasdeinicialización.
__new__(cls,args)
Método exclusivo de las clases de nuevo estilo que se ejecuta antes que__init__yqueseencargadeconstruirydevolverelobjetoensí.EsequivalentealosconstructoresdeC++oJava.Setratadeunmétodoestático,esdecir,queexisteconindependenciadelasinstanciasdelaclase:esunmétododeclase,nodeobjeto,ypor lo tanto elprimerparámetronoesself, sino lapropiaclase:cls.
__del__(self)
Métodollamadocuandoelobjetovaaserborrado.Tambiénllamadodestructor,seutilizapararealizartareasdelimpieza.
__str__(self)
Métodollamadoparacrearunacadenadetextoquerepresenteanuestroobjeto.Seutilizacuandousamosprintparamostrarnuestroobjetoocuandousamoslafunciónstr(obj)paracrearunacadenaapartirdenuestroobjeto.
__cmp__(self, otro) Método llamado cuando se utilizan los
operadores de comparación para comprobar si nuestro objeto es
menor,mayoroigualalobjetopasadocomoparámetro.Debedevolver
unnúmeronegativosinuestroobjetoesmenor,cerosisoniguales,
yunnúmeropositivosinuestroobjetoesmayor.Siestemétodono
está definido y se intenta comparar el objeto mediante los
operadores<,<=,>o>=selanzaráunaexcepción.Siseutilizan
losoperadores==o!=paracomprobarsidosobjetossoniguales,
secompruebasisonelmismoobjeto(sitienenelmismoid).
__len__(self)
Métodollamadoparacomprobarlalongituddelobjeto.Seutiliza,porejemplo,cuando se llama a la función len(obj) sobre nuestro objeto. Como es desuponer,elmétododebedevolverlalongituddelobjeto.
Existenbastantesmásmétodosespeciales,quepermiteentreotrascosasutilizarelmecanismodeslicingsobrenuestroobjeto,utilizarlosoperadoresaritméticoso usar la sintaxis de diccionarios, pero un estudio exhaustivo de todos losmétodosquedafueradelpropósitodelcapítulo.
REVISITANDOOBJETOSEn los capítulos dedicados a los tipos simples y las colecciones veíamos porprimera vez algunos de los objetos del lenguaje Python: números, booleanos,cadenasdetexto,diccionarios,listasytuplas.Ahoraquesabemosquésonlasclases,losobjetos,lasfunciones,ylosmétodoseselmomentoderevisitarestosobjetosparadescubrirsuverdaderopotencial.Veremosacontinuaciónalgunosmétodosútilesdeestosobjetos.Evidentemente,no es necesario memorizarlos, pero sí, al menos, recordar que existen paracuandoseannecesarios.
DiccionariosD.get(k[,d])
Buscaelvalordelaclavekeneldiccionario.EsequivalenteautilizarD[k]peroalutilizarestemétodopodemosindicarunvaloradevolverpordefectosinoseencuentra laclave,mientrasquecon la sintaxisD[k], de no existir la clave selanzaríaunaexcepción.
D.has_key(k) Comprueba si el diccionario tiene la clave k. Es
equivalentealasintaxiskinD.
D.items()Devuelveunalistadetuplasconparesclave-valor.
D.keys()
Devuelveunalistadelasclavesdeldiccionario.
D.pop(k[,d])Borralaclavekdeldiccionarioydevuelvesuvalor.
Si no se encuentra dicha clave se devuelve d si se especificó el
parámetroobienselanzaunaexcepción.
D.values()Devuelveunalistadelosvaloresdeldiccionario.
CadenasS.count(sub[,start[,end]])
Devuelveelnúmerodevecesqueseencuentrasubenlacadena.Losparámetrosopcionalesstartyenddefinenunasubcadenaenlaquebuscar.
S.find(sub[, start[, end]]) Devuelve la posición en la que se
encontróporprimeravezsubenlacadenao-1sinoseencontró.
S.join(sequence)
Devuelve una cadena resultante de concatenar las cadenas de la secuenciasequenceseparadasporlacadenasobrelaquesellamaelmétodo.
S.partition(sep)
Buscaelseparadorsepenlacadenaydevuelveunatuplaconlasubcadenahastadichoseparador,elseparadorensi,ylasubcadenadelseparadorhastaelfinaldelacadena.Sinoseencuentraelseparador, la tuplacontendrálacadenaensiydoscadenasvacías.
S.replace(old,new[,count])Devuelveunacadenaenlaquesehan
reemplazado todas las ocurrencias de la cadena old por la cadena
new. Si se especifica el parámetro count, este indica el número
máximodeocurrenciasareemplazar.
S.split([sep [,maxsplit]]) Devuelve una lista conteniendo las
subcadenasenlasquesedividenuestracadenaaldividirlasporel
delimitadorsep.Enelcasodequenoseespecifiquesep,seusan
espacios.Siseespecificamaxsplit, este indica el número máximo
departicionesarealizar.
ListasL.append(object)
Añadeunobjetoalfinaldelalista.
L.count(value)
Devuelveelnúmerodevecesqueseencontróvalueenlalista.
L.extend(iterable)Añadeloselementosdeliterablealalista.
L.index(value[,start[, stop]]) Devuelve la posición en la que se
encontrólaprimeraocurrenciadevalue.Siseespecifican,starty
stopdefinenlasposicionesdeinicioyfindeunasublistaenla
quebuscar.
L.insert(index, object) Inserta el objeto object en la posición
index.
L.pop([index])
Devuelve el valor en la posición index y lo elimina de la lista. Si no se
especificalaposición,seutilizaelúltimoelementodelalista.
L.remove(value) Eliminar la primera ocurrencia de value en la
lista.
L.reverse()
Inviertelalista.Estafuncióntrabajasobrelapropialistadesdelaqueseinvocaelmétodo,nosobreunacopia.
L.sort(cmp=None, key=None, reverse=False) Ordena la lista. Si se
especificacmp,estedebeserunafunciónquetomecomoparámetro
dosvaloresxeydelalistaydevuelva-1sixesmenorquey,0
sisonigualesy1sixesmayorquey.
Elparámetroreverse esunbooleanoque indica si sedebeordenar la listadeformainversa,loqueseríaequivalenteallamarprimeroaL.sort()ydespuésaL.reverse().Porúltimo,siseespecifica,elparámetrokeydebeserunafunciónquetomeunelemento de la lista y devuelva una clave a utilizar a la hora de comparar, enlugardelelementoensi.
PROGRAMACIÓNFUNCIONALLaprogramaciónfuncionalesunparadigmaenelquelaprogramaciónsebasacasiensutotalidadenfunciones,entendiendoelconceptodefunciónsegúnsudefiniciónmatemática, y no como los simples subprogramas de los lenguajesimperativosquehemosvistohastaahora.En los lenguajes funcionalespurosunprogramaconsiste exclusivamenteen laaplicacióndedistintasfuncionesaunvalordeentradaparaobtenerunvalordesalida.Python, sin ser un lenguaje puramente funcional incluye varias característicastomadasdeloslenguajesfuncionalescomosonlasfuncionesdeordensuperiorolasfuncioneslambda(funcionesanónimas).
FuncionesdeordensuperiorElconceptodefuncionesdeordensuperiorserefierealusodefuncionescomosi de un valor cualquiera se tratara, posibilitando el pasar funciones comoparámetrosdeotrasfuncionesodevolverfuncionescomovalorderetorno.Estoesposibleporque,comohemosinsistidoyaenvariasocasiones,enPythontodosonobjetos.Ylasfuncionesnosonunaexcepción.Veamosunpequeñoejemplo
defsaludar(lang):
defsaludar_es():
print“Hola”
defsaludar_en():
print“Hi”
defsaludar_fr():
print“Salut”
lang_func={“es”:saludar_es,
“en”:saludar_en,
“fr”:saludar_fr}
returnlang_func[lang]
f=saludar(“es”)
f()
Comopodemosobservarloprimeroquehacemosennuestropequeñoprogramaesllamaralafunciónsaludarconunparámetro“es”.Enlafunciónsaludarsedefinen varias funciones: saludar_es, saludar_en y saludar_fr y acontinuaciónsecreaundiccionarioquetienecomoclavescadenasdetextoqueidentificanacadalenguaje,ycomovaloreslasfunciones.Elvalorderetornodela funciónesunadeestas funciones.La funciónadevolvervienedeterminadaporelvalordelparámetrolangquesepasócomoargumentodesaludar.Comoel valor de retornodesaludar es una función, comohemosvisto, estoquieredecirquefesunavariablequecontieneunafunción.Podemosentoncesllamar a la función a la que se refiere f de la forma en que llamaríamos acualquierotrafunción,añadiendounosparéntesisy,deformaopcional,unaseriedeparámetrosentrelosparéntesis.Esto se podría acortar, ya que no es necesario almacenar la función que nospasancomovalorderetornoenunavariableparapoderllamarla:
>>>saludar(“en”)()
Hi
>>>saludar(“fr”)()
Salut
En este caso el primer par de paréntesis indica los parámetros de la funciónsaludar,yelsegundopar,losdelafuncióndevueltaporsaludar.
IteracionesdeordensuperiorsobrelistasUnadelascosasmásinteresantesquepodemoshacerconnuestrasfuncionesdeorden superior es pasarlas como argumentos de las funciones map, filter yreduce.Estasfuncionesnospermitensustituirlosbuclestípicosdeloslenguajesimperativosmedianteconstruccionesequivalentes.
map(function,sequence[,sequence,...])Lafunciónmapaplicaunafunciónacadaelementodeunasecuenciaydevuelveunalistaconelresultadodeaplicarlafunciónacadaelemento.Sisepasancomoparámetrosnsecuencias,lafuncióntendráqueaceptarnargumentos.Sialgunadelassecuenciasesmáspequeñaquelasdemás,elvalorquelellegaalafunciónfunctionparaposicionesmayoresqueeltamañodedichasecuenciaseráNone.
Acontinuaciónpodemosverunejemploenelqueseutilizamapparaelevaralcuadradotodosloselementosdeunalista:
defcuadrado(n):returnn**2
l=[1,2,3]l2=map(cuadrado,l)
filter(function,sequence)Lafunciónfilterverificaqueloselementosdeunasecuenciacumplanunadeterminadacondición,devolviendounasecuenciaconloselementosquecumplenesacondición.Esdecir,paracadaelementodesequenceseaplicalafunciónfunction;sielresultadoesTrueseañadealalistayencasocontrariosedescarta.
Acontinuaciónpodemosver un ejemplo en el que se utilizafilterparaconservarsololosnúmerosquesonpares.
defes_par(n):
return(n%2.0==0)l=[1,2,3]
l2=filter(es_par,l)
reduce(function,sequence[,initial])Lafunciónreduceaplicaunafunciónaparesdeelementosdeunasecuenciahastadejarlaenunsolovalor.
Acontinuaciónpodemosver un ejemplo en el que se utilizareduceparasumartodosloselementosdeunalista.
defsumar(x,y):
returnx+yl=[1,2,3]
l2=reduce(sumar,l)
FuncioneslambdaEl operador lambda sirve para crear funciones anónimas en línea. Al serfuncionesanónimas,esdecir,sinnombre,estasnopodránserreferenciadasmástarde.Las funciones lambda se construyen mediante el operador lambda, losparámetros de la función separados por comas (atención, SIN paréntesis), dospuntos(:)yelcódigodelafunción.Estaconstrucciónpodríanhabersidodeutilidadenlosejemplosanterioresparareducircódigo.Elprogramaqueutilizamosparaexplicarfilter,porejemplo,podríaexpresarseasí:
l=[1,2,3]
l2=filter(lambdan:n%2.0==0,l)
Comparémosloconlaversiónanterior:defes_par(n):
return(n%2.0==0)l=[1,2,3]l2=filter(es_par,l)
Lasfuncioneslambdaestánrestringidasporlasintaxisaunasolaexpresión.
ComprensióndelistasEn Python 3 map, filter y reduce perderán importancia. Y aunque estasfuncionessemantendrán,reducepasaráaformarpartedelmódulofunctools,con lo que quedará fuera de las funciones disponibles por defecto, y map yfiltersedesaconsejaránenfavordelaslistcomprehensionsocomprensióndelistas.La comprensión de listas es una característica tomada del lenguaje deprogramaciónfuncionalHaskellqueestápresenteenPythondesdelaversión2.0yconsisteenunaconstrucciónquepermitecrearlistasapartirdeotraslistas.Cadaunadeestasconstruccionesconstadeunaexpresiónquedeterminacómomodificarelelementodelalistaoriginal,seguidadeunaovariasclausulasforyopcionalmenteunaovariasclausulasif.Veamos un ejemplo de cómo se podría utilizar la comprensión de listas paraelevar al cuadrado todos los elementos de una lista, como hicimos en nuestroejemplodemap.
l2=[n**2forninl]
Esta expresión se leería como “para cada n en l haz n ** 2”. Como vemostenemosprimerolaexpresiónquemodificalosvaloresdelalistaoriginal(n**2), despuéselfor, elnombrequevamosautilizarpara referirnosal elementoactualdelalistaoriginal,elin,ylalistasobrelaqueseitera.Elejemploqueutilizamospara la funciónfilter (conservar solo losnúmerosquesonpares)sepodríaexpresarconcomprensióndelistasasí:
l2=[nforninlifn%2.0==0]
Veamosporúltimounejemplodecompresióndelistasconvariasclausulasfor:l=[0,1,2,3]
m=[“a”,“b”]
n=[s*vforsinm
forvinl
ifv>0]
Estaconstrucciónseríaequivalenteaunaseriedefor-inanidados:l=[0,1,2,3]
m=[“a”,“b”]
n=[]
forsinm:
forvinl:
ifv>0:
n.append(s*v)
GeneradoresLasexpresionesgeneradorasfuncionandeformamuysimilaralacomprensiónde listas. De hecho su sintaxis es exactamente igual, a excepción de que seutilizanparéntesisenlugardecorchetes:
l2=(n**2forninl)
Sin embargo las expresiones generadoras se diferencian de la comprensión delistasenquenosedevuelveunalista,sinoungenerador.
>>>l2=[n**2forninl]
>>>l2
[0,1,4,9]
>>>l2=(n**2forninl)
>>>l2
<generatorobjectat0×00E33210>
Ungeneradoresunaclaseespecialdefunciónquegeneravaloressobrelosqueiterar. Para devolver el siguiente valor sobre el que iterar se utiliza la palabraclaveyieldenlugardereturn.Veamosporejemploungeneradorquedevuelvanúmerosdenamconunsaltos.
defmi_generador(n,m,s):
while(n<=m):
yieldn
n+=s
>>>x=mi_generador(0,5,1)
>>>x
<generatorobjectat0×00E25710>
El generador se puede utilizar en cualquier lugar donde se necesite un objetoiterable.Porejemploenunfor-in:
forninmi_generador(0,5,1):
printn
Como no estamos creando una lista completa enmemoria, sino generando unsolovalorcadavezquesenecesita,ensituacionesen lasquenoseanecesariotenerlalistacompletaelusodegeneradorespuedesuponerunagrandiferenciade memoria. En todo caso siempre es posible crear una lista a partir de ungeneradormediantelafunciónlist:
lista=list(mi_generador)
DecoradoresUn decorador no es es mas que una función que recibe una función comoparámetro y devuelve otra función como resultado. Por ejemplo podríamosquerer añadir la funcionalidad de que se imprimiera el nombre de la funciónllamadapormotivosdedepuración:
defmi_decorador(funcion):
defnueva(*args):
print“Llamadaalafuncion”,funcion.__name__
retorno=funcion(*args)
returnretorno
returnnueva
Comovemoselcódigodelafunciónmi_decoradornohacemásquecrearunanuevafunciónydevolverla.Estanuevafunciónimprimeelnombredelafunciónalaque“decoramos”,ejecutaelcódigodedichafunción,ydevuelvesuvalorderetorno. Es decir, que si llamáramos a la nueva función que nos devuelvemi_decorador, el resultado sería elmismo que el de llamar directamente a lafunción que le pasamos como parámetro, exceptuando el que se imprimiríaademáselnombredelafunción.
Supongamoscomoejemplounafunciónimpquenohaceotracosaquemostrarenpantallalacadenapasadacomoparámetro.
>>>imp(“hola”)
hola
>>>mi_decorador(imp)(“hola”)
Llamadaalafunciónimp
hola
Lasintaxisparallamaralafunciónquenosdevuelvemi_decoradornoesmuyclara, aunque si lo estudiamos detenidamente veremos que no tiene mayorcomplicación.Primerosellamaalafunciónquedecoraconlafunciónadecorar:mi_decorador(imp); y una vez obtenida la función ya decorada se la puedellamar pasando el mismo parámetro que se pasó anteriormente:mi_decorador(imp)(“hola”)
Estosepodríaexpresarmásclaramenteprecediendoladefinicióndelafunciónque queremos decorar con el signo @ seguido del nombre de la funcióndecoradora:
@mi_decorador
defimp(s):
prints
De esta forma cada vez que se llame aimp se estará llamando realmente a laversión decorada. Python incorpora esta sintaxis desde la versión 2.4 enadelante.Siquisiéramosaplicarmásdeundecoradorbastaríaañadirunanuevalíneaconelnuevodecorador.
@otro_decorador
@mi_decorador
defimp(s):
prints
Es importante advertir que los decoradores se ejecutarán de abajo a arriba.Esdecir, en este ejemplo primero se ejecutaría mi_decorador y despuésotro_decorador.
EXCEPCIONESLas excepciones son errores detectados por Python durante la ejecución delprograma. Cuando el intérprete se encuentra con una situación excepcional,comoelintentardividirunnúmeroentre0oelintentaraccederaunarchivoquenoexiste,estegeneraolanzaunaexcepción,informandoalusuariodequeexistealgúnproblema.Silaexcepciónnosecapturaelflujodeejecuciónseinterrumpeysemuestralainformaciónasociadaalaexcepciónenlaconsoladeformaqueelprogramadorpuedasolucionarelproblema.Veamosunpequeñoprogramaque lanzaríaunaexcepciónal intentardividir1entre0.
defdivision(a,b):
returna/b
defcalcular():
division(1,0)
calcular()
Siloejecutamosobtendremoselsiguientemensajedeerror:$pythonejemplo.pyTraceback(mostrecentcalllast):File“ejemplo.py”,line7,incalcular()File“ejemplo.py”,line5,incalculardivision(1,0)File“ejemplo.py”,line2,indivisiona/bZeroDivisionError:integerdivisionormodulobyzero
Loprimeroquesemuestraeseltrazadodepilaotraceback,queconsisteenuna lista con las llamadas que provocaron la excepción. Como vemos en eltrazadodepila,elerrorestuvocausadoporlallamadaacalcular()delalínea7,queasuvezllamaadivision(1,0)enlalínea5yenúltimainstanciaporlaejecucióndelasentenciaa/bdelalínea2dedivision.
Acontinuaciónvemoseltipodelaexcepción,ZeroDivisionError,juntoauna descripción del error: “integer division or modulo by zero” (módulo odivisiónenteraentrecero).
EnPythonseutilizaunaconstruccióntry-exceptparacapturarytratarlasexcepciones.Elbloquetry (intentar) define el fragmentode código en el quecreemos que podría producirse una excepción. El bloque except (excepción)permite indicar el tratamiento que se llevará a cabo de producirse dichaexcepción. Muchas veces nuestro tratamiento de la excepción consistirásimplementeenimprimirunmensajemásamigableparaelusuario,otrasvecesnosinteresaráregistrarloserroresydevezencuandopodremosestablecerunaestrategiaderesolucióndelproblema.
Enelsiguienteejemplointentamoscrearunobjetofdetipofichero.Denoexistir el archivo pasado como parámetro, se lanza una excepción de tipoIOError,quecapturamosgraciasanuestrotry-except.
try:
f=file(“archivo.txt”)
except:
print“Elarchivonoexiste”
Python permite utilizar variosexcept para un solo bloquetry, de formaquepodamosdaruntratamientodistintoalaexcepcióndependiendodeltipodeexcepcióndelaquesetrate.Estoesunabuenapráctica,yestansencillocomoindicarelnombredeltipoacontinuacióndelexcept.
try:
num=int(“3a”)
printno_existe
exceptNameError:
print“Lavariablenoexiste”
exceptValueError:
print“Elvalornoesunnumero”
Cuandoselanzaunaexcepciónenelbloquetry,sebuscaencadaunadelasclausulasexceptunmanejadoradecuadoparaeltipodeerrorqueseprodujo.Encasodequenoseencuentre,sepropagalaexcepción.
Ademáspodemoshacerqueunmismoexceptsirvaparatratarmásdeunaexcepciónusandounatuplaparalistarlostiposdeerrorquequeremosquetrateelbloque:
try:num=int(“3a”)printno_existe
except(NameError,ValueError):print“Ocurriounerror”
La construccióntry-except puede contar además con una clausulaelse,quedefineunfragmentodecódigoaejecutarsólosinosehaproducidoningunaexcepcióneneltry.
try:
num=33
except:
print“Hubounerror!”
else:
print“Todoestabien”
Tambiénexisteunaclausulafinallyqueseejecutasiempre,seproduzcaonounaexcepción.Estaclausulasesueleutilizar,entreotrascosas,paratareasdelimpieza.
try:
z=x/y
exceptZeroDivisionError:
print“Divisionporcero”
finally:
print“Limpiando”
Tambiénesinteresantecomentarquecomoprogramadorespodemoscrearylanzar nuestras propias excepciones. Basta crear una clase que herede deExceptionocualquieradesushijasylanzarlaconraise.
classMiError(Exception):
def__init__(self,valor):
self.valor=valor
def__str__(self):
return“Error“+str(self.valor)
try:
ifresultado>20:
raiseMiError(33)
exceptMiError,e:
printe
Porúltimo,acontinuaciónse listanamodode referencia lasexcepcionesdisponibles por defecto, así como la clase de la que deriva cada una de ellasentreparéntesis.
BaseException:Clasedelaqueheredantodaslasexcepciones.
Exception(BaseException):Superclasede todas lasexcepcionesquenoseandesalida.
GeneratorExit(Exception):Sepidequesesalgadeungenerador.
StandardError(Exception):Clasebaseparatodaslasexcepcionesquenotenganqueverconsalirdelintérprete.
ArithmeticError(StandardError): Clase base para los erroresaritméticos.
FloatingPointError(ArithmeticError):Errorenunaoperacióndecomaflotante.
OverflowError(ArithmeticError): Resultado demasiado grande parapoderrepresentarse.
ZeroDivisionError(ArithmeticError): Lanzada cuando el segundoargumentodeunaoperacióndedivisiónomóduloera0.
AssertionError(StandardError): Falló la condición de un estamentoassert.
AttributeError(StandardError):Noseencontróelatributo.
EOFError(StandardError):Seintentóleermásalládelfinaldefichero.
EnvironmentError(StandardError): Clase padre de los erroresrelacionadosconlaentrada/salida.
IOError(EnvironmentError):Errorenunaoperacióndeentrada/salida.
OSError(EnvironmentError):Errorenunallamadaasistema.
WindowsError(OSError):ErrorenunallamadaasistemaenWindows.
ImportError(StandardError):No se encuentra elmódulo o el elementodelmóduloquesequeríaimportar.
LookupError(StandardError):Clasepadredeloserroresdeacceso.
IndexError(LookupError):El índicede la secuenciaestá fueradel rangoposible.
KeyError(LookupError):Laclavenoexiste.
MemoryError(StandardError):Noquedamemoriasuficiente.
NameError(StandardError): No se encontró ningún elemento con esenombre.
UnboundLocalError(NameError): El nombre no está asociado a ningunavariable.
ReferenceError(StandardError): El objeto no tiene ninguna referenciafuerteapuntandohaciaél.
RuntimeError(StandardError): Error en tiempo de ejecución noespecificado.
NotImplementedError(RuntimeError): Ese método o función no estáimplementado.
SyntaxError(StandardError):Clasepadreparaloserroressintácticos.
IndentationError(SyntaxError):Errorenlaindentacióndelarchivo.
TabError(IndentationError): Error debido a la mezcla de espacios ytabuladores.
SystemError(StandardError):Errorinternodelintérprete.
TypeError(StandardError):Tipodeargumentonoapropiado.
ValueError(StandardError):Valordelargumentonoapropiado.
UnicodeError(ValueError):Clasepadreparaloserroresrelacionadosconunicode.
UnicodeDecodeError(UnicodeError):Errordedecodificaciónunicode.
UnicodeEncodeError(UnicodeError):Errordecodificaciónunicode.
UnicodeTranslateError(UnicodeError):Errordetraducciónunicode.
StopIteration(Exception):Seutilizaparaindicarelfinaldeliterador.
Warning(Exception):Clasepadreparalosavisos.
DeprecationWarning(Warning): Clase padre para avisos sobrecaracterísticasobsoletas.
FutureWarning(Warning): Aviso. La semántica de la construccióncambiaráenunfuturo.
ImportWarning(Warning): Aviso sobre posibles errores a la hora deimportar.
PendingDeprecationWarning(Warning):Avisosobrecaracterísticasquesemarcaráncomoobsoletasenunfuturopróximo.
RuntimeWarning(Warning): Aviso sobre comportamientos dudosos entiempodeejecución.
SyntaxWarning(Warning):Avisosobresintaxisdudosa.
UnicodeWarning(Warning): Aviso sobre problemas relacionados conUnicode,sobretodoconproblemasdeconversión.
UserWarning(Warning): Clase padre para avisos creados por elprogramador.
KeyboardInterrupt(BaseException):Elprogramafueinterrumpidoporelusuario.
SystemExit(BaseException): Petición del intérprete para terminar laejecución.
MÓDULOSYPAQUETES
MódulosPara facilitar el mantenimiento y la lectura los programas demasiado largospuedendividirse enmódulos, agrupandoelementos relacionados.Losmódulosson entidades que permiten una organización y división lógica de nuestrocódigo. Los ficheros son su contrapartida física: cada archivo Pythonalmacenadoendiscoequivaleaunmódulo.Vamos a crear nuestro primer módulo entonces creando un pequeño archivomodulo.pyconelsiguientecontenido:
defmi_funcion():
print“unafuncion”
classMiClase:
def__init__(self):
print“unaclase”
print“unmodulo”
Si quisiéramos utilizar la funcionalidad definida en este módulo en nuestroprograma tendríamos que importarlo. Para importar un módulo se utiliza la
palabraclaveimportseguidadelnombredelmódulo,queconsisteenelnombredelarchivomenoslaextensión.Comoejemplo,creemosunarchivoprograma.pyen el mismo directorio en el que guardamos el archivo del módulo (esto esimportante,porque sino seencuentraenelmismodirectorioPythonnopodráencontrarlo),conelsiguientecontenido:
importmodulo
modulo.mi_funcion()
El import no solo hace que tengamos disponible todo lo definido dentro delmódulo,sinoquetambiénejecutaelcódigodelmódulo.Porestarazónnuestroprograma, ademásde imprimir el texto “una funcion”al llamar ami_funcion,también imprimiría el texto “un modulo”, debido al print del móduloimportado.Noseimprimiría,noobstante,eltexto“unaclase”,yaqueloquesehizoenelmódulofuetansolodefinirdelaclase,noinstanciarla.Laclausulaimporttambiénpermiteimportarvariosmódulosenlamismalínea.En el siguiente ejemplo podemos ver cómo se importa con una sola clausulaimport losmódulos de la distribución por defecto de Pythonos, que englobafuncionalidad relativa al sistemaoperativo;sys, con funcionalidad relacionadaconelpropiointérpretedePythonytime,enelquesealmacenanfuncionesparamanipularfechasyhoras.
importos,sys,time
printtime.asctime()
Sindudaoshabréisfijadoenesteyelanteriorejemploenundetalleimportante,y es que, como vemos, es necesario preceder el nombre de los objetos queimportamosdeunmóduloconelnombredelmóduloalquepertenecen,oloqueeslomismo,elespaciodenombresenelqueseencuentran.Estopermitequenosobreescribamosaccidentalmentealgúnotroobjetoquetuvieraelmismonombrealimportarotromódulo.Sinembargoesposibleutilizar laconstrucciónfrom-importparaahorrarnoseltenerqueindicarelnombredelmóduloantesdelobjetoquenosinteresa.Deestaformaseimportaelobjetoolosobjetosqueindiquemosalespaciodenombresactual.
fromtimeimportasctime
printasctime()
Aunque se considera unamala práctica, también es posible importar todos losnombresdelmóduloalespaciodenombresactualusandoelcarácter*:
fromtimeimport*
Ahorabien,recordareisquealahoradecrearnuestroprimermóduloinsistíenqueloguardaraisenelmismodirectorioenelqueseencontrabaelprogramaqueloimportaba.Entonces,¿cómopodemosimportarlosmódulosos,sysotimesinoseencuentranlosarchivosos.py,sys.pyytime.pyenelmismodirectorio?AlahoradeimportarunmóduloPythonrecorretodoslosdirectoriosindicadosen la variable de entorno PYTHONPATH en busca de un archivo con el nombreadecuado.Elvalorde lavariablePYTHONPATH sepuedeconsultardesdePythonmediantesys.path
>>>importsys
>>>sys.path
De esta forma para que nuestro módulo estuviera disponible para todos losprogramasdel sistemabastaríaconque locopiáramosaunode losdirectoriosindicadosenPYTHONPATH.En el caso de que Python no encontrara ningún módulo con el nombreespecificado,selanzaríaunaexcepcióndetipoImportError.Por último es interesante comentar que en Python los módulos también sonobjetos;detipomoduleenconcreto.Porsupuestoestosignificaquepuedenteneratributosymétodos.Unodesusatributos,__name__, seutiliza amenudoparaincluircódigoejecutableenunmóduloperoqueestesóloseejecutesisellamaalmódulocomoprograma,ynoalimportarlo.Paralograrestobastasaberquecuando se ejecuta el módulo directamente __name__ tiene como valor“__main__”,mientrasquecuandoseimporta,elvalorde__name__eselnombredelmódulo:
print“Semuestrasiempre”
if__name__==“__main__”:
print“Semuestrasinoesimportacion”
Otroatributointeresantees__doc__,que,comoenelcasodefuncionesyclases,sirve a modo de documentación del objeto (docstring o cadena dedocumentación).Suvaloreseldelaprimeralíneadelcuerpodelmódulo,enelcasodequeestaseaunacadenadetexto;encasocontrariovaldráNone.
PaquetesSi los módulos sirven para organizar el código, los paquetes sirven paraorganizarlosmódulos.Lospaquetessontiposespecialesdemódulos(ambossonde tipo module) que permiten agrupar módulos relacionados. Mientras losmódulos se corresponden a nivel físico con los archivos, los paquetes serepresentanmediantedirectorios.Enunaaplicacióncualquierapodríamostener,porejemplo,unpaqueteiuparalainterfazounpaquetebbddparalapersistenciaabasededatos.ParahacerquePythontrateaundirectoriocomounpaqueteesnecesariocrearun archivo __init__.py en dicha carpeta. En este archivo se pueden definirelementosquepertenezcanadichopaquete,comounaconstanteDRIVERparaelpaquetebbdd, aunquehabitualmente se tratarádeunarchivovacío.Parahacerque un ciertomódulo se encuentre dentro de un paquete, basta con copiar elarchivoquedefineelmóduloaldirectoriodelpaquete.Como losmódulos, para importar paquetes también se utiliza import y from-
importyelcarácter.parasepararpaquetes,subpaquetesymódulos.importpaq.subpaq.modulo
paq.subpaq.modulo.func()
A lo largo de los próximos capítulos veremos algunosmódulos y paquetes deutilidad.Paraencontraralgúnmóduloopaquetequecubraunaciertanecesidad,puedes consultar la lista de PyPI (Python Package Index) enhttp://pypi.python.org/,quecuentaalahoradeescribirestaslíneas,conmásde4000paquetesdistintos.
ENTRADA/SALIDAYFICHEROSNuestros programas serían de muy poca utilidad si no fueran capaces deinteraccionarconelusuario.Encapítulosanterioresvimos,depasada,elusodelapalabraclaveprintparamostrarmensajesenpantalla.Enestalección,ademásdedescribirmásdetalladamentedelusodeprintparamostrar mensajes al usuario, aprenderemos a utilizar las funciones input yraw_input para pedir información, así como los argumentos de línea decomandosy,porúltimo,laentrada/salidadeficheros.
EntradaestándarLaformamássencilladeobtenerinformaciónporpartedelusuarioesmediantela función raw_input. Esta función toma como parámetro una cadena a usarcomoprompt (esdecir,como textoamostraralusuariopidiendo laentrada)ydevuelve una cadena con los caracteres introducidos por el usuario hasta quepulsólateclaEnter.Veamosunpequeñoejemplo:nombre=raw_input(“Comotellamas?“)print“Encantado,“+nombre
Si necesitáramos un entero como entrada en lugar de una cadena, porejemplo, podríamos utilizar la función int para convertir la cadena a entero,aunqueseríaconvenientetenerencuentaquepuedelanzarseunaexcepciónsiloqueintroduceelusuarionoesunnúmero.
try:
edad=raw_input(“Cuantosanyostienes?“)dias=int(edad)*365
print“Hasvivido“+str(dias)+“dias”
exceptValueError:
print“Esonoesunnumero”
Lafuncióninputesunpocomáscomplicada.Loquehaceestafunciónes
utilizarraw_inputparaleerunacadenadelaentradaestándar,ydespuéspasaaevaluarlacomosidecódigoPythonsetratara;porlotantoinputdeberíatratarseconsumocuidado.
ParámetrosdelíneadecomandoAdemásdelusodeinputyraw_inputelprogramadorPythoncuentaconotrosmétodosparaobtenerdatosdelusuario.Unodeelloseselusodeparámetrosalahoradellamaralprogramaenlalíneadecomandos.Porejemplo:
pythoneditor.pyhola.txt
Enestecasohola.txtseríaelparámetro,alquesepuedeaccederatravésdelalista sys.argv, aunque, como es de suponer, antes de poder utilizar dichavariable debemos importar el módulo sys. sys.argv[0] contiene siempre elnombre del programa tal como lo ha ejecutado el usuario, sys.argv[1], siexiste,seríaelprimerparámetro;sys.argv[2]elsegundo,yasísucesivamente.
importsys
if(len(sys.argv)>1):
print“Abriendo“+sys.argv[1]
else:
print“Debesindicarelnombredelarchivo”
Existenmódulos,comooptparse,quefacilitaneltrabajoconlosargumentosde
la línea de comandos, pero explicar su uso queda fuera del objetivo de estecapítulo.
SalidaestándarLaformamássencillademostraralgoenlasalidaestándaresmedianteelusodelasentenciaprint,comohemosvistomultituddevecesenejemplosanteriores.En su formamás básica a la palabra claveprint le sigue una cadena, que semostraráenlasalidaestándaralejecutarseelestamento.
>>>print“Holamundo”
Holamundo
Despuésdeimprimirlacadenapasadacomoparámetroelpunterosesitúaenlasiguientelíneadelapantalla,porloqueelprintdePythonfuncionaigualqueelprintlndeJava.En algunas funciones equivalentes de otros lenguajes de programación esnecesario añadir un carácter de nueva línea para indicar explícitamente quequeremospasaralasiguientelínea.EsteeselcasodelafunciónprintfdeColapropiafunciónprintdeJava.Ya explicamos el uso de estos caracteres especiales durante la explicación deltipo cadena en el capítulo sobre los tipos básicos de Python. La siguiente
sentencia, por ejemplo, imprimiría la palabra “Hola”, seguida de un renglónvacío(doscaracteresdenuevalínea,‘\n’),yacontinuaciónlapalabra“mundo”indentada(uncaráctertabulador,‘\t’).
print“Hola\n\n\tmundo”
Paraque la siguiente impresión se realizara en lamisma línea tendríamosquecolocar una coma al final de la sentencia. Comparemos el resultado de estecódigo:
>>>foriinrange(3):
>>>...printi,
012
Coneldeesteotro,enelquenoutilizaunacomaalfinaldelasentencia:>>>foriinrange(3):
>>>...printi
0
1
2
Estemecanismodecolocarunacomaalfinaldelasentenciafuncionadebidoaqueeselsímboloqueseutilizaparasepararcadenasquequeramosimprimirenlamismalínea.
>>>print“Hola”,“mundo”
Holamundo
Estosediferenciadelusodeloperador+paraconcatenarlascadenasenquealutilizarlascomasprintintroduceautomáticamenteunespacioparasepararcadaunadelascadenas.Estenoeselcasoalutilizareloperador+,yaqueloquelellegaaprintesunsoloargumento:unacadenayaconcatenada.
>>>print“Hola”+“mundo”
Holamundo
Además,alutilizareloperador+tendríamosqueconvertirantescadaargumentoenunacadenadenoserloya,yaquenoesposibleconcatenarcadenasyotrostipos,mientrasquealusarelprimermétodonoesnecesarialaconversión.
>>>print“Cuesta”,3,“euros”
Cuesta3euros
>>>print“Cuesta”+3+“euros”
<type‘exceptions.TypeError’>:cannotconcatenate‘str’and
‘int’objects
La sentencia print, o más bien las cadenas que imprime, permiten tambiénutilizar técnicas avanzadas de formateo, de forma similar al sprintf de C.Veamosunejemplobastantesimple:
print“Hola%s”%“mundo”
print“%s%s”%(“Hola”,“mundo”)
Loquehacelaprimeralíneaesintroducirlosvaloresaladerechadelsímbolo%(la cadena “mundo”) en las posiciones indicadas por los especificadores deconversión de la cadena a la izquierda del símbolo%, tras convertirlos al tipoadecuado.Enlasegundalínea,vemoscómosepuedepasarmásdeunvalorasustituir,pormediodeunatupla.Enesteejemplosólotenemosunespecificadordeconversión:%s.Losespecificadoresmás sencillos están formadospor el símbolo% seguidodeunaletraqueindicaeltipoconelqueformatearelvalor.Porejemplo:
Especificador Formato%s Cadena%d Entero%o Octal%x Hexadecimal%f Real
Sepuedeintroducirunnúmeroentreel%yelcarácterqueindicaeltipoalqueformatear,indicandoelnúmeromínimodecaracteresquequeremosqueocupelacadena. Si el tamaño de la cadena resultante es menor que este número, seañadiránespaciosalaizquierdadelacadena.Enelcasodequeelnúmeroseanegativo,ocurriráexactamentelomismo,sóloquelosespaciosseañadiránaladerechadelacadena.
>>>print“%10smundo”%“Hola”
______Holamundo
>>>print“%-10smundo”%“Hola”
Hola_______mundo
Enelcasodelosrealesesposibleindicarlaprecisiónautilizarprecediendolafdeunpuntoseguidodelnúmerodedecimalesquequeremosmostrar:
>>>frommathimportpi
>>>print“%.4f”%pi
3.1416
Lamisma sintaxis sepuedeutilizarpara indicar el númerode caracteresde lacadenaquequeremosmostrar
>>>print“%.4s”%“holamundo”
hola
ArchivosLos ficheros en Python son objetos de tipo file creadosmediante la funciónopen (abrir). Esta función toma como parámetros una cadena con la ruta alficheroaabrir,quepuedeserrelativaoabsoluta;unacadenaopcionalindicandoelmododeacceso(sinoseespecificaseaccedeenmodolectura)y,porúltimo,unenteroopcionalparaespecificaruntamañodebufferdistintodelutilizadopordefecto.El modo de acceso puede ser cualquier combinación lógica de los siguientesmodos:
‘r’: read, lectura.Abre el archivo enmodo lectura. El archivo tiene queexistir previamente, en caso contrario se lanzará una excepción de tipoIOError.‘w’:write, escritura.Abre el archivo enmodo escritura. Si el archivo noexistesecrea.Siexiste,sobreescribeelcontenido.
‘a’: append, añadir.Abre el archivo enmodoescritura.Sediferenciadelmodo‘w’enqueenestecasonosesobreescribeelcontenidodelarchivo,sinoquesecomienzaaescribiralfinaldelarchivo.‘b’:binary,binario.‘+’:permitelecturayescriturasimultáneas.‘U’: universal newline, saltos de línea universales. Permite trabajar conarchivosquetenganunformatoparalossaltosdelíneaquenocoincideconeldelaplataformaactual(enWindowsseutilizaelcarácterCRLF,enUnixLFyenMacOSCR).
f=open(“archivo.txt”,“w”)
Tras crear el objeto que representa nuestro archivo mediante la función openpodremosrealizarlasoperacionesdelectura/escriturapertinentesutilizandolosmétodosdelobjetoqueveremosenlassiguientessecciones.Unavez terminemosde trabajar con el archivodebemos cerrarlo utilizando elmétodoclose.
LecturadearchivosParalalecturadearchivosseutilizanlosmétodosread,readlineyreadlines.El método read devuelve una cadena con el contenido del archivo o bien elcontenidodelosprimerosnbytes,siseespecificaeltamañomáximoaleer.
completo=f.read()
parte=f2.read(512)
Elmétodoreadlinesirveparaleerlaslíneasdelficherounaporuna.Esdecir,cadavezquesellamaaestemétodo,sedevuelveelcontenidodelarchivodesdeel puntero hasta que se encuentra un carácter de nueva línea, incluyendo estecarácter.
whileTrue:
linea=f.readline()
ifnotlinea:break
printlinea
Por último, readlines, funciona leyendo todas las líneas del archivo ydevolviendounalistaconlaslíneasleídas.
EscrituradearchivosPara la escritura de archivos se utilizan los métodos write y writelines.Mientraselprimerofuncionaescribiendoenelarchivounacadenadetextoque
tomacomoparámetro,elsegundotomacomoparámetrounalistadecadenasdetextoindicandolaslíneasquequeremosescribirenelfichero.
Moverelpunterodelectura/escrituraHay situaciones en las que nos puede interesar mover el puntero delectura/escritura a una posición determinada del archivo. Por ejemplo siqueremos empezar a escribir en una posición determinada y no al final o alprincipiodelarchivo.Para esto se utiliza el método seek que toma como parámetro un númeropositivoonegativoautilizarcomodesplazamiento.Tambiénesposibleutilizarun segundo parámetro para indicar desde dónde queremos que se haga eldesplazamiento: 0 indicará que el desplazamiento se refiere al principio delfichero (comportamientopordefecto), 1 se refiere a laposiciónactual, y2, alfinaldelfichero.Para determinar la posición en la que se encuentra actualmente el puntero seutilizaelmétodotell(),quedevuelveunenteroindicandoladistanciaenbytesdesdeelprincipiodelfichero.
EXPRESIONESREGULARESLas expresiones regulares, también llamadas regex o regexp, consisten enpatronesquedescribenconjuntosdecadenasdecaracteres.AlgoparecidoseríaescribirenlalíneadecomandosdeWindows
dir*.exe
‘*.exe’ sería una “expresión regular” que describiría todas las cadenas decaracteresqueempiezanconcualquiercosaseguidade‘.exe’,esdecir,todoslosarchivosexe.EltrabajoconexpresionesregularesenPythonserealizamedianteelmódulore,que data de Python 1.5 y que proporciona una sintaxis para la creación depatronessimilaraladePerl.EnPython1.6elmódulosereescribióparadotarlodesoportedecadenasunicodeymejorarsurendimiento.El módulo re contiene funciones para buscar patrones dentro de una cadena(search),comprobarsiunacadenaseajustaaundeterminadocriteriodescritomedianteunpatrón(match),dividirlacadenausandolasocurrenciasdelpatrón
comopuntosderuptura(split)oparasustituirtodaslasocurrenciasdelpatrónpor otra cadena (sub). Veremos estas funciones y alguna más en la próximasección, pero por ahora, aprendamos algo más sobre la sintaxis de lasexpresionesregulares.
PatronesLaexpresión regularmássencillaconsisteenunacadenasimple,quedescribeun conjunto compuesto tan solo por esamisma cadena. Por ejemplo, veamoscómolacadena“python”coincideconlaexpresiónregular“python”usandolafunciónmatch:
importre
ifre.match(“python”,“python”):
print“cierto”
Si quisiéramos comprobar si la cadena es “python”, “jython”, “cython” ocualquier otra cosa que termine en “ython”, podríamos utilizar el caráctercomodín,elpunto‘.’:
re.match(“.ython”,“python”)
re.match(“.ython”,“jython”)
Laexpresiónregular“.ython”describiríaa todas lascadenasqueconsistanenuncaráctercualquiera,menoseldenuevalínea,seguidode“ython”.Uncaráctercualquieraysolouno.Nocero,nidos,nitres.
En el caso de que necesitáramos el carácter ‘.’ en la expresión regular, ocualquier otro de los caracteres especiales que veremos a continuación,tendríamosqueescaparloutilizandolabarrainvertida.Paracomprobarsilacadenaconsisteen3caracteresseguidosdeunpunto,porejemplo,podríamosutilizarlosiguiente:
re.match(“...\.”,“abc.”)
Si necesitáramos una expresión que sólo resultara cierta para las cadenas“python”,“jython”y“cython”yningunaotra,podríamosutilizarelcarácter‘|’paraexpresaralternativaescribiendolostressubpatronescompletos:
re.match(“python|jython|cython”,“python”)
obientansololapartequepuedacambiar,encerradaentreparéntesis,formandoloqueseconocecomoungrupo.Losgrupos tienenunagran importanciaa lahora de trabajar con expresiones regulares y este no es su único uso, comoveremosenlasiguientesección.
re.match(“(p|j|c)ython”,“python”)
Otra opción consistiría en encerrar los caracteres ‘p’, ‘j’ y ‘c’ entre corchetespara formar una clase de caracteres, indicando que en esa posición puedecolocarsecualquieradeloscaracteresdelaclase.
re.match(“[pjc]ython”,“python”)
¿Ysiquisiéramoscomprobarsilacadenaes“python0”,“python1”,“python2”,... , “python9”? En lugar de tener que encerrar los 10 dígitos dentro de loscorchetespodemosutilizarelguión,quesirveparaindicarrangos.Porejemploa-d indicaría todas las letrasminúsculasde la‘a’a la ‘d’;0-9 serían todos losnúmerosde0a9inclusive.
re.match(“python[0-9]”,“python0”)
Siquisiéramos,porejemplo,queelúltimocarácterfueraoundígitoounaletrasimplementeseescribiríandentrodeloscorchetestodosloscriterios,unodetrásdeotro.
re.match(“python[0-9a-zA-Z]”,“pythonp”)
Es necesario advertir que dentro de las clases de caracteres los caracteresespecialesnonecesitanserescapados.Paracomprobarsilacadenaes“python.”o“python,”,entonces,escribiríamos:
re.match(“python[.,]”,“python.”)
yno
re.match(“python[\.,]”,“python.”)
yaqueenesteúltimocasoestaríamoscomprobandosilacadenaes“python.”,“python,”o“python\”.
Losconjuntosdecaracterestambiénsepuedennegarutilizandoelsímbolo‘^’.La expresión “python[^0-9a-z]”, por ejemplo, indicaríaquenos interesan lascadenasquecomiencenpor“python”ytengancomoúltimocarácteralgoquenoseaniunaletraminúsculaniunnúmero.
re.match(“python[^0-9a-z]”,“python+”)
Elusode[0-9] para referirse aundígitonoesmuycomún,yaque, al ser lacomprobación de que un carácter es un dígito algomuy utilizado, existe unasecuencia especial equivalente: ‘\d’. Existen otras secuencias disponibles quelistamosacontinuación:
“\d”:undígito.Equivalea[0-9]“\D”:cualquiercarácterquenoseaundígito.Equivalea[^0-9]“\w”:cualquiercarácteralfanumérico.Equivalea[a-zA-Z0-9_]“\W”:cualquiercarácternoalfanumérico.Equivalea[^a-zA-Z0-9_]“\s”:cualquiercarácterenblanco.Equivalea[\t\n\r\f\v]“\S”: cualquier carácter que no sea un espacio en blanco. Equivale a [^\t\n\r\f\v]
Veamosahoracómorepresentarrepeticionesdecaracteres,dadoquenoseríademucha utilidad tener que, por ejemplo, escribir una expresión regular con 30caracteres‘\d’parabuscarnúmerosde30dígitos.Paraestemenestertenemosloscaracteresespeciales+,*y?,ademásdelasllaves{}.Elcarácter+indicaqueloquetenemosalaizquierda,seauncaráctercomo‘a’,unaclasecomo ‘[abc]’ o un subpatrón como(abc), puede encontrarse una omasveces.Porejemplo laexpresión regular“python+”describiría las cadenas“python”, “pythonn” y “pythonnn”, pero no “pytho”, ya que debe haber almenosunan.Elcarácter*essimilara+,peroenestecasoloquesesitúaasuizquierdapuedeencontrarseceroomasveces.Elcarácter?indicaopcionalidad,esdecir,loquetenemosalaizquierdapuedeonoaparecer(puedeaparecer0o1veces).Finalmente las llavessirvenpara indicarelnúmerodevecesexactoquepuede
aparecerelcarácterdelaizquierda,obienunrangodevecesquepuedeaparecer.Por ejemplo{3} indicaría que tiene que aparecer exactamente 3 veces, {3,8}indicaríaquetienequeaparecerde3a8veces,{,8}de0a8vecesy{3,}tresvecesomas(lasquesean).Otro elemento interesante en las expresiones regulares, para terminar, es laespecificacióndelasposicionesenquesetienequeencontrarlacadena,esaeslautilidad de ^ y $, que indican, respectivamente, que el elemento sobre el queactúandebeiralprincipiodelacadenaoalfinaldeesta.La cadena “http://mundogeek.net”, por ejemplo, se ajustaría a la expresiónregular“^http”,mientrasquelacadena“Elprotocoloeshttp”noloharía,yaqueelhttpnoseencuentraalprincipiodelacadena.
UsandoelmóduloreYahemosvistoporencimacómoseutilizalafunciónmatchdelmóduloreparacomprobarsiunacadenaseajustaaundeterminadopatrón.Elprimerparámetrodelafuncióneslaexpresiónregular,elsegundo,lacadenaacomprobaryexisteuntercerparámetroopcionalquecontienedistintosflagsquesepuedenutilizarparamodificarelcomportamientodelasexpresionesregulares.Algunosejemplosdeflagsdelmóduloresonre.IGNORECASE,quehacequenosetengaencuentasilasletrassonmayúsculasominúsculasore.VERBOSE,quehacequeseignorenlosespaciosyloscomentariosenlacadenaquerepresentalaexpresiónregular.ElvalorderetornodelafunciónseráNoneencasodequelacadenanoseajusteal patrón o un objeto de tipo MatchObject en caso contrario. Este objetoMatchObjectcuentaconmétodosstartyendquedevuelven laposiciónen laquecomienzayfinalizalasubcadenareconocidaymétodosgroupygroupsquepermitenaccederalosgruposquepropiciaronelreconocimientodelacadena.
Al llamar al método group sin parámetros se nos devuelve el grupo 0 de lacadena reconocida. El grupo 0 es la subcadena reconocida por la expresiónregularalcompleto,aunquenoexistanparéntesisquedelimitenelgrupo.
>>>mo=re.match(“http://.+\net”,“http://mundogeek.net”)
>>>printmo.group()
http://mundogeek.net
Podríamoscreargruposutilizandolosparéntesis,comoaprendimosenlasecciónanterior,obteniendoasílapartedelacadenaquenosinterese.
>>>mo=re.match(“http://(.+)\net”,“http://mundogeek.net”)
>>>printmo.group(0)
http://mundogeek.net
>>>printmo.group(1)
mundogeek
El método groups, por su parte, devuelve una lista con todos los grupos,exceptuandoelgrupo0,queseomite.
>>>mo=re.match(“http://(.+)\(.{3})”,“http://mundogeek.net”)
>>>printmo.groups()
(‘mundogeek’,‘net’)
Lafunciónsearchdelmódulorefuncionadeformasimilaramatch;contamosconlosmismosparámetrosyelmismovalorderetorno.Laúnicadiferenciaesquealutilizarmatchlacadenadebeajustarsealpatróndesdeelprimercarácterde la cadena,mientras que consearch buscamos cualquier parte de la cadenaque se ajuste al patrón. Por esta razón el método start de un objetoMatchObjectobtenidomediantelafunciónmatchsiempredevolverá0,mientrasqueenelcasodesearchestonotieneporquéserasí.Otra función de búsqueda del módulo re es findall. Este toma los mismosparámetros que las dos funciones anteriores, pero devuelve una lista con lassubcadenasquecumplieronelpatrón.Otra posibilidad, si no queremos todas las coincidencias, es utilizarfinditer,que devuelve un iterador con el que consultar uno a uno los distintosMatchObject.Las expresiones regulares no solo permiten realizar búsquedas ocomprobaciones, sino que, como comentamos anteriormente, también tenemosfuncionesdisponiblesparadividirlacadenaorealizarreemplazos.Lafunciónsplitsinirmáslejostomacomoparámetrosunpatrón,unacadenayun entero opcional indicando el número máximo de elementos en los quequeremosdividirlacadena,yutilizaelpatrónamododepuntosdeseparaciónparalacadena,devolviendounalistaconlassubcadenas.
Lafunciónsubtomacomoparámetrosunpatrónasustituir,unacadenaqueusarcomo reemplazo cada vez que encontremos el patrón, la cadena sobre la querealizarlassustituciones,yunenteroopcionalindicandoelnúmeromáximodesustitucionesquequeremosrealizar.Al llamar a estosmétodos lo que ocurre en realidad es que se crea un nuevoobjeto de tipo RegexObject que representa la expresión regular, y se llama amétodos de este objeto que tienen losmismos nombres que las funciones delmódulo.Sivamosautilizarunmismopatrónvariasvecesnospuede interesarcrearunobjeto de este tipo y llamar a sus métodos nosotros mismos; de esta formaevitamosqueelintérpretetengaquecrearunnuevoobjetocadavezqueusemoselpatrónymejoraremoselrendimientodelaaplicación.ParacrearunobjetoRegexObject seutiliza la funcióncompiledelmódulo,alqueselepasacomoparámetrolacadenaquerepresentaelpatrónquequeremosutilizar para nuestra expresión regular y, opcionalmente, una serie de flags deentrelosquecomentamosanteriormente.
SOCKETSLa comunicación entre distintas entidades en una red se basa enPython en elclásicoconceptodesockets.Lossocketssonunconceptoabstractoconelquesedesignaalpuntofinaldeunaconexión.Los programas utilizan sockets para comunicarse con otros programas, quepuedenestarsituadosencomputadorasdistintas.UnsocketquedadefinidoporladirecciónIPdelamáquina,elpuertoenelqueescucha,yelprotocoloqueutiliza.Los tipos y funciones necesarios para trabajar con sockets se encuentran enPythonenelmódulosocket,comonopodríaserdeotraforma.Lossocketsseclasificanensocketsdeflujo(socket.SOCK_STREAM)osocketsdedatagramas(socket.SOCK_DGRAM)dependiendodesielservicioutilizaTCP,queesorientadoaconexiónyfiable,oUDP,respectivamente.Enestecapítulosólocubriremoslossocketsdeflujo,quecubrenun90%delasnecesidadescomunes.Los sockets también se pueden clasificar según la familia. Tenemos sockets
UNIX(socket.AF_UNIX)quesecrearonantesdelaconcepcióndelasredesysebasan en ficheros, sockets socket.AF_INET que son los que nos interesan,socketssocket.AF_INET6paraIPv6,etc.Paracrearunsocketseutilizaelconstructorsocket.socket()quepuedetomarcomo parámetros opcionales la familia, el tipo y el protocolo. Por defecto seutilizalafamiliaAF_INETyeltipoSOCK_STREAM.Veremosduranteelrestodelcapítulocómocrearunpardeprogramasclienteyservidoramododeejemplo.Loprimeroquetenemosquehacerescrearunobjetosocketparaelservidor
socket_s=socket.socket()Tenemosahoraqueindicarenquépuertosevaamantener
alaescuchanuestroservidorutilizandoelmétodobind.ParasocketsIP,comoes
nuestrocaso,elargumentodebindesunatuplaquecontieneelhostyelpuerto.El
hostsepuededejarvacío,indicandoalmétodoquepuedeutilizarcualquiernombre
queestédisponible.
socket_s.bind((“localhost”, 9999)) Por último utilizamos listen para hacer que el
socket acepte conexiones entrantes y accept para comenzar a escuchar. El método
listen requiere de un parámetro que indica el número de conexiones máximas que
queremosaceptar;evidentemente,estevalordebeseralmenos1.
Elmétodoacceptsemantienealaesperadeconexionesentrantes,bloqueandolaejecuciónhastaquellegaunmensaje.Cuando llega un mensaje, accept desbloquea la ejecución, devolviendo unobjetosocketquerepresentalaconexióndelclienteyunatuplaquecontieneelhostypuertodedichaconexión.
socket_s.listen(10)
socket_c,(host_c,puerto_c)=socket_s.accept()
Unavezquetenemosesteobjetosocketpodemoscomunicarnosconelclienteatravés suyo, mediante los métodos recv y send (o recvfrom y sendfrom enUDP)quepermitenrecibiroenviarmensajesrespectivamente.Elmétodosendtoma como parámetros los datos a enviar,mientras que elmétodo recv tomacomoparámetroelnúmeromáximodebytesaaceptar.
recibido=socket_c.recv(1024)print“Recibido:“,recibiosocket_c.send(recibido)
Una vez que hemos terminado de trabajar con el socket, lo cerramos con elmétodoclose.Crearunclienteesaúnmássencillo.Solotenemosquecrearelobjetosocket,utilizar elmétodoconnect para conectarnos al servidor y utilizar losmétodossendyrecvquevimosanteriormente.Elargumentodeconnectesunatuplaconhostypuerto,exactamenteigualquebind.
socket_c = socket.socket() socket_c.connect((“localhost”, 9999))
socket_c.send(“hola”)
Veamospor últimoun ejemplo completo.En este ejemplo el clientemanda alservidorcualquiermensajequeescribaelusuarioyelservidornohacemásquerepetirelmensajerecibido.Laejecuciónterminacuandoelusuarioescribequit.Esteseríaelcódigodelscriptservidor:importsocket
s=socket.socket()s.bind((“localhost”,9999))s.listen(1)
sc,addr=s.accept()whileTrue:recibido=sc.recv(1024)ifrecibido==“quit”:
breakprint“Recibido:”,recibidosc.send(recibido)
print“adios”
sc.close()s.close()Yacontinuacióntenemoseldelscriptcliente:importsocket
s=socket.socket()s.connect((“localhost”,9999))whileTrue:
mensaje=raw_input(“>“)s.send(mensaje)
mensaje==“quit”:
break
print“adios”
s.close()
INTERACTUARCONWEBSExistendosmódulosprincipalespara leerdatosdeURLsenPython:urllibyurllib2.Enestalecciónaprenderemosautilizarurllib2yaqueesmuchomáscompleto, aunque urllib tiene funcionalidades propias que no se puedenencontrarenurllib2,porloquetambiénlotocaremosdepasada.urllib2 puede leer datos de unaURL usando varios protocolos comoHTTP,HTTPS,FTP,oGopher.Seutilizaunafunciónurlopenparacrearunobjetoparecidoaunficheroconelque leer de la URL. Este objeto cuenta con métodos como read, readline,readlinesyclose, loscuales funcionanexactamente igualqueen losobjetosfile,aunqueenrealidadestamostrabajandoconunwrapperquenosabstraedeunsocketqueseutilizapordebajo.Elmétodo read, como recordareis, sirve para leer el “archivo” completo o elnúmerodebytesespecificadocomoparámetro,readlineparaleerunalínea,yreadlinesparaleertodaslaslíneasydevolverunalistaconellas.
También contamos conun par demétodosgeturl, para obtener laURLde laque estamos leyendo (que puede ser útil para comprobar si ha habido unaredirección)einfoquenosdevuelveunobjetocon lascabecerasde respuestadelservidor(alasquetambiénsepuedeaccedermedianteelatributoheaders).
importurllib2
try:
f=urllib2.urlopen(“http://www.python.org”)printf.read()
f.close()
exceptHTTPError,e:
print“Ocurrióunerror”
printe.code
exceptURLError,e:
print“Ocurrióunerror”
printe.reason
Al trabajar con urllib2 nos podemos encontrar, como vemos, con errores detipo URLError. Si trabajamos con HTTP podemos encontrarnos también conerroresdelasubclasedeURLErrorHTTPError,queselanzancuandoelservidordevuelveuncódigodeerrorHTTP,comoelerror404cuandonoseencuentraelrecurso. También podríamos encontrarnos con errores lanzados por la libreríaque urllib2 utiliza por debajo para las transferenciasHTTP: httplib; o conexcepcioneslanzadasporelpropiomódulosocket.La funciónurlopen cuenta con un parámetro opcionaldata con el que poderenviar información a direcciones HTTP (y solo HTTP) usando POST (losparámetros se envían en la propia petición), por ejemplo para responder a unformulario.Esteparámetroesunacadenacodificadaadecuadamente,siguiendoelformatoutilizadoenlasURLs:‘password=contrase%A4a&usuario=manuel’Lo más sencillo para codificar la cadena es utilizar el método urlencode deurllib,queaceptaundiccionarioounalistadetuplas(clave,valor)ygeneralacadenacodificadacorrespondiente:importurllib,urllib2
params=urllib.urlencode({“usuario”:“manuel”,“password”:“contraseña”})f=urllib2.urlopen(“http://ejemplo.com/login”,params)
SiloúnicoquequeremoshaceresdescargarelcontenidodeunaURLaunarchivo local,podemosutilizar la funciónurlretrievedeurllib en lugar deleerdeunobjetocreadoconurlopenyescribirlosdatosleídos.
La función urlretrieve toma como parámetros la URL a descargar y,opcionalmente, un parámetro filename con la ruta local en la que guardar elarchivo, un parámetro data similar al deurlopen y un parámetro reporthookconunafunciónqueutilizarparainformardelprogreso.
A excepción de las ocasiones en las que se utiliza el parámetro data lasconexionessiempreserealizanutilizandoGET(losparámetrosseenvíanen laURL).ParaenviardatosusandoGETbastaconconcatenarlacadenaresultantedeurlencodeconlaURLalaquenosvamosaconectarmedianteelsímbolo?.
params = urllib.urlencode({“usuario”: “manuel”, “password”: “contraseña”}) f =
urllib2.urlopen(“http://ejemplo.com/login”+
“?”+params)
En urllib también se utiliza una función urlopen para crear nuestrospseudo-archivos,peroadiferenciadelaversióndeurllib, lafunciónurlopendeurllib2 tambiénpuede tomarcomoparámetrounobjetoRequest,en lugardelaURLylosdatosaenviar.
La clase Request define objetos que encapsulan toda la informaciónrelativaaunapetición.Atravésdeesteobjetopodemosrealizarpeticionesmáscomplejas,añadiendonuestraspropiascabeceras,comoelUser-Agent.
El constructormás sencillo para el objetoRequest no tomamás que unacadena indicando la URL a la que conectarse, por lo que utilizar este objetocomoparámetrodeurlopenseríaequivalenteautilizarunacadenaconlaURLdirectamente.
Sin embargo el constructor de Request también tiene como parámetrosopcionales una cadena data para mandar datos por POST y un diccionarioheaders con las cabeceras (además de un par de campos origin_req_host yunverifiable,quequedanfueradelpropósitodelcapítuloporserderarouso).
VeamoscómoañadirnuestraspropiascabecerasutilizandocomoejemplolacabeceraUser-Agent.ElUser-Agentesunacabeceraquesirveparaidentificarelnavegador y sistema operativo que estamos utilizando para conectarnos a esaURL. Por defecto urllib2 se identifica como “Python-urllib/2.5”; siquisiéramos identificarnos como un Linux corriendo Konqueror por ejemplo,usaríamosuncódigosimilaralsiguiente:
ua=“Mozilla/5.0(compatible;Konqueror/3.5.8;Linux)”
h={“User-Agent”:ua}
r=urllib2.Request(“http://www.python.org”,headers=h)f=urllib2.urlopen(r)
printf.read()
Para personalizar la forma en que trabaja urllib2 podemos instalar ungrupo de manejadores ( handlers) agrupados en un objeto de la claseOpenerDirector (opener o abridor), que será el que se utilice a partir de esemomentoalllamaraurlopen.
Paraconstruirunopenerseutilizalafunciónbuild_openera laqueselepasalosmanejadoresqueformaránpartedelopener.Elopenerseencargaráde
encadenarlaejecucióndelosdistintosmanejadoresenelordendado.Tambiénse puede usar el constructor de OpenerDirector, y añadir los manejadoresusandosumétodoadd_handler.
Parainstalarelopenerunavezcreadoseutilizalafuncióninstall_opener,que toma como parámetro el opener a instalar. También se podría, si sóloqueremosabrirlaURLconeseopenerunasolavez,utilizarelmétodoopendelopener.
urllib2 cuenta con handlers que se encargan de manejar los esquemasdisponibles (HTTP, HTTPS, FTP), manejar la autenticación, manejar lasredirecciones,etc.
Paraañadirautenticación tendríamosque instalarunopenerque incluyeracomo manejador HTTPBasicAuthHandler, ProxyBasicAuthHandler,HTTPDigestAuthHandlery/oProxyDigestAuthHandler.
Para utilizar autenticación HTTP básica, por ejemplo, usaríamosHTTPBasicAuthHandler:
aut_h=urllib2.HTTPBasicAuthHandler()
aut_h.add_password(“realm”, “host”, “usuario”, “password”) opener =
urllib2.build_opener(aut_h)urllib2.install_opener(opener)
f=urllib2.urlopen(“http://www.python.org”)
Siquisiéramosespecificarunproxyenelcódigotendríamosqueutilizarunopener que contuviera el manejador ProxyHandler. El manejador por defectoincluyeunainstanciadeProxyHandlerconstruidollamandoalinicializadorsinparámetros, con lo que se lee la lista de proxies a utilizar de la variable deentorno adecuada. Sin embargo también podemos construir un ProxyHandlerpasando como parámetro al inicializador un diccionario cuyas claves son losprotocolosylosvalores,laURLdelproxyautilizarparadichoprotocolo.
proxy_h = urllib2.ProxyHandler({“http” : “http://miproxy.net:123”}) opener =
urllib2.build_opener(proxy_h)urllib2.install_opener(opener)
f=urllib2.urlopen(“http://www.python.org”)
ParaqueseguardenlascookiesquemandaHTTPutilizamoselmanejadorHTTPCookieProcessor.
cookie_h = urllib2.HTTPCookieProcessor() opener = urllib2.build_opener(cookie_h)
urllib2.install_opener(opener)
f=urllib2.urlopen(“http://www.python.org”)
Si queremos acceder a estas cookies o poder mandar nuestras propiascookies, podemos pasarle como parámetro al inicializador de
HTTPCookieProcessorunobjetodetipoCookieJardelmódulocookielib.Para leer las cookiesmandadas basta crear un objeto iterable a partir del
CookieJar(tambiénpodríamosbuscarlascabecerascorrespondientes,peroestesistemaesmásclaroysencillo):
importurllib2,cookielib
cookie_j=cookielib.CookieJar()
cookie_h = urllib2.HTTPCookieProcessor(cookie_j) opener =urllib2.build_opener(cookie_h)
opener.open(“http://www.python.org”)
fornum,cookieinenumerate(cookie_j):printnum,cookie.nameprintcookie.valueprintEn el improbable caso de que necesitáramos añadir una cookie antes de
realizar la conexión, en lugar de conectarnos para que el sitio la mande,podríamosutilizarelmétodoset_cookie deCookieJar, al que lepasamosunobjeto de tipo Cookie. El constructor de Cookie, no obstante, es bastantecomplicado.
THREADS
¿Quésonlosprocesosylosthreads?Lascomputadorasseríanmuchomenosútilessinopudiéramoshacermásdeunacosa a la vez. Si no pudiéramos, por ejemplo, escuchar música en nuestroreproductordeaudiofavoritomientrasleemosuntutorialdePythonenMundoGeek.Pero, ¿cómoseconseguíaestoencomputadorasantiguasconun solonúcleo /una sola CPU? Lo que ocurría, y lo que ocurre ahora, es que en realidad noestamosejecutandovariosprocesosalavez(sellamaprocesoaunprogramaenejecución),sinoquelosprocesossevanturnandoy,dadalavelocidadalaqueejecutan las instrucciones, nosotros tenemos la impresión de que las tareas seejecutandeformaparalelacomosituviéramosmultitareareal.Cadavezqueunprocesodistintopasaaejecutarseesnecesariorealizarloquesellamauncambiodecontexto,duranteelcualsesalvaelestadodelprogramaqueseestabaejecutandoamemoriaysecargaelestadodelprogramaquevaaentraraejecutarse.
En Python podemos crear nuevos procesosmediante la función os.fork, queejecuta la llamada al sistemafork, omediante otras funcionesmás avanzadascomo popen2.popen2, de forma que nuestro programa pueda realizar variastareasdeformaparalela.Sinembargoelcambiodecontextopuedeserrelativamentelento,ylosrecursosnecesariosparamantenerelestadodemasiados,porloqueamenudoesmuchomáseficazutilizarloqueseconocecomothreads,hilosdeejecución,oprocesosligeros.Losthreadssonunconceptosimilaralosprocesos:tambiénsetratadecódigoenejecución. Sin embargo los threads se ejecutan dentro de un proceso, y losthreadsdelprocesocompartenrecursosentresi,comolamemoria,porejemplo.Elsistemaoperativonecesitamenosrecursosparacrearygestionarlosthreads,yalcompartirrecursos,elcambiodecontextoesmásrápido.Además,dadoquelos threads comparten el mismo espacio de memoria global, es sencillocompartir información entre ellos: cualquier variable global que tengamos ennuestroprogramaesvistaportodoslosthreads.
ElGILLa ejecución de los threads en Python está controlada por el GIL (GlobalInterpreter Lock) de forma que sólo un thread puede ejecutarse a la vez,independientementedelnúmerodeprocesadoresconelquecuentelamáquina.Esto posibilita que el escribir extensiones en C para Python sea mucho mássencillo,perotieneladesventajadelimitarmuchoelrendimiento,porloqueapesardetodo,enPython,enocasionesnospuedeinteresarmásutilizarprocesosquethreads,quenosufrendeestalimitación.Cada cierto número de instrucciones de bytecode la máquina virtual para laejecucióndelthreadyeligeotrodeentrelosqueestabanesperando.Por defecto el cambio de thread se realiza cada 10 instrucciones de bytecode,aunque se puede modificar mediante la función sys.setcheckinterval.Tambiénsecambiadethreadcuandoelhiloseponeadormircontime.sleepocuando comienza una operación de entrada/salida, las cuales pueden tardarmuchoenfinalizar,yporlotanto,denorealizarelcambio,tendríamosalaCPUdemasiadotiemposintrabajaresperandoaquelaoperacióndeE/Sterminara.
Para minimizar un poco el efecto del GIL en el rendimiento de nuestraaplicaciónesconvenientellamaralintérpreteconelflag-O,loqueharáquesegenereunbytecodeoptimizadoconmenosinstrucciones,y,porlotanto,menoscambiosdecontexto.Tambiénpodemosplantearnoselutilizarprocesosenlugardethreads,comoyacomentamos,utilizandoporejemploelmóduloprocessing;escribirelcódigoenelqueel rendimientoseacríticoenunaextensiónenCoutilizarIronPythonoJython,quecarecendeGIL.
ThreadsenPythonEl trabajo con threads se lleva a cabo en Pythonmediante elmódulothread.Estemóduloesopcionalydependientedelaplataforma,ypuedesernecesario,aunquenoescomún,recompilarelintérpreteparaañadirelsoportedethreads.Ademásdethread, tambiéncontamosconelmódulothreading que se apoyaenelprimeroparaproporcionarnosunaAPIdemásaltonivel,máscompleta,yorientadaaobjetos.ElmódulothreadingsebasaligeramenteenelmodelodethreadsdeJava.El módulo threading contiene una clase Thread que debemos extender paracrearnuestrospropioshilosdeejecución.Elmétodoruncontendráelcódigoquequeremos que ejecute el thread. Si queremos especificar nuestro propioconstructor, este deberá llamar a threading.Thread.__init__(self) parainicializarelobjetocorrectamente.
importthreading
classMiThread(threading.Thread):
def__init__(self,num):
threading.Thread.__init__(self)
self.num=num
defrun(self):
print“Soyelhilo”,self.num
Paraqueelthreadcomienceaejecutarsucódigobastaconcrearunainstanciadelaclasequeacabamosdedefiniryllamarasumétodostart.Elcódigodelhiloprincipalyeldelqueacabamosdecrearseejecutarándeformaconcurrente.
print“Soyelhiloprincipal”
foriinrange(0,10):
t=MiThread(i)
t.start()
t.join()
Elmétodo join se utiliza para que el hilo que ejecuta la llamada se bloqueehastaquefinaliceelthreadsobreelquesellama.Enestecasoseutilizaparaqueel hilo principal no termine su ejecución antes que los hijos, lo cual podríaresultarenalgunasplataformasenlaterminacióndeloshijosantesdefinalizarsuejecución.Elmétodojoinpuedetomarcomoparámetrounnúmeroencomaflotanteindicandoelnúmeromáximodesegundosaesperar.Si se intenta llamar al método start para una instancia que ya se estáejecutando,obtendremosunaexcepción.Laformarecomendadadecrearnuevoshilosdeejecuciónconsisteenextenderla clase Thread, como hemos visto, aunque también es posible crear unainstanciadeThreaddirectamente,eindicarcomoparámetrosdelconstructorunaclase ejecutable (una clase con elmétodo especial__call__) o una función aejecutar, y los argumentos en una tupla (parámetro args) o un diccionario(parámetrokwargs).
importthreading
defimprime(num):
print“Soyelhilo”,num
print“Soyelhiloprincipal”
foriinrange(0,10):
t=threading.Thread(target=imprime,args=(i,))
t.start()
Además de los parámetros target, args y kwargs también podemos pasar alconstructorunparámetrodetipocadenanameconelnombrequequeremosquetome el thread (el thread tendrá un nombre predeterminado aunque no loespecifiquemos);unparámetrodetipobooleanoverboseparaindicaralmóduloque imprimamensajes sobre el estado de los threads para la depuración y unparámetrogroup,queporahoranoadmiteningúnvalorperoqueenelfuturose
utilizaráparacreargruposdethreadsypodertrabajaraniveldegrupos.Para comprobar si un thread sigue ejecutándose, se puede utilizar el métodoisAlive.TambiénpodemosasignarunnombrealhiloyconsultarsunombreconlosmétodossetNameygetName,respectivamente.Mediantelafunciónthreading.enumerateobtendremosunalistadelosobjetosThreadqueseestánejecutando,incluyendoelhiloprincipal(podemoscompararelobjetoThreadconlavariablemain_threadparacomprobarsisetratadelhiloprincipal) y con threading.activeCount podemos consultar el número dethreadsejecutándose.Los objetos Thread también cuentan con un método setDaemon que toma unvalorbooleanoindicandosisetratadeundemonio.Lautilidaddeestoesquesisolo quedan threads de tipo demonio ejecutándose, la aplicación terminaráautomáticamente,terminandoestosthreadsdeformasegura.Por último tenemos en el módulo threading una clase Timer que hereda deThreadycuyautilidadesladeejecutarelcódigodesumétodorundespuésdeun periodo de tiempo indicado como parámetro en su constructor. Tambiénincluyeunmétodocancelmediante el que cancelar la ejecución antes de quetermineelperiododeespera.
SincronizaciónUno de losmayores problemas a los que tenemos que enfrentarnos al utilizarthreadses lanecesidaddesincronizarelaccesoaciertos recursosporpartedelosthreads.Entrelosmecanismosdesincronizaciónquetenemosdisponiblesenel módulo threading se encuentran los locks, locks reentrantes, semáforos,condicionesyeventos.Loslocks, tambiénllamadosmutex(demutualexclusion),cierresdeexclusiónmutua, cierres o candados, son objetos con dos estados posibles: adquirido olibre.Cuandounthreadadquiereelcandado,losdemásthreadsquelleguenaesepuntoposteriormenteypidanadquirirlosebloquearánhastaqueelthreadquelohaadquiridolibereelcandado,momentoenelcualpodráentrarotrothread.El candado se representamediante la clase Lock. Para adquirir el candado seutilizaelmétodoacquiredelobjeto,alqueselepuedepasarunbooleanoparaindicarsiqueremosesperaraqueselibere(True)ono(False).Siindicamosquenoqueremosesperar, elmétododevolveráTrue oFalse dependiendode si seadquirióonoelcandado,respectivamente.Pordefecto,sinoseindicanada,el
hilosebloqueaindefinidamente.Paraliberarelcandadounavezhemosterminadodeejecutarelbloquedecódigoenelquepudieraproducirseunproblemadeconcurrencia,seutilizaelmétodorelease.
lista=[]
lock=threading.Lock()
defanyadir(obj):
lock.acquire()
lista.append(obj)
lock.release()
defobtener():
lock.acquire()
obj=lista.pop()
lock.release()
returnobj
LaclaseRLockfuncionadeformasimilaraLock,peroenestecasoelcandadopuede ser adquirido por el mismo thread varias veces, y no quedará liberadohastaqueelthreadllameareleasetantasvecescomollamóaacquire.Comoen Lock, y como en todas las primitivas de sincronización que veremos acontinuación,esposibleindicaraacquiresiqueremosquesebloqueeono.Lossemáforossonotraclasedecandados.Laclasecorrespondiente,Semaphore,tambiéncuentaconmétodosacquireyrelease,perosediferenciadeunLocknormal en que el constructor de Semaphore puede tomar como parámetroopcionalunenterovalue indicandoelnúmeromáximode threadsquepuedenaccedera laveza laseccióndecódigocrítico.Sinoseindicanadapermiteelaccesoaunsolothread.Cuandounthreadllamaaacquire,lavariablequeindicaelnúmerodethreadsquepuedenadquirirelsemáforodisminuyeen1,porquehemospermitidoentrarenlaseccióndecódigocríticoaunhilomás.Cuandounhilollamaarelease,lavariableaumentaen1.Noeshastaqueestavariabledelsemáforoes0,quellamaraacquireproduciráun bloqueo en el thread que realizó la petición, a la espera de que algún otrothreadllameareleaseparaliberarsuplaza.Esimportantedestacarqueelvalorinicialdelavariabletalcomolopasamosenelconstructor,noesun límitemáximo,sinoquemúltiples llamadasareleasepuedenhacerqueelvalordelavariableseamayorquesuvalororiginal.Sinoesestoloquequeremos,podemosutilizarlaclaseBoundedSemaphoreencuyocaso,ahora si, se consideraría un error llamar a release demasiadas veces, y selanzaríaunaexcepcióndetipoValueErrordesuperarseelvalorinicial.
Podríamos utilizar los semáforos, por ejemplo, en un pequeño programa en elquemúltiplesthreadsdescargarandatosdeunaURL,deformaquepudiéramoslimitar el númerode conexiones a realizar al sitiowebparanobombardear elsitioconcientosdepeticionesconcurrentes.
semaforo=threading.Semaphore(4)
defdescargar(url):
semaforo.acquire()
urllib.urlretrieve(url)
semaforo.release()
Las condiciones (clase Condition) son de utilidad para hacer que los threadssólopuedanentrarenlaseccióncríticadedarseunaciertacondiciónoevento.Para esto utilizan un Lock pasado como parámetro, o crean un objeto RLockautomáticamentesinosepasaningúnparámetroalconstructor.Sonespecialmenteadecuadasparaelclásicoproblemadeproductor-consumidor.La clase cuenta conmétodosacquire yrelease, que llamarán a losmétodoscorrespondientesdelcandadoasociado.Tambiéntenemosmétodoswait,notifyynotifyAll.El método wait debe llamarse después de haber adquirido el candado conacquire. Este método libera el candado y bloquea al thread hasta que unallamadaanotifyonotifyAllenotro thread le indicanquesehacumplidolacondiciónporlaqueesperaba.Elthreadqueinformaalosdemásdequesehaproducidolacondición,tambiéndebellamaraacquireantesdellamaranotifyonotifyAll.Al llamar a notify, se informa del evento a un solo thread, y por tanto sedespiertaunsolothread.AlllamaranotifyAllsedespiertantodoslos threadsqueesperabanalacondición.Tanto el thread que notifica como los que son notificados tienenque terminarliberandoellockconrelease.
lista=[]
cond=threading.Condition()
defconsumir():
cond.acquire()
cond.wait()
obj=lista.pop()
cond.release()
returnobj
defproducir(obj):
cond.acquire()
lista.append(obj)
cond.notify()
cond.release()
Los eventos, implementados mediante la clase Event, son un wrapper porencimadeCondition y sirvenprincipalmentepara coordinar threadsmedianteseñalesqueindicanquesehaproducidounevento.Loseventosnosabstraendelhecho de que estemos utilizando un Lock por debajo, por lo que carecen demétodosacquireyrelease.El thread que debe esperar el evento llama al método wait y se bloquea,opcionalmentepasandocomoparámetrounnúmeroencomaflotanteindicandoel númeromáximode segundos a esperar.Otrohilo, cuandoocurre el evento,mandalaseñalalosthreadsbloqueadosalaesperadedichoeventoutilizandoelmétodoset.Losthreadsqueestabanesperandosedesbloqueanunavezrecibidala señal.El flagquedetermina si sehaproducidoel evento sepuedevolveraestablecerafalsousandoclear.Comovemos los eventos sonmuy similares a las condiciones, a excepcióndequesedesbloqueantodoslosthreadsqueesperabaneleventoyquenotenemosquellamaraacquireyrelease.
importthreading,time
classMiThread(threading.Thread):
def__init__(self,evento):
threading.Thread.__init__(self)
self.evento=evento
defrun(self):
printself.getName(),“esperandoalevento”
self.evento.wait()
printself.getName(),“terminalaespera”
evento=threading.Event()
t1=MiThread(evento)
t1.start()
t2=MiThread(evento)
t2.start()
#Esperamosunpoco
time.sleep(5)
evento.set()
Porúltimo,unpequeñoextra.SisoisusuariosdeJavasindudaestaréisechandoen falta una palabra clave syncronized para hacer que sólo un thread puedaaccederalmétodosobreelqueseutilizaalavez.UnaconstruccióncomúneselusodeundecoradorparaimplementarestafuncionalidadusandounLock.Seríaalgoasí:
defsynchronized(lock):
defdec(f):
deffunc_dec(*args,**kwargs):
lock.acquire()
try:
returnf(*args,**kwargs)
finally:
lock.release()
returnfunc_dec
returndec
classMyThread(threading.Thread):
@synchronized(mi_lock)
defrun(self):
print“metodosincronizado”
DatosglobalesindependientesComo ya hemos comentado los threads comparten las variables globales. Sinembargo pueden existir situaciones en las que queramos utilizar variablesglobalesperoqueestasvariablessecomportencomosifueranlocalesaunsolothread. Es decir, que cada uno de los threads tengan valores distintosindependientes,yqueloscambiosdeundeterminadothreadsobreelvalornoseveanreflejadosenlascopiasdelosdemásthreads.Paralograrestecomportamientosepuedeutilizarlaclasethreading.local,quecrea un almacén de datos locales. Primero debemos crear una instancia de laclase,odeunasubclase,paradespuésalmacenaryobtenerlosvaloresatravésdeparámetrosdelaclase.
datos_locales=threading.local()datos_locales.mi_var=“hola”
printdatos_locales.mi_var
Fijémonosenel siguiente código,por ejemplo.Paraelhiloprincipal elobjetolocal tiene un atributo var, y por lo tanto el print imprime su valor sinproblemas.Sinembargoparaelhiloteseatributonoexiste,yporlotantolanza
unaexcepción.local=threading.local()deff():printlocal.varlocal.var=“hola”
t=threading.Thread(target=f)printlocal.var
t.start()
t.join()
CompartirinformaciónParacompartirinformaciónentrelosthreadsdeformasencillapodemosutilizarlaclaseQueue.Queue,queimplementaunacola(unaestructuradedatosdetipoFIFO)consoportemultihilo.Estaclaseutilizalasprimitivasdethreadingparaahorrarnostenerquesincronizarelaccesoalosdatosnosotrosmismos.El constructor de Queue toma un parámetro opcional indicando el tamañomáximodelacola.Sinoseindicaningúnvalornohaylímitedetamaño.Paraañadirunelementoalacolaseutilizaelmétodoput(item);paraobtenerelsiguiente elemento, get(). Ambos métodos tienen un parámetro booleanoopcional block que indica si queremos que se espere hasta que haya algúnelementoenlacolaparapoderdevolverloohastaquelacoladejedeestarllenaparapoderintroducirlo.También existe un parámetro opcional timeout que indica, en segundos, eltiempo máximo a esperar. Si el timeout acaba sin poder haber realizado laoperacióndebidoaquelacolaestaballenaovacía,obiensiblockeraFalse,se
lanzaráunaexcepcióndetipoQueue.FulloQueue.Empty,respectivamente.Conqsize obtenemos el tamañode la colay conempty()yfull() podemoscomprobarsiestávacíaollena.
q=Queue.Queue()
classMiThread(threading.Thread):
def__init__(self,q):
self.q=q
threading.Thread.__init__(self)
defrun(self):
whileTrue:
try:
obj=q.get(False)
exceptQueue.Empty:
print“Fin”
break
printobj
foriinrange(10):
q.put(i)
t=MiThread(q)
t.start()
t.join()
SERIALIZACIÓNDEOBJETOSAlgunas veces tenemos la necesidad de guardar un objeto a disco para poderrecuperarlomástarde,opuedequenosseanecesariomandarunobjetoatravésdelared,aotroprogramaenPythonejecutándoseenotramáquina.Al procesode transformar el estadodeunobjeto enun formatoque se puedaalmacenar,recuperarytransportarseleconoceconelnombredeserializaciónomarshalling.EnPythontenemosvariosmódulosquenosfacilitanestatarea,comomarshal,pickle,cPickleyshelve.Elmódulomarshaleselmásbásicoyelmásprimitivodelostres,yesque,dehecho,supropósitoprincipalysurazóndesernoeseldeserializarobjetos,sinotrabajarconbytecodePython(archivos.pyc).marshalsólopermiteserializarobjetossimples(lamayoríadelostiposincluidospor defecto en Python), y no proporciona ningún tipo de mecanismo deseguridad ni comprobaciones frente a datos corruptos o mal formateados. Es
más, el formato utilizado para guardar el bytecode (y por tanto el formatoutilizadoparaguardarlosobjetosconmarshal)puedecambiarentreversiones,porloquenoesadecuadoparaalmacenardatosdelargaduración.pickle, por su parte, permite serializar casi cualquier objeto (objetos de tiposdefinidos por el usuario, colecciones que contienen colecciones, etc) y cuentacon algunos mecanismos de seguridad básicos. Sin embargo, al ser máscomplejoquemarshal,y,sobretodo,alestarescritoenPythonenlugardeenC,comomarshal,tambiénesmuchomáslento.La solución, si la velocidad de la serialización es importante para nuestraaplicación,esutilizarcPickle,quenoesmásqueesunaimplementaciónenCdepickle.cPickleeshasta1000vecesmásrápidoquepickle,yprácticamenteigualderápidoquemarshal.Si intentamos importar cPickle y se produce un error por algún motivo, selanzará una excepción de tipo ImportError. Para utilizar cPickle si estádisponible y pickle en caso contrario, podríamos usar un código similar alsiguiente:
try:
importcPickleaspickleexceptImportError:
importpickle
as en un import sirve para importar el elemento seleccionado utilizando otronombreindicado,enlugardesunombre.La formamás sencilla de serializar un objeto usandopickle esmediante unallamadaa la funcióndumppasandocomoargumentoelobjetoaserializaryunobjetoarchivoenelqueguardarlo(ocualquierotrotipodeobjetosimilaraunarchivo,siemprequeofrezcamétodosread,readlineywrite).
try:
importcPickleaspickleexceptImportError:
importpickle
fichero=file(“datos.dat”,“w”)animales=[“piton”,“mono”,“camello”]
pickle.dump(animales,fichero)fichero.close()
La funcióndump también tiene un parámetro opcional protocol que indica elprotocolo a utilizar al guardar. Por defecto su valor es 0, que utiliza formatotexto y es el menos eficiente. El protocolo 1 es más eficiente que el 0, peromenosqueel2.Tantoelprotocolo1comoel2utilizanunformatobinarioparaguardarlosdatos.
try:
importcPickleaspickleexceptImportError:
importpickle
fichero=file(“datos.dat”,“w”)animales=[“piton”,“mono”,“camello”]
pickle.dump(animales,fichero,2)fichero.close()
Paravolveracargarunobjetoserializadoseutilizalafunciónload,alaqueselepasaelarchivoenelqueseguardó.
try:
importcPickleaspickleexceptImportError:
importpickle
fichero=file(“datos.dat”,“w”)animales=[“piton”,“mono”,“camello”]
pickle.dump(animales,fichero)
fichero.close()
fichero=file(“datos.dat”)
animales2=pickle.load(fichero)
printanimales2
Supongamosahoraquequeremosalmacenarunpardelistasenunfichero.Estosería tansencillocomo llamarunavezadumpporcada lista,y llamardespuésunavezaloadporcadalista.
fichero=file(“datos.dat”,“w”)animales=[“piton”,“mono”,“camello”]
lenguajes=[“python”,“mono”,“perl”]
pickle.dump(animales,fichero)pickle.dump(lenguajes,fichero)
fichero = file(“datos.dat”) animales2 = pickle.load(fichero) lenguajes2 =
pickle.load(fichero)printanimales2
printlenguajes2
Pero,¿ysihubiéramosguardado30objetosyquisiéramosaccederalúltimodeellos?¿osinorecordáramosenquéposiciónlohabíamosguardado?Elmóduloshelve extiendepickle /cPickle para proporcionar una forma de realizar laserialización más clara y sencilla, en la que podemos acceder a la versiónserializadadeunobjetomedianteunacadenaasociada,atravésdeunaestructuraparecidaaundiccionario.La única función que necesitamos conocer del módulo shelve es open, quecuentaconunparámetrofilenamemedianteelqueindicarlarutaaunarchivoenelqueguardarlosobjetos(enrealidadsepuedecrearmásdeunarchivo,connombresbasadosenfilename,peroestoestransparentealusuario).Lafunciónopentambiéncuentaconunparámetroopcionalprotocol,conelqueespecificarelprotocoloquequeremosqueutilicepicklepordebajo.Como resultado de la llamada aopen obtenemos un objetoShelf, con el quepodemos trabajar como si deundiccionarionormal se tratase (a excepcióndeque las claves sólo pueden ser cadenas) para almacenar y recuperar nuestros
objetos.Como un diccionario cualquiera la clase Shelf cuenta con métodos get,has_key,items,keys,values,…Una vez hayamos terminado de trabajar con el objeto Shelf, lo cerraremosutilizandoelmétodoclose.
importshelve
animales=[“piton”,“mono”,“camello”]
lenguajes=[“python”,“mono”,“perl”]
shelf=shelve.open(“datos.dat”)shelf[“primera”]=animales
shelf[“segunda”]=lenguajes
printshelf[“segunda”]
shelf.close()
BASESDEDATOSExisten problemas para los que guardar nuestros datos en ficheros de textoplano,enarchivosXML,omedianteserializaciónconpickleoshelvepuedenser soluciones poco convenientes. En ocasiones no queda más remedio querecurrir a las bases de datos, ya sea por cuestiones de escalabilidad, deinteroperabilidad,decoherencia,deseguridad,deconfidencialidad,etc.AlolargodeestecapítuloaprenderemosatrabajarconbasesdedatosenPython.Sinembargoseasumenunaseriedeconocimientosbásicos,comopuedeserelmanejo elemental de SQL. Si este no es el caso, existen miles de recursos adisposición del lector en Internet para introducirse en el manejo de bases dedatos.
DBAPI
Existen cientos de bases de datos en el mercado, tanto comerciales comogratuitas.Tambiénexistendecenasdemódulosdistintosparatrabajarcondichasbases de datos en Python, lo que significa decenas de APIs distintas poraprender.EnPython,comoenotroslenguajescomoJavaconJDBC,existeunapropuestadeAPIestándarparaelmanejodebasesdedatos,de formaqueelcódigoseaprácticamente igual independientemente de la base de datos que estemosutilizandopordebajo.EstaespecificaciónrecibeelnombredePythonDatabaseAPIoDB-APIyserecogeenelPEP249(http://www.python.org/dev/peps/pep-0249/).DB-API se encuentra en estos momentos en su versión 2.0, y existenimplementacionesparalasbasesdedatosrelacionalesmásconocidas,asícomoparaalgunasbasesdedatosnorelacionales.A lo largo de este capítulo utilizaremos la base de datos SQLite para losejemplos, ya que no se necesita instalar y ejecutar un proceso servidorindependiente con el que se comunique el programa, sino que se trata de unapequeñalibreríaenCqueseintegraconlaaplicaciónyquevieneincluidaconPythonpordefectodesdelaversión2.5.DesdelamismaversiónPythontambiénincorpora un módulo compatible con esta base de datos que sigue laespecificacióndeDBAPI2.0:sqlite3,porloquenonecesitaremosningúntipodeconfiguraciónextra.Nada impide al lector, no obstante, instalar y utilizar cualquier otra base dedatos,comoMySQL,conlacualpodemostrabajaratravésdeldrivercompatibleconDBAPI2.0MySQLdb(http://mysql-python.sourceforge.net/).
VariablesglobalesAntesdecomenzaratrabajarconsqlite3,vamosaconsultaralgunosdatosinteresantessobreelmódulo.TodoslosdriverscompatiblesconDB-API2.0debentener3variablesglobalesquelosdescriben.Asaber:
apilevel:unacadenaconlaversióndeDBAPIqueutiliza.Actualmentesólo puede tomar como valor “1.0” o “2.0”. Si la variable no existe se
asumequees1.0.threadsafety:setratadeunenterode0a3quedescribeloseguroqueeselmóduloparaelusoconthreads.Sies0nosepuedecompartirelmóduloentrethreadssinutilizaralgúntipodemecanismodesincronización;sies1,pueden compartir el módulo pero no las conexiones; si es 2, módulos yconexiones pero no cursores y, por último, si es 3, es totalmente thread-safe.paramstyle: informasobrelasintaxisautilizarparainsertarvaloresenlaconsultaSQLdeformadinámica.
qmark:interrogaciones.sql=“selectallfromtwherevalor=?”
numeric:unnúmeroindicandolaposición.sql=“selectallfromtwherevalor=:1”
named:elnombredelvalor.sql=“selectallfromtwherevalor=:valor”
format:especificadoresdeformatosimilaresalosdelprintfdeC.sql=“selectallfromtwherevalor=%s”
pyformat:similaralanterior,peroconlasextensionesdePython.sql=“selectallfromtwherevalor=%(valor)”
Veamoslosvalorescorrespondientesasqlite3:>>>importsqlite3asdbapi>>>printdbapi.apilevel2.0>>>printdbapi.threadsafety1>>>printdbapi.paramstyleqmark
ExcepcionesA continuación podéis encontrar la jerarquía de excepciones que deben
proporcionarlosmódulos,juntoconunapequeñadescripcióndecadaexcepción,amododereferencia.
StandardError
|__Warning
|__Error
|__InterfaceError
|__DatabaseError
|__DataError
|__OperationalError
|__IntegrityError
|__InternalError
|__ProgrammingError
|__NotSupportedError
StandardError:SuperclaseparatodaslasexcepcionesdeDBAPI.Warning:Excepciónqueselanzaparaavisosimportantes.Error:Superclasedeloserrores.InterfaceError:Erroresrelacionadosconlainterfazdelabasededatos,ynoconlabasededatosensí.DatabaseError:Erroresrelacionadosconlabasededatos.DataError: Errores relacionados con los datos, como una división entrecero.OperationalError:Erroresrelacionadosconelfuncionamientodelabasededatos,comounadesconexióninesperada.IntegrityError:Erroresrelacionadosconlaintegridadreferencial.InternalError:Errorinternodelabasededatos.ProgrammingError: Errores de programación, como errores en el códigoSQL.NotSupportedError:Excepciónqueselanzacuandosesolicitaunmétodoquenoestásoportadoporlabasededatos.
UsobásicodeDB-APIPasemosahoraavercómotrabajarconnuestrabasededatosatravésdeDB-API.
Lo primero que tendremos que hacer es realizar una conexión con elservidorde labasededatos.Estosehacemediante la funciónconnect,cuyosparámetros no están estandarizados y dependen de la base de datos a la queestemosconectándonos.
En el casode sqlite3 sólonecesitamospasar comoparámetrouna cadenaconlarutaalarchivoenelqueguardarlosdatosdelabasededatos,obienlacadena “:memory:” para utilizar la memoria RAM en lugar de un fichero endisco.
Por otro lado, en el caso deMySQLdb, connect toma como parámetros lamáquinaenlaquecorreelservidor(host),elpuerto(port),nombredeusuarioconelqueautenticarse (user),contraseña(password)ybasededatosa laqueconectarnosdeentrelasqueseencuentranennuestroSGBD(db).
LafunciónconnectdevuelveunobjetodetipoConnectionquerepresenta
laconexiónconelservidor.>>>bbdd=dbapi.connect(“bbdd.dat”)>>>printbbdd
<sqlite3.Connectionobjectat0x00A71DA0>
Las distintas operaciones que podemos realizar con la base de datos serealizanatravésdeunobjetoCursor.Paracrearesteobjetoseutilizaelmétodocursor()delobjetoConnection:c=bbdd.cursor()
Las operaciones se ejecutan a través del método execute de Cursor,pasandocomoparámetrounacadenaconelcódigoSQLaejecutar.
Comoejemplocreemosunanuevatablaempleadosenlabasededatos:c.execute(“““createtableempleados(dnitext,nombretext,departamentotext)”””)yacontinuación,insertemosunatuplaennuestranuevatabla:c.execute(“““insertintoempleados
values(‘12345678-A’,‘ManuelGil’,‘Contabilidad’)”””)Sinuestrabasededatossoportatransacciones,siestasestánactivadas,ysi
la característica de auto-commit está desactivada, será necesario llamar almétodo commit de la conexión para que se lleven a cabo las operacionesdefinidasenlatransacción.
Si en estas circunstancias utilizáramos una herramienta externa paracomprobarelcontenidodenuestrabasededatossinhacerprimeroelcommitnosencontraríamosentoncesconunabasededatosvacía.
SicomprobáramoselcontenidodelabasededatosdesdePython,sincerrarelcursornilaconexión,recibiríamoselresultadodelcontextodelatransacción,porloquepareceríaquesehanllevadoacaboloscambios,aunquenoesasí,yloscambiossóloseaplican,comocomentamos,alllamaracommit.
Para bases de datos que no soporten transacciones el estándar dicta quedebeproporcionarseunmétodocommitconimplementaciónvacía,porloquenoes mala idea llamar siempre a commit aunque no sea necesario para podercambiardesistemadebasededatosconsolomodificarlalíneadelimport.
Si nuestra base de datos soporta la característica de rollback tambiénpodemoscancelarlatransacciónactualcon:
bbdd.rollback()
Silabasededatosnosoportarollbackllamaraestemétodoproduciráunaexcepción.
Veamosahoraunejemplocompletodeuso:
importsqlite3asdbapi
bbdd=dbapi.connect(“bbdd.dat”)cursor=bbdd.cursor()
cursor.execute(“““createtableempleados(dnitext,nombretext,departamentotext)”””)
cursor.execute(“““insert into empleados values (‘12345678-A’, ‘ManuelGil’,‘Contabilidad’)”””)bbdd.commit()
cursor.execute(“““select * from empleados wheredepartamento=’Contabilidad’”””)fortuplaincursor.fetchall():printtupla
Comovemos,para realizarconsultasa labasededatos tambiénseutilizaexecute. Para consultar las tuplas resultantes de la sentencia SQL se puedellamar a los métodos de Cursor fetchone, fetchmany o fetchall o usar elobjetoCursorcomouniterador.
cursor.execute(“““select*fromempleadoswheredepartamento=’Contabilidad’”””)for
resultadoincursor:printtupla
Elmétodofetchone devuelve la siguiente tupla del conjunto resultado oNone cuando no existenmás tuplas, fetchmany devuelve el número de tuplasindicadoporelenteropasadocomoparámetroobienelnúmeroindicadoporelatributoCursor.arraysizesinosepasaningúnparámetro(Cursor.arraysizevale1pordefecto)yfetchalldevuelveunobjetoiterablecontodaslastuplas.
A la hora de trabajar con selects u otros tipos de sentencias SQL esimportante tener en cuenta que no deberían usarse los métodos de cadenahabituales para construir las sentencias, dado que esto nos haría vulnerables aataquesdeinyecciónSQL,sinoqueensulugardebeusarselacaracterísticadesustitucióndeparámetrosdeDBAPI.
Supongamos que estamos desarrollando una aplicación web con Pythonparaunbancoyquesepudieraconsultarunalistadesucursalesdelbancoenunaciudad determinada con una URL de la formahttp://www.mibanco.com/sucursales?ciudad=Madrid Podríamos tener unaconsultacomoesta:
cursor.execute(“““select*fromsucursaleswhereciudad=’”+ciudad+“’”””)
Aprimeravistapodríaparecerquenoexisteningúnproblema:nohacemosmásqueobtener las sucursalesque seencuentrenen la ciudad indicadapor lavariableciudad.Pero,¿quéocurriríasiunusuariomalintencionadoaccedieraa
unaURLcomo“http://www.mibanco.com/sucursales?ciudad=Madrid’;SELECT*FROMcontrasenyas”?
Como no se realiza ninguna validación sobre los valores que puedecontener la variable ciudad, sería sencillo que alguien pudiera hacerse con elcontroltotaldelaaplicación.
Locorrectosería,comodecíamos,utilizarlacaracterísticadesustitucióndeparámetros de DB API. El valor de paramstyle para el módulo sqlite3 eraqmark.Estosignificaquedebemosescribirunsignodeinterrogaciónenellugaren el que queramos insertar el valor, y basta pasar un segundo parámetro aexecuteenformadesecuenciaomappingconlosvaloresautilizarparaqueelmódulocreelasentenciapornosotros.
cursor.execute(“““select*fromsucursaleswhereciudad=?”””,(ciudad,))
Porúltimo,alfinaldelprogramasedebecerrarelcursorylaconexión:cursor.close()bbdd.close()
TiposSQLEn ocasiones podemos necesitar trabajar con tipos de SQL, y almacenar,
porejemplo, fechasuhorasusandoDateyTimeynoconcadenas.LaAPIdebasesdedatosdePythonincluyeunaseriedeconstructoresautilizarparacrearestostipos.Estosson:
Date(year,month,day):Paraalmacenarfechas.Time(hour,minute,second):Paraalmacenarhoras.Timestamp(year,month,day,hour,minute,second):Paraalmacenartimestamps(unafechaconsuhora).DateFromTicks(ticks):Paracrearunafechaapartirdeunnúmeroconlossegundos transcurridos desde el epoch (el 1 de Enero de 1970 a las00:00:00GMT).TimeFromTicks(ticks):Similaralanterior,parahorasenlugardefechas.TimestampFromTicks(ticks):Similaralanterior,paratimestamps.Binary(string):Valorbinario.
OtrasopcionesPor supuesto no estamos obligados a utilizar DB-API, ni bases de datosrelacionales. En Python existen módulos para trabajar con bases de datosorientadas a objetos, como ZODB (Zope Object Database) y motores paramapeoobjeto-relacional(ORM)comoSQLAlchemy,SQLObjectoStorm.Además,siutilizamosIronPythonenlugardeCPythontenemoslaposibilidaddeutilizar lasconexionesabasesdedatosde .NET,ysiutilizamosJython, lasdeJava.
DOCUMENTACIÓN
DocstringsEncapítulosanterioresyacomentamosenvariasocasionesquetodoslosobjetoscuentanconunavariableespecial__doc__mediantelaqueindicarelpropósitoyusodelobjeto.Estossonlosllamadosdocstringsocadenasdedocumentación.A estos atributos se les puede asociar el texto correspondiente explícitamente,asignándoloalliteralcadenacorrespondiente,comoconcualquierotravariable.Sin embargo, por conveniencia, Python ofrece un mecanismo mucho mássencilloyesquesielprimerestamentodeladefinicióndelobjetoesunacadena,estaseasociaalavariable__doc__automáticamente.
defhaz_algo(arg):
“““Esteeseldocstringdelafuncion.”””
printarg
printhaz_algo.__doc__
haz_algo.__doc__=“““Esteesunnuevodocstring.”””
printhaz_algo.__doc__Como vemos lo interesante de estas cadenas es que, a diferencia de los
comentarios normales de Python y de los comentarios de otros lenguajes, lascadenas de documentación no se eliminan del bytecode, por lo que se puedenconsultar en tiempo de ejecución, usando, por ejemplo, la función help dellenguaje,outilizandolasentenciaprintcomoenelejemploanterior.
>>>help(haz_algo)Helponfunctionhaz_algoinmodule__main__:haz_algo(arg)
Esteesunnuevodocstring.
PydocLa función help, que comentamos brevemente con anterioridad, utiliza elmódulo pydoc para generar la documentación de un objeto a partir de sudocstringy losdocstringsdesusmiembros.Estemódulo, incluidopordefectoconPythondesdelaversión2.1,sepuedeimportarennuestrocódigoPythonyutilizarseprogramáticamente,obiensepuedeutilizarcomounaherramientadelíneadecomandosque sería el equivalente a la aplicación JavadocdelmundoJava.pydocpuedemostrarlainformacióncomotextoenlaconsola,talcomoloutilizahelp, pero tambiénpuedegenerar archivosHTMLcomo javadoco facilitar lainformaciónatravésdeunpequeñoservidorwebincluidoconelmódulo.Pydocesmuysencillodeutilizar.Con
pydoc.pynombre1[nombre2...]
semuestraladocumentacióndeltema,módulo,clase,paquete,funciónopalabraclave indicada de forma similar a la funciónhelp. Si el nombre eskeywords,
topics o modules se listarán las distintas palabras claves, temas y módulosrespectivamente.Si se pasa el flag -w, el script guardará la documentación en uno o variosarchivoshtmlenlugardemostrarlaporpantalla.
pydoc.py-wnombre1[nombre2...]
Elflag-ksirveparabuscarunadeterminadapalabraenlassinopsisdetodoslosmódulos disponibles. La sinopsis es la primera línea de la cadena dedocumentación.
pydoc.py-kxml
Con-ppodemosiniciarelservidorHTTPenelpuertoindicado.pydoc.py-ppuerto
Unavezhechoestopodemosaccederaladocumentacióndetodoslosmódulosdisponiblesabriendolapáginahttp://localhost:puertoennuestronavegador.Porúltimo,medianteelflag-gpodemoslanzarunainterfazgráficaparabuscardocumentaciónqueutilizaelservidorHTTPparamostrarlosresultados.
EpydocyreStructuredTextElproblemadepydoc esqueesmuysimple,ynopermiteañadir semánticaomodificarestilosdeladocumentación.Nopodemos,porejemplo,indicarqueenuna línea en concreto de entre las líneas de documentación de la funcióndescribeunparámetrodelafunciónomostrarunciertotérminoencursiva.Existen proyectos para generar documentación con funcionalidades másavanzadas como Docutils, Epydoc o Sphinx, aunque es necesario aprendersintaxisespeciales.Docutils es un proyecto desarrollado porDavidGoodger que incluye distintasherramientas para generar documentación utilizando el formatoreStructuredText,unformatodetextoplanocreadoporelmismoautor,yqueesel formatomás utilizado en la comunidad Python. reStructuredText se utiliza,entreotros,paralacreacióndelosPEPs(PythonEnhancementProposals).Sinembargo,actualmenteDocutilsesmásindicadoparagenerardocumentosapartirdearchivosdetexto,ynoapartirdedocstringsextraídosdecódigofuente
Python, ya que el parser encargado de este trabajo dista mucho de estarterminado.EpyDocesunadelasherramientasdegeneracióndedocumentaciónparaPythonmásutilizadas.Ademásdetextoplanoydesupropioformato,llamadoepytext,soporta reStructuredText y sintaxis Javadoc, cosa que los programadores Javaagradecerán.AlolargodelrestodelcapítuloutilizaremosreStructuredTextcomolenguajedemarcadoyEpyDocparagenerarlosdocumentosfinales.EpydocsepuededescargardesdesupáginawebenformadeinstaladorexeparaWindows,paqueteRPMparaFedoraosimilares,oenarchivoszipytar.gzqueincluyen scripts de instalación: http://epydoc.sourceforge.net/. También seencuentraenlosrepositoriosdevariasdistribucionesLinux.UnavezhayamosinstaladoEpydocsiguiendoelmétodoadecuadoparanuestrosistemaoperativotendremosaccesoasufuncionalidadatravésdedosinterfacesdeusuariodistintas:elscriptepydoc,queconsisteenunaaplicacióndelíneadecomandos, y el script epydocgui (epydoc.pyw en Windows), que ofrece unainterfazgráfica.Ademástambiénpodemosaccederalafuncionalidaddeepydocprogramáticamente,comoenelcasodepydoc.Vamos a crear un pequeño módulo con un par de clases para ver primero elresultado de utilizarepydoc con docstrings de texto plano, sin ningún tipo demarcadoespecial.
“““Moduloparaejemplificarelusodeepydoc.”””
classPersona:
“““Miclasedeejemplo.”””
def__init__(self,nombre):
“““InicializadordelaclasePersona.”””
self.nombre=nombre
self.mostrar_nombre()
defmostrar_nombre(self):
“““Imprimeelnombredelapersona”””
print“Estaeslapersona%s”%self.nombre
classEmpleado(Persona):“““SubclasedePersona.”””pass
if__name__==“__main__”:raul=Persona(“Raul”)El formato de salida por defecto de epydoc es HTML. Por lo tanto para
generarladocumentaciónenformadedocumentosHTMLbastaríaescribiralgo
así:epydocejemplo.pyobienepydoc --html ejemplo.py Para generar un archivo PDF, utilizando LaTeX, se
utilizaría el flag --pdf: epydoc --pdf ejemplo.py Si LaTeX no está instalado o
epydocnoencuentraelejecutablenoseráposiblegenerarelPDF.
Tambiénpodemos indicar el nombre del proyecto y laURLmediante lasopciones --name y --url: epydoc --name Ejemplo --url http://mundogeek.netejemplo.pyEinclusoañadirdiagramasmostrandolaclasebaseysubclases(--graph classtree), las llamadas entre funciones y métodos (--graphcallgraph), clases y subclases usando notación UML (--graph uml-
classtree)otodosellos(--graphall).epydoc --graph all ejemplo.py Para generar el grafo de llamadas, no obstante, es
necesario generar un archivo con la información necesaria utilizando el módulo
profileoelmódulohotshoteindicarelarchivoresultanteutilizandoelflag--
pstat: epydoc --graph all --pstat profile.out ejemplo.py Veamos ahora algunas
funcionalidadesbásicasdemarcadoenreStructuredText.
Paraponeruntextoenitálicaserodeaeltextoconasteriscos:*itálica*->itálicaParaponerloennegrita,seutilizandosasteriscos:**negrita**->negritaPara mostrar el texto como monoespacio, por ejemplo para mostrar códigoinline,seutiliza“.
“monoespacio”->monoespacioSinecesitamosutilizarcualquieradeestoscaracteres
especiales,sepuedenescaparutilizandolabarrainvertida.
\* es un carácter especial -> * es un carácter especial Los títulos se crean
añadiendo una línea de caracteres no alfanuméricos por debajo del texto, o por
encima y por debajo del texto. Para crear un subtitulo basta utilizar una nueva
combinación.
Título
======
Subtitulo
---------
TítuloSubtitulo
Paracrearunalistanoordenadaseempiezacadalíneaconelcarácter‘*’,‘-’o‘+’:
*Python*C*Java
Python
CJava
Paracrearunalistanumeradaseempiezalalíneaconelnúmeroseguidodeun punto, o bien con el símbolo ‘#’ para que se introduzca el númeroautomáticamente.
1.Python
2.C
3.Java
1. Python2. C3. Java
Paradescribirpropiedadesdeloselementosqueestamosdocumentandoseutilizanloscamposofields.EnreStructuredTextloscamposcomienzancon‘:’,le sigue el nombredel campoyopcionalmente sus argumentos, y se cierra denuevocon‘:’,paraterminarconelcuerpodelcampo.
EstossonalgunosdeloscamposquesoportaEpydoc:
Funcionesymétodos:paramp:Unparámetro Describeelparámetrop.:typep:str Especificaeltipoesperadoparaelparámetrop.:return:Truesisoniguales
Valorderetorno.
:rtype:str Tipodelvalorderetorno.:keywordp:Unparámetro
Descripcióndelparámetroconvalorpordefectoynombrep.
:raisee:Sielparámetroescero
Describelascircunstanciasparalasqueselanzalaexcepcióne.
Variables:ivarv:Unavariable Descripcióndelainstanciav.:cvarv:Unavariable Descripcióndelavariableestáticadeclasev.:varv:Unavariable Descripcióndelavariablevdelmódulo.:typev:str Tipodelavariablev.
Notas:note:Unanota Unanotasobreelobjeto.:attention:Importante Unanotaimportantesobreelobjeto.:bug:Nofuncionaparaelvalor0 Descripcióndeunerrorenelobjeto.:warning:Cuidadoconelvalor0 Unaadvertenciaacercadeunobjeto.:see:Ver‘Pythonparatodos’ Paraindicarinformaciónrelacionada.
Estado:version:1.0 Versiónactualdelobjeto.:change:Versióninicial Listadodecambios.:todo:Internacionalización Uncambioplaneadoparaelobjeto.:status:Versiónestable Estadodelobjeto.
Autoría:author:RaulGonzalez Autoroautoresdelobjeto.:organization:Mundogeek Organizaciónquecreóomantieneelobjeto.:license:GPL Licenciadelobjeto.:contact:zootropoengmail Informacióndecontactodelautor.
Para que Epydoc sepa que utilizamos reStructuredText es necesarioindicarlomedianteunavariable__docformat__enelcódigo,obienmediantelaopción--docformatdelíneadecomandos.Lasopcionesposiblessonepytext,plaintext,restructuredtextojavadoc.
Veamosunejemploconcampos:“““Moduloparaejemplificarelusode*epydoc*.
:author:RaulGonzalez:version:0.1”””
__docformat__=“restructuredtext”
classPersona:“““Modelaunapersona.”””def__init__(self,nombre,edad):
“““Inicializadordelaclase`Persona`.:paramnombre:Nombredelapersona.
:paramedad:Edaddelapersona”””self.nombre=nombreself.edad=edadself.mostrar_nombre()
defmostrar_nombre(self):“““Imprimeelnombredelapersona”””print“Estaeslapersona%s”%self.nombre
classEmpleado(Persona):“““Subclasede`Persona`correspondientealaspersonasquetrabajanparalaorganizacion.:todo:Escribirimplementacion.”””
passif__name__==“__main__”:
juan=Persona(“Juan”,26)reStructuredText tambiénsoportaunsegundo tipodecamposenelqueel
cuerpodel campoesuna lista.Deesta formapodemos,por ejemplo,describirtodoslosparámetrosdeunafunciónométodoconunsolocampo:Parameters:,enlugardeconuncampo:param:paracadaparámetro.
classPersona:
“““Modelaunapersona.”””
def__init__(self,nombre,edad):
“““Inicializadordelaclase`Persona`.
:Parameters:
-`nombre`:Nombredelapersona.
-`edad`:Edaddelapersona.
”””
self.nombre=nombreself.edad=edad
self.mostrar_nombre()
Otros campos que admiten listas son :Exceptions: para indicar variasexcepciones(:except:),:Variables:paracomentarvariasvariables(:var:)o:Ivariables:paracomentarvariasinstancias(:ivar:).
PRUEBASParaasegurarenlamedidadeloposibleelcorrectofuncionamientoylacalidaddel software se suelen utilizar distintos tipos de pruebas, comopueden ser laspruebasunitarias,laspruebasdeintegración,olaspruebasderegresión.Alolargodeestecapítulonoscentraremosenlaspruebasunitarias,mediantelasquesecompruebaelcorrectofuncionamientodelasunidadeslógicasenlasquesedivideelprograma,sintenerencuentalainterrelaciónconotrasunidades.La solución más extendida para las pruebas unitarias en el mundo Python esunittest, a menudo combinado con doctest para pruebas más sencillas.AmbosmódulosestánincluidosenlalibreríaestándardePython.
DoctestComoesdesuponerporelnombredelmódulo,doctestpermitecombinar laspruebas con la documentación. Esta idea de utilizar las pruebas unitarias paraprobarelcódigoy tambiénamododedocumentaciónpermiterealizarpruebasdeformamuysencilla,propiciaelquelaspruebassemantenganactualizadas,ysirve a modo de ejemplo de uso del código y como ayuda para entender supropósito.Cuando doctest encuentra una línea en la documentación que comienza con‘>>>’ se asume que lo que le sigue es código Python a ejecutar, y que larespuestaesperadaseencuentraenlalíneaolíneassiguientes,sin>>>.Eltextodelapruebaterminacuandoseencuentraunalíneaenblanco,ocuandosellegaalfinaldelacadenadedocumentación.Tomemos como ejemplo la siguiente función, que devuelve una lista con loscuadradosdetodoslosnúmerosquecomponenlalistapasadacomoparámetro:
defcuadrados(lista):
“““Calculaelcuadradodelosnumerosdeunalista”””
return[n**2forninlista]
Podríamoscrearunapruebacomolasiguiente,enlaquecomprobamosqueelresultadoalpasarlalista[0,1,2,3]eselqueesperábamos:
defcuadrados(lista):“““Calculaelcuadradodelosnumerosdeunalista>>>l=[0,1,2,3]>>>cuadrados(l)[0,1,4,9]”””
return[n**2forninlista]Loquehacemosenesteejemploesindicaradoctestquecreeunlistalcon
valor[0,1,2,3], que llame a continuación a la funcióncuadrados con lcomoargumento,yquecompruebequeelresultadodevueltoseaiguala[0,1,4,9].
Paraejecutarlaspruebasseutilizalafuncióntestmoddelmódulo,alaqueselepuedepasaropcionalmenteelnombredeunmóduloaevaluar(parámetroname).Enelcasodequenoseindiqueningúnargumento,comoenestecaso,seevalúaelmóduloactual:
defcuadrados(lista):“““Calculaelcuadradodelosnumerosdeunalista>>>l=[0,1,2,3]>>>cuadrados(l)[0,1,4,9]”””
return[n**2forninlista]
def_test():importdoctestdoctest.testmod()
if__name__==“__main__”:_test()En el caso de que el código no pase alguna de las pruebas que hemos
definido, doctest mostrará el resultado obtenido y el resultado esperado. Encasocontrario,sitodoescorrecto,nosemostraráningúnmensaje,amenosqueañadamos la opción -v al llamar al script o el parámetro verbose=True a lafunción testmod, en cuyo caso se mostrarán todas las pruebas ejecutadas,independientementedesiseejecutaronconéxito.
Esteseríaelaspectodelasalidadedoctestutilizandoelparámetro-v:Trying:
l=[0,1,2,3]
ExpectingnothingokTrying:
cuadrados(l)Expecting:[0,1,4,9]
ok2itemshadnotests:__main____main__._test1itemspassedalltests:2testsin__main__.cuadrados2testsin3items.2passedand0failed.Testpassed.Ahoravamos a introducir un error en el códigode la funciónparaver el
aspecto de un mensaje de error de doctest. Supongamos, por ejemplo, quehubiéramos escrito un operador de multiplicación (‘*’) en lugar de uno deexponenciación(‘**’):
defcuadrados(lista):“““Calculaelcuadradodelosnumerosdeunalista>>>l=[0,1,2,3]>>>cuadrados(l)[0,1,4,9]“““
return[n*2forninlista]
def_test():importdoctestdoctest.testmod()
if__name__==“__main__”:_test()Obtendríamosalgoparecidoaesto:****************************************File“ejemplo.py”,line5,in__main__.cuadradosFailedexample:
cuadrados(l)Expected:[0,1,4,9]
Got:[0,2,4,6]
****************************************1 items had failures: 1 of 2 in __main__.cuadrados ***Test Failed*** 1
failures.Comovemos,elmensajenosindicaquehafalladolapruebadelalínea5,
al llamar a cuadrados(l), cuyo resultado debería ser [0, 1, 4, 9], y sinembargoobtuvimos[0,2,4,6].
Veamosporúltimocómoutilizar sentenciasanidadasparahacer cosasunpoco más complicadas con doctest. En el ejemplo siguiente nuestra funcióncalculael cuadradodeunúniconúmeropasadocomoparámetro,ydiseñamosunapruebaquecompruebequeelresultadoeseladecuadoparavariasllamadascondistintosvalores.Lassentenciasanidadascomienzancon“...”enlugarde“>>>”:
defcuadrado(num):“““Calculaelcuadradodeunnumero.
>>>l=[0,1,2,3]>>>forninl:...cuadrado(n)[0,1,4,9]“““
returnnum**2
def_test():importdoctestdoctest.testmod()
if__name__==“__main__”:_test()
unittest/PyUnitunittest,tambiénllamadoPyUnit,formapartedeunafamiliadeherramientasconocidacolectivamentecomoxUnit,unconjuntodeframeworksbasadosenelsoftwareSUnitparaSmalltalk, creadoporKentBeck,unode lospadresde laeXtremeProgramming.Otrosejemplosdeherramientasqueformanpartedeestafamilia son JUnit para Java, creada por el propio Kent Beck junto a ErichGamma,oNUnit,para.NET.Elusodeunittest esmuysencillo.Paracadagrupodepruebas tenemosquecrear una clase que herede de unittest.TestCase, y añadir una serie demétodos que comiencen con test, que serán cada una de las pruebas quequeremosejecutardentrodeesabateríadepruebas.Paraejecutar laspruebas,basta llamara la funciónmain()delmódulo,con loqueseejecutarán todos losmétodoscuyonombrecomiencecon test,enordenalfanumérico.Alejecutarcadaunadelaspruebaselresultadopuedeser:
OK:Lapruebahapasadoconéxito.FAIL: La prueba no ha pasado con éxito. Se lanza una excepciónAssertionErrorparaindicarlo.ERROR: Al ejecutar la prueba se lanzó una excepción distinta deAssertionError.
Enelsiguienteejemplo,dadoqueelmétodoquemodelanuestrapruebanolanzaningunaexcepción,lapruebapasaríaconéxito.
importunittest
classEjemploPruebas(unittest.TestCase):
deftest(self):
pass
if__name__==“__main__”:unittest.main()Enesteotro,sinembargo,fallaría:importunittest
classEjemploPruebas(unittest.TestCase):deftest(self):
raiseAssertionError()
if__name__==“__main__”:unittest.main()Nadanosimpideutilizarcláusulasifparaevaluarlascondicionesquenos
interesen y lanzar una excepción de tipo AssertionError cuando no sea así,pero la claseTestCase cuenta con variosmétodos que nos pueden facilitar latareaderealizarcomprobacionessencillas.Sonlossiguientes:
assertAlmostEqual(first,second,places=7,msg=None):Compruebaque los objetos pasados como parámetros sean iguales hasta el séptimodecimal(oelnúmerodedecimalesindicadoporplaces).assertEqual(first, second, msg=None): Comprueba que los objetospasadoscomoparámetrosseaniguales.assertFalse(expr,msg=None):Compruebaquelaexpresiónseafalsa.assertNotAlmostEqual(first, second, places=7, msg=None):Compruebaquelosobjetospasadoscomoparámetrosnoseanigualeshastaelséptimodecimal(ohastaelnúmerodedecimalesindicadoporplaces).assertNotEqual(first,second,msg=None):Compruebaquelosobjetospasadoscomoparámetrosnoseaniguales.
assertRaises(excClass,callableObj,*args,**kwargs):CompruebaquealllamaralobjetocallableObjconlosparámetrosdefinidospor*argsy**kwargsselanzaunaexcepcióndetipoexcClass.assertTrue(expr,msg=None):Compruebaquelaexpresiónseacierta.assert_(expr,msg=None):Compruebaquelaexpresiónseacierta.fail(msg=None):Fallainmediatamente.failIf(expr,msg=None):Fallasilaexpresiónescierta.failIfAlmostEqual(first,second,places=7,msg=None):Falla si losobjetospasadoscomoparámetros son igualeshastael séptimodecimal (ohastaelnúmerodedecimalesindicadoporplaces).failIfEqual(first, second, msg=None): Falla si los objetos pasadoscomoparámetrossoniguales.failUnless(expr,msg=None):Fallaamenosquelaexpresiónseacierta.failUnlessAlmostEqual(first,second,places=7,msg=None):Fallaamenos que los objetos pasados como parámetros sean iguales hasta elséptimodecimal(ohastaelnúmerodedecimalesindicadoporplaces).failUnlessEqual(first, second, msg=None): Falla a menos que losobjetospasadoscomoparámetrosseaniguales.failUnlessRaises(excClass,callableObj,*args,**kwargs):Fallaamenos que al llamar al objetocallableObj con los parámetros definidospor*argsy**kwargsselanceunaexcepcióndetipoexcClass.
Comovemostodoslosmétodoscuentanconunparámetroopcionalmsgconunmensajeamostrarcuandodichacomprobaciónfalle.
Retomemos nuestra pequeña función para calcular el cuadrado de unnúmero. Para probar el funcionamiento de la función podríamos hacer, porejemplo,algoasí:
importunittest
defcuadrado(num):“““Calculaelcuadradodeunnumero.”””
returnnum**2
classEjemploPruebas(unittest.TestCase):deftest(self):
l=[0,1,2,3]r=[cuadrado(n)forninl]self.assertEqual(r,[0,1,4,9])
if__name__==“__main__”:unittest.main()
PreparacióndelcontextoEnocasionesesnecesarioprepararelentornoenelquequeremosqueseejecutenlaspruebas.Porejemplo,puedesernecesariointroducirunosvalorespordefectoenunabasededatos,crearunaconexiónconunamáquina,crearalgúnarchivo,etc.EstoesloqueseconoceenelmundodexUnitcomotestfixture.
La clase TestCase proporciona un par de métodos que podemossobreescribirparaconstruirydesconstruirelentornoyqueseejecutanantesydespués de las pruebas definidas en esa clase. Estos métodos son setUp() ytearDown().
classEjemploFixture(unittest.TestCase):
defsetUp(self):
print“Preparandocontexto”
self.lista=[0,1,2,3]
deftest(self):
print“Ejecutandoprueba”
r=[cuadrado(n)forninself.lista]
self.assertEqual(r,[0,1,4,9])
deftearDown(self):print“Desconstruyendocontexto”delself.lista
DISTRIBUIRAPLICACIONESPYTHON
Una vez terminemos con el desarrollo de nuestra nueva aplicación esconvenienteempaquetarladeformaqueseasencilloparalosusuariosinstalarla,yparanosotrosdistribuirla.EnPythonexistendosmódulosprincipalesparaestecometido:distutils,queespartedelalibreríaestándaryeraelmétodomásutilizadohastahacepoco,ysetuptools, que extiende la funcionalidad de distutils y es cada vez máspopular.En este capítulo veremos el funcionamiento de ambas herramientas, yterminaremosexplicandocómocrearejecutables.exeparaWindowsapartirdenuestroprogramaenPython.
distutilsTodo programa distribuido con distutils contiene un script llamado porconvención setup.py, que se encarga de instalar la aplicación llamando a lafunciónsetupdedistutils.core.Estafuncióntienemontonesdeargumentos,quecontrolan,entreotrascosas,cómoinstalarlaaplicación.Destinadosadescribirlaaplicacióntenemoslossiguientesargumentos:
name:Elnombredelpaquete.version:Elnúmerodeversión.description:Unalíneadescribiendoelpaquete.long_description:Descripcióncompletadelpaquete.author:Nombredelautordelaaplicación.author_email:Correoelectrónicodelautor.maintainer:Nombre de la persona encargada demantener el paquete, sidifieredelautor.
maintainer_email: Correo de la persona encargada de mantener elpaquete,sidifieredelautor.url:Webdelaaplicación.download_url:Urldelaquedescargarlaaplicación.license:Licenciadelaaplicación
Tambiéntenemosargumentosquecontrolanlosarchivosydirectoriosquedebeninstalarse,comosonpackages,py_modules,scriptsyext_modules.Elparámetroscripts,queesunalistadecadenas,indicaelnombredelmóduloomódulosprincipales,esdecir,losqueejecutaelusuario.Sinuestraaplicaciónconsistiera, por ejemplo, en un solo script ejemplo.py, el código de setup.pypodríatenerunaspectosimilaralsiguiente:fromdistutils.coreimportsetup
setup(name=”Aplicaciondeejemplo”,version=”0.1”,description=”Ejemplo del funcionamiento de distutils”, author=”RaulGonzalez”,author_email=”zootropoengmail”,url=”http://mundogeek.net/tutorial-python/”,license=”GPL”,scripts=[“ejemplo.py”]
)Si hemos escritootrosmódulospara ser utilizadospor el script principal,
estos se indicanmediante el parámetropy_modules.Por ejemplo, supongamosque la aplicación consiste en un script principal ejemplo.py, y un módulo deapoyoapoyo.py:
fromdistutils.coreimportsetup
setup(name=”Aplicaciondeejemplo”,version=”0.1”,description=”Ejemplo del funcionamiento de distutils”, author=”Raul
Gonzalez”,author_email=”zootropoengmail”,url=”http://mundogeek.net/tutorial-python/”,license=”GPL”,scripts=[“ejemplo.py”],py_modules=[“apoyo”])Para instalarpaquetesPython(directoriosquecontienenvariosmódulosy
unarchivo__init__.py)usaríamoselparámetropackages.Siademásdelmóduloejemplo.pyquisiéramosinstalarlospaquetesguiybbdd,porejemplo,haríamosalgoasí:
fromdistutils.coreimportsetup
setup(name=”Aplicaciondeejemplo”,version=”0.1”,description=”Ejemplo del funcionamiento de distutils”, author=”Raul
Gonzalez”,author_email=”zootropoengmail”,url=”http://mundogeek.net/tutorial-python/”,license=”GPL”,scripts=[“ejemplo.py”],packages=[“gui”,“bbdd”])ext_modules, por último, sirve para incluir extensiones que utilice el
programa,enC,C++,Fortran,…Veamosahoracómoseutilizaríaelarchivosetup.pyunavezcreado.Alejecutarelcomandopython setup.py install los módulos y paquetes especificados por py_modules y
packages se instalan en el directorio Lib de Python. Los programas indicados en
scripts,secopianaldirectorioScriptsdePython.
Una vez hemos comprobado que la aplicación se instala correctamente,procedemos a crear archivos mediante los que distribuir la aplicación a losusuarios.Paracreararchivosconelcódigofuenteseutilizalaopciónsdistdesetup.py,quecreapordefectounarchivotar.gzenUnixyunzipenWindows.
python setup.py sdist Sin embargo se puede utilizar --formats para especificar el
formatooformatosquequeramosgenerar
bztar .tar.bz2gztar .tar.gztar .tarzip .zipztar .tar.Z
Paracrearunarchivotar.bz2,untar.gzyunzip,porejemplo,seutilizaríalasiguienteorden:pythonsetup.pysdist--formats=bztar,gztar,zipParagenerarunarchivo de distribución binaria, se usa la opciónbdist: python setup.py bdistLosformatosquesoportabdistsonlossiguientes:
rpm RPMgztar .tar.gz
bztar .tar.bz2ztar .tar.Ztar .tarwininst InstaladorWindowszip .zip
Para crear un archivo rpm y un instalador de Windows, por ejemplo,escribiríamos:pythonsetup.pybdist --formats=wininst,rpmTambiénesposiblecrear otros tipos de archivos de distribución utilizando scripts que extiendendistutils, como es el caso de los paquetes deb mediante el script stdeb(http://stdeb.python-hosting.com/)
setuptoolssetuptools extiende distutils añadiendo una serie de funcionalidades muyinteresantes: introduce un nuevo formato de archivo para distribución deaplicacionesPython llamadoegg, se encargadebuscar todos lospaquetesquedebeninstalarseyañadirlasposiblesdependencias,permiteinstalarpaquetesdePyPIconunsolocomando,etc.Además, como setuptools se basa en distutils, un script de instalación básicoutilizando setuptools esprácticamente igual a su equivalente condistutils.Tansólocambiaríalasentenciadeimportación.
fromsetuptoolsimportsetup
setup(name=”Aplicaciondeejemplo”,
version=”0.1”,
description=”Ejemplo del funcionamiento de distutils”, author=”Raul Gonzalez”,
author_email=”zootropoengmail”,
url=”http://mundogeek.net/tutorial-python/”,license=”GPL”,
scripts=[“ejemplo.py”],
)
ElúnicoinconvenientequepodríamosencontraralusodesetuptoolsesquenoestáincluidopordefectoenPython2.5,aunqueesprobablequeestocambieen próximas versiones debido a su gran uso. Pero los desarrolladores desetuptools han pensado en todo, e incluso esto no debería suponer ningúnproblema,yaqueconunmínimoesfuerzopornuestrapartepodemoshacerquesetuptoolssedescarguee instaleautomáticamenteen lamáquinadelusuariosieste no se encuentra ya en el sistema.Basta distribuir con nuestro paquete unpequeñomóduloextraez_setup.pyquevieneincluidopordefectoconsetuptools(http://peak.telecommunity.com/dist/ez_setup.py) y llamar a la funciónuse_setuptoolsdelmóduloaliniciodesetup.py:
fromez_setupimportuse_setuptoolsuse_setuptools()
fromsetuptoolsimportsetupsetup(name=”Aplicaciondeejemplo”,version=”0.1”,description=”Ejemplo del funcionamiento de distutils”, author=”Raul
Gonzalez”,author_email=”zootropoengmail”,url=”http://mundogeek.net/tutorial-python/”,license=”GPL”,scripts=[“ejemplo.py”],)Veamosahoraconmásdetenimientoalgunosde loscambiosynovedades
queintroducesetuptools.
IntegraciónconPyPIAlestilodeCPANenPerlsetuptoolspermiteinstalardeformafácilysencillalospaquetespertenecientesaPyPI,elÍndicedePaquetesPython(http://pypi.python.org/pypi),asícomosubirnuestrospropiospaquetes.
PyPIcuentaenelmomentodeescribirestaslíneascon4782paquetes,porlo que poder instalar los paquetes de este repositorio con un simple comandosuponeunaayudamuyatenerencuenta.
Instalar un paquete de PyPI es tan sencillo como pasar al comandoeasy_installelnombredelpaqueteainstalar
easy_installdocutils
Searchingfordocutils
Readinghttp://pypi.python.org/simple/docutils/
Readinghttp://docutils.sourceforge.net/
Bestmatch:docutils0.5
Downloadinghttp://prdownloads.sourceforge.net/docutils/
docutils-0.5.tar.gz?download
Processingdocutils-0.5.tar.gz
Running docutils-0.5/setup.py -q bdist_egg --dist-dir /tmp/easy_install-
wUAyUZ/docutils-0.5/egg-dist-tmp-kWkkkv “optparse” module already present; ignoring
extras/optparse.py.
“textwrap”modulealreadypresent;ignoringextras/textwrap.py.
zip_safeflagnotset;analyzingarchivecontents…
docutils.writers.newlatex2e.__init__:modulereferences__file__
docutils.writers.pep_html.__init__:modulereferences__file__
docutils.writers.html4css1.__init__:modulereferences__file__
docutils.writers.s5_html.__init__:modulereferences__file__
docutils.parsers.rst.directives.misc:modulereferences__file__
Adding docutils 0.5 to easy-install.pth file Installing rst2pseudoxml.py script to
/usr/binInstallingrst2html.pyscriptto/usr/binInstallingrst2latex.pyscriptto
/usr/bin Installing rst2s5.py script to /usr/bin Installing rst2newlatex.py script
to /usr/bin Installing rstpep2html.py script to /usr/bin Installing rst2xml.py
scriptto/usr/binInstalled/usr/lib/python2.5/site-packages/docutils-0.5-py2.5.egg
Processingdependenciesfordocutils
Finishedprocessingdependenciesfordocutils
PodersubirnuestrospaquetesaPyPIrequieredeunprocesounpocomáslaborioso. Primero registramos los detalles de nuestra aplicación en PyPImediantelaopciónregisterdelscriptsetup.py,elcualnospreguntarápornuestronombre de usuario, contraseña y correo electrónico si no tenemos cuenta enPyPI,onombredeusuarioycontraseñasinosregistramosanteriormente:
pythonsetup.pyregisterrunningregisterrunningegg_infocreating Aplicacion_de_ejemplo.egg-info writing
Aplicacion_de_ejemplo.egg-info/PKG-INFOwriting top-level names to Aplicacion_de_ejemplo.egg-info/top_level.txt
writing dependency_links to Aplicacion_de_ejemplo.egg-info/dependency_links.txt writing manifest file ‘Aplicacion_de_ejemplo.egg-info/SOURCES.txt’
readingmanifestfile‘Aplicacion_de_ejemplo.egg-info/SOURCES.txt’writingmanifestfile‘Aplicacion_de_ejemplo.egg-info/SOURCES.txt’Weneedtoknowwhoyouare,sopleasechooseeither:1.useyourexisting
login,2.registerasanewuser,3.havetheservergenerateanewpasswordforyou(andemailittoyou),or
4.quitYourselection[default1]:1Username:zootropoPassword:Serverresponse(200):OKIcanstoreyourPyPIloginsofuturesubmissionswillbefaster.(theloginwillbestoredin/home/zootropo/.pypirc)Saveyourlogin(y/N)?
y
Para crear y subir una distribución con el código fuente de nuestraaplicación se utiliza la opción sdist upload: python setup.py sdist uploadTambiénpodríamoscrearysubirunegg(unformatodearchivoparadistribuiraplicaciones Python que veremos en la próxima sección) utilizando la opciónbdist_eggupload:pythonsetup.pybdist_egguploadOcombinarlostrespasosen un solo comando: python setup.py register sdist bdist_egg uploadUna vezsubido el paquete cualquier persona podría instalarlo en su sistema utilizandoeasy_install, de la misma forma que cualquier otro paquete de PyPI:easy_installmi-paquete
EggsLoseggs(huevoeninglés)sonarchivosdeextensión.eggmediantelosque
distribuir aplicaciones en Python. Serían algo así como el equivalente a losarchivos .jar del mundo Java. Son multiplataforma, permiten manejardependencias,ypermiteninstalardistintasversionesdelmismopaquete.
La formamás sencillade instalar aplicacionesdistribuidas comoarchivoseggesmedianteelcomandoeasy_install,elcualcomentamosbrevementeenelpuntoanterioralhablarsobresuusoparainstalarpaquetesdePyPI.Parainstalarunarchivoeggnotenemosmásquepasarleelnombredelarchivoalcomandoeasy_install:easy_installmi-aplicacion.eggobienpodemospasarlelaURLdelaque descargar el egg: easy_install http://mundogeek.net/mi-aplicacion.eggParaconstruir nuestros propios eggs podemos utilizar el comando bdist_egg desetup.py, de forma similar a lamanera en que construíamos paquetes RPM oinstaladores para Windows con distutils: python setup.py bdist_egg OtroscambiosdestacablesUnode loscambiosmás interesanteses la incorporacióndeunnuevoargumentopara la funciónsetup llamadoinstall_requires, queconsiste en una cadena o lista de cadenas que indica los paquetes de los quedependelaaplicación.Sinuestraaplicaciónnecesitaratenerinstaladoelpaqueteapoyo para poder ejecutarse, por ejemplo, escribiríamos lo siguiente:install_requires=[“apoyo”]
Ydeestaforma,easy_installseencargaríadebuscareinstalarelpaquetesifuera necesario, bien en PyPI, o en cualquier otro repositorio indicado por elparámetrodependency_links.
Además podemos especificar que se necesita una versión concreta delpaquete requerido,queseamayoromenorqueunaciertaversión,oquenosetrate de una versión determinada utilizando operadores relacionales (==,!=, <,<=,>,>=):install_requires=[“apoyo>=1.0<2.0”]
También existen argumentos similares para declarar paquetes que deben
instalarse para poder ejecutar el script de instalación (setup_requires), parapoderejecutarlasposiblespruebasincluidasconelpaquete(tests_require)ypara conseguir funcionalidades adicionales (extras_require, que consiste enestecasoenundiccionario).
setuptoolsincluyetambiénatajosútiles,comolafunciónfind_packages()quenosevitatenerquelistartodosycadaunodelospaquetesqueutilizanuestroscriptenelparámetropackages,comoeraelcasodedistutils:
fromez_setupimportuse_setuptools
use_setuptools()
fromsetuptoolsimportsetup,find_packagessetup(name=”Aplicaciondeejemplo”,
version=”0.1”,
description=”Ejemplodelfuncionamientodedistutils”,author=”RaulGonzalez”,
author_email=”zootropoengmail”,
url=”http://mundogeek.net/tutorial-python/”,license=”GPL”,
scripts=[“ejemplo.py”],
packages=find_packages()
)
Crearejecutables.exeTanto en Mac OS como en la mayor parte de las distribuciones Linux elintérpretedePythonestáinstaladopordefecto,porloquelosusuariosdeestossistemas no tienen mayor complicación a la hora de instalar y ejecutaraplicacionesescritasenPython.En el caso de Windows, esto no es así, por lo que sería interesante que losusuarios de este sistema operativo no tuvieran que instalar el intérprete dePython. También sería interesante que nuestro programa consistiera en unarchivo.exeenlugardeunoovariosarchivos.py,parasimplificarlascosas.Todoestolopodemoslogrargraciasapy2exe,unaextensiónparadistutilsque,como su nombre indica, permite crear ejecutables para Windows a partir decódigoPython,yquepermiteejecutarestasaplicacionessinnecesidaddetenerinstaladoelintérpretedePythonenelsistema.Py2exefuncionaexaminandonuestrocódigofuenteenbuscadelosmódulosypaquetes que utilizamos, compilándolos y construyendo un nuevo archivo que
incluyeestosarchivosyunpequeñointérpretedePythonintegrado.Para probar el funcionamiento de py2exe creemos un pequeño programaejemplo.py
print“Soyun.exe”
yelarchivosetup.pycorrespondiente.Loscambiosquetenemosquerealizarasetup.py son sencillos: importar py2exe, y utilizar los argumentos console ywindows para indicar elnombredel scripto scriptsquequeramosconvertir enejecutablesdeconsolaoejecutablesdeinterfazgráfica,respectivamente.
fromdistutils.coreimportsetupimportpy2exe
setup(name=”Aplicaciondeejemplo”,
version=”0.1”,
description=”Ejemplodelfuncionamientodedistutils”,author=”RaulGonzalez”,
author_email=”zootropo en gmail”, url=”http://mundogeek.net/tutorial-python/”,
license=”GPL”,
scripts=[“ejemplo.py”],
console=[“ejemplo.py”]
)Paracrearelejecutable,utilizamosunanuevaopcióndelíneadecomandos
para setup.pydisponible tras importar elmóduloy llamada, cómono,py2exe:python setup.pypy2exeConestopy2exegeneraráundirectoriobuild, con laslibrerías compiladas, y un directorio dist, con los archivos que conformannuestraaplicación.
Entre losarchivosquepodemosencontrarendist tendremosunoovariosejecutables con el mismo nombre que los scripts indicados en console ywindows, un archivo python*.dll, que es el intérprete de Python, y un archivolibrary.zip,quecontienevariosarchivospycquesonlosmódulosqueutilizalaaplicacióncompilados.
Siqueremosreducirelnúmerodearchivosadistribuir,podemosutilizarlaopción--bundledepy2exeparaañadiralibrary.ziplasdllylospyd(--bundle2)olasdll,lospydyelintérprete(--bundle1).
pythonsetup.pypy2exe--bundle1
obienpodemosañadirunnuevoargumentooptionsalafunciónsetupqueindiqueelvalorautilizar(opciónbundle_files),deformaquenotengamosqueañadirelflag--bundlecadavezqueusemoselcomandopy2exe:
fromdistutils.coreimportsetupimportpy2exe
setup(name=”Aplicaciondeejemplo”,version=”0.1”,description=”Ejemplo del funcionamiento de distutils”, author=”Raul
Gonzalez”,author_email=”zootropo en gmail”, url=”http://mundogeek.net/tutorial-
python/”,license=”GPL”,scripts=[“ejemplo.py”],console=[“ejemplo.py”],options={“py2exe”:{“bundle_files”:1}})Por último podemos incluso prescindir de library.zip e incrustarlo en el
ejecutableutilizandoelargumentozipfile=Nonefromdistutils.coreimportsetupimportpy2exe
setup(name=”Aplicaciondeejemplo”,
version=”0.1”,
description=”Ejemplodelfuncionamientodedistutils”,author=”RaulGonzalez”,
author_email=”zootropo en gmail”, url=”http://mundogeek.net/tutorial-python/”,
license=”GPL”,
scripts=[“ejemplo.py”],
console=[“ejemplo.py”],
options={“py2exe”:{“bundle_files”:1}},zipfile=None
)
Índiceanalítico
Símbolos__call____cmp____del____doc____init____len____main____name____new____str__
A
archivosatributos
B
basesdedatosboolbreak
C
cadenas,métodoscandadosclasesclasesdenuevoestiloclassclosecolamultihilocolecciones
diccionarioslistastuplas
comentarioscompilecomprensióndelistascondiciones,sincronizacióncontinuecookiescountcPickle
D
dbapidecoradoresdefdiccionario,métodosdistutilsdocstringdocstringsdoctestdocutils
E
eggselifelseencapsulaciónenvepydoceventosexcepcionesexcept
F
Falseficherosfilefilterfinallyfindallfloatfor...inforkfrom...importfuertementetipadofuncionesfuncionesdeordensuperiorfuncioneslambda
G
generadores
GIL
GlobalInterpreterLock
H
hashbanghelpherenciaherenciamúltiplehilos
I
ifimportinputint10.VéaseaquíenterosiPython
J
Jython
K
Komodo
L
lambdalenguajecompiladolenguajedescriptlenguajeinterpretadolenguajesemiinterpretadolistas,métodoslocks
M
mapmarshalmarshallingmatchmétodosmodulemódulosmutex
N
namemanglingNone
O
objetosopenoperacionesrelacionalesoperadoresaniveldebitoperadoresaritméticosoperadoreslógicosorientaciónaobjetos
P
paquetesparámetros,funcionesparámetrosporlíneadecomandoparámetros,valorespordefectoparticionadopasoporreferenciapasoporvalorpatrones(expresionesregulares)picklepolimorfismoprintprocesosprogramaciónfuncionalprogramaciónorientadaaobjetospropiedadespruebaspy2exePyDEVpydocPyPIPyPyPython
definicióninstalaciónventajas
PYTHONPATH
R
raiseraw,cadenaraw_inputreadreadlinereadlinesreducereStructuredTextreturn
S
searchseekselfsemáforosserializaciónsetuptoolssharpbangshebangshelvesincronizaciónslicingsocketssplitsubsubclasesuperclasesys.argv
T
telltestfixtureteststhreadingthreadstipadodinámicotipadofuertetiposbásicos
booleanoscadenasnúmeros
complejosenterosreales
Truetry
U
unittestupperurlliburllib2
V
valoresinmutablesvaloresmutables
W
whileWingIDEwritewritelines
Y
yield
RaúlGonzálezDuqueesun Ingeniero Informáticoaficionadoa losgadgets, laprogramación, Internet, la ciencia ficción, la tecnología, y todo lo relacionadoconelmundogeek.NacidoenToledo, lleva toda lavidaviviendoenMadrid,desdedondesededicaadesarrollaraplicaciones.
TableofContentsPythonparatodosIntroducción
¿QuéesPython?¿PorquéPython?InstalacióndePythonHerramientasbásicas
MiprimerprogramaenPythonTiposbásicos
NúmerosCadenasBooleanos
ColeccionesListasTuplasDiccionarios
ControldeflujoSentenciascondicionalesBucles
FuncionesOrientaciónaobjetos
ClasesyobjetosHerenciaHerenciamúltiplePolimorfismoEncapsulaciónClasesde“nuevo-estilo”Métodosespeciales
RevisitandoobjetosDiccionariosCadenasListas
ProgramaciónfuncionalFuncionesdeordensuperiorIteracionesdeordensuperiorsobrelistasFuncioneslambda
ComprensióndelistasGeneradoresDecoradores
ExcepcionesMódulosypaquetes
MódulosPaquetes
Entrada/salidayficherosEntradaestándarParámetrosdelíneadecomandoSalidaestándarArchivos
ExpresionesregularesPatronesUsandoelmódulore
SocketsInteractuarconwebsThreads
¿Quésonlosprocesosylosthreads?ElGILThreadsenPythonSincronizaciónDatosglobalesindependientesCompartirinformación
SerializacióndeobjetosBasesdedatos
DBAPIOtrasopciones
DocumentaciónDocstringsPydocEpydocyreStructuredText
PruebasDoctestunittest/PyUnit
DistribuiraplicacionesPythondistutilssetuptoolsCrearejecutables.exe
ÍndiceanalíticoAutor