Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Por qué nos gusta Python

El siguiente texto era parte del informe del proyecto final de carrera de Juan José Conti y Cecilia Lorena Puccinelli, titulado Desarrollo Ágil de un Sistema de Gestión de Negocios Inmobiliarios con Software Libre. Finalmente no fue incluido en la versión definitiva del mismo por lo que lo rescatamos en este blog.

Python

Python es un lenguaje de programación de propósito general de alto nivel. Su filosofía de diseño hace énfasis en lograr productividad para los programadores y legibilidad del código.

Fue creado en el año 1990 por Guido Van Rossum y desde entonces ha sido desarrollado como un proyecto del Software Libre por desarrolladores alrededor del mundo.

A menudo es comparado con lenguajes como C, Java, Perl y Lisp, pero tiene muchas características distintivas que marcan su identidad propia:

Intérprete interactivo

Python viene acompañado de un intérprete interactivo o read-eval-print-loop que permite agilizar el desarrollo de programas ya que se pueden probar ideas y rápidamete ver como resultan sin necesidad de escribir un programa entero en el cual envolverla. Otros lenguajes como Smalltalk, Haskell o Scheme también tienen esta herramienta.

El siguiente es un ejemplo sencillo de uso del intérprete interactivo, los comandos ingresados por el usuario están precedidos por >>>:

>>> 1 + 2

3

>>> "hola"

'hola'

>>> nombre = "fabio"

>>> "hola " + nombre

'hola fabio'

Tipos de datos poderosos

Python incluye de forma nativa tipos de datos poderosos muy útiles a la hora de programar. Las listas y tuplas son secuencias de objetos:

lista = [1,2,3, “cuatro”, Cliente()]

tupla = ('u', 1, 3)

Ambos tipos de datos pueden almacenar cualquier tipo de objetos en orden. La principal diferencia entre estos dos tipos de datos es que las listas pueden cambiar en forma dinámica su tamaño mientras que las tuplas son de tamaño fijo (se dice que es un objeto inmutable, como los números o cadenas de texto).

Los diccionarios son una estructura de datos sin orden que permite almacenar valores asociados a una clave para luego poder recuperarlos a partir de esta. Solo objetos inmutables pueden ser clave de un diccionario.

dicc = {'a': 1, 'b': 10}

dicc['a'] obtiene el objeto 1

dicc[3] = ‘tres’ agrega al diccionario el objeto ‘tres’ asociado a la clave 3 .

Espacios en blanco con significado

En Python los bloques se delimitan mediante el uso de indentación en lugar de utilizar llaves:

if carteles == 0:

    print “No hay carteles disponibles.”

else:

    print “Hay %d carteles disponibles.” % carteles

Esto tiene como objetivo forzar una correcta indentación que redunde en una mayor legibilidad del código escrito.

i = 20

while i > 0:

    print i

    i -= 1

Tipado de pato

El nombre tipado de pato surge de la idea: “si un objeto camina como pato y come como pato, entonces debe ser un pato”.

Las restricciones de tipo no son checkeadas en tiempo de compilación, sino que las operaciones sobre los objetos siempre se intentan llevar a cabo y fallan en tiempo de ejecución si el objeto no puede responder al mensaje que se le envió.

Supongamos que definimos la función saludar como sigue:

def saludar(o):

    print “hola %s“ % o.nombre

A esta función no lo interesa el tipo del objeto o, recibido como parámetro, lo único que le importa es que tenga el atributo nombre.

Así, si la variable m referencia un objeto de la clase Mujer y la variable h referencia un objeto de la clase Hombre y ambas clases tienen un atributo llamado nombre, entonces las siguientes llamadas se ejecutarán sin problemas:

saludar(m)

saludar(h)

Si la variable ‘a’ referencia a un objeto de la clase Anonimo y ésta no tiene un atributo llamado nombre, la llamada a:

saludar(a) lanzará una excepción.

Librería estándar amplia

Python viene con las baerías incluidas!

Al igual que Java, Python cuenta con una amplia librería estándar que acompaña al lenguaje. Ésta incluye módulos para manejar expresiones regulares, crear interfaces gráficas, conectarse a bases de datos entre muchos otros.

Ésta es una de sus mayores ventajas y a esto se debe la popular expresión “Python viene con las baterías incluidas”.

Librerías externas

Además de los muchos componentes incluidos en la librería estándar de Python, hemos utilizando algunas librerías externas:

  • PIL: librería para manejo de imágenes.
  • Pysicopg: conector para el motor de bases de datos PostgreSQL.
  • ReportLab: librería para generar documentos PDF.
  • BeautifulSoap: librería para procesar documentos HTML. Su uso es descrito en la sección 1.1.37.
  • Pynum2word: módulo que convierte números en palabras.


Recomendaciones al programar en Python

Esta es una traducción de la última sección del PEP 8 de Python, Recomendaciones al programar:

Programming Recommendations

El código debe ser escrito de forma que no tenga desventajas en otras implementaciones de Python aparte de CPython (PyPy, Jython, IronPython, Pyrex, Psyco).

Por ejemplo, no te bases en la implementación eficiente que tiene CPython de concatenación in-place de strings en sentencias de la forma a+=b o a=a+b. Esas sentencias corren más despacio en Jython. En su lugar la forma ''.join() debe ser usada en bibliotecas, en las partes dónde la performance sea importante. Esto asegura que la concatenación ocurra en tiempo lineal en todas las implementaciones.

La comparación con singletons como None siempre debe hacerse con is o is not, nunca con los operadores de igualdad.

También tené cuidado al escribir if x cuando realmente querés decir if x is not None, por ejemplo al testear si a una variable o argumento que por defecto es None le fue asignado a otro valor. El otro valor puede tener un tipo (por ejemplo, un contenedor) que puede ser falso en un contexto booleado! (NdT: por ejemplo [] o {}).

Usá excepciones basadas en clases

Las excepciones basadas en strings en código nuevo deben desaparecer ya que esta característica del lenguaje fue eliminada en Python 2.6.

Los módulos o paquetes deben definir su propia clase base para exepciones específica de su dominio, la cual debe extender la clase built-in Exception. Siempre incluí un docstring a la clase, por ejemplo:


class MessageError(Exception):

"""Clase base para errores en el paquete mail."""

La convención para el nombramiento de clases se aplica aquí, aunque deberías agregar el sufijo 'Error' a tus clases de excepciones, si la excepción es un error. Las excepciones que no sean errores no necesitan un sufijo especial.

Al lanzar una excepción, usá raise ValueError('mensaje') en lugar de raise ValueError, 'message'.

La forma usando paréntesis es preferida porque cuando los argumentos de la excepción son largos o incluyen formateo de strings, no necesitás usar el carácter de continuación de línea () gracias al paréntesis. La forma vieja será quitada en Python 3000.

Al atrapar excepciones, mencioná la excepción específica cuando puedas, en lugar de usar la clausula except:.

Por ejemplo, usá:


try:

    import platform_specific_module

except ImportError:

    platform_specific_module = None

Una clausula except: atrapará las excepciones SystemExit y KeyboardInterrupt, haciendo que sea más difícil interrumpir al programa con Control-C, y puede traer otros problemas. Si querés atrapar todas las excepciones que señalan errores en el programa, usar except Exception:.

Una buena regla es limitar el uso de la clausula except: a dos casos:

  1. Si el manejador de excepción estará imprimiendo o logeando el traceback; al menos el usuario se dará cuenta de que ocurrió un error.
  2. Si el código necesita hacer algún trabajo de limpieza, pero después deja que la excepción se propague hacia arriba con raise. try...finally es la mejor forma de manejar este caso.

Adicionalmente, para todas las clausulas try/except, limitá la clausula try a la absolutamente mínima cantidad de código necesario. De nuevo, esto evita esconder bugs.

Si:


try:

    value = collection[key]

except KeyError:

    return key_not_found(key)

else:

    return handle_value(value)

No:


try:

    # Too broad!

    return handle_value(collection[key])

except KeyError:

    # Will also catch KeyError raised by handle_value()

    return key_not_found(key)

Usar los métodos de string en lugar del módulo string.

Los métodos de string son siempre más rápidos y comparten la misma API con los strings unicode. Sobreescribí esta regla si necesitás compatibilidad hacia atrás con versiones de Python menores a 2.0.

Usá ''.startswith() y ''.endswith() en lugar de rebanamiento de strings (string slicing) para checkear prefijos y sufijos.

startswith() y endswith() son más limpios y menos propensos a errores. Por ejemplo:

Si: if foo.startswith('bar'):

No: if foo[:3] == 'bar':

La excepción es sin tu código debe funcionar con Python 1.5.2 (pero esperemos que no!).

La comparación entre tipos de objetos debe usar siempre isinstance() en lugar de comparar tipos directamente.

Si: if isinstance(obj, int):

No: if type(obj) is type(1):

Al chequear si un objeto es un string, recordá que hay strings Unicode también! En Python 2.3, str y unicode tienen una clase base común, basestring, así que podés hacer:

if isinstance(obj, basestring):

En Python 2.2, el módulo types tiene el tipo StringTypes definido para ese propósito, por ejemplo:


from types import StringTypes

if isinstance(obj, StringTypes):

En Python 2.0 y 2.1, tenés que hcaer:

from types import StringType, UnicodeType

if isinstance(obj, StringType)

o


isinstance(obj, UnicodeType) :

Para secuencias, (strings, listas, tuplas), hacé uso del hecho de que las secuencias vacías tienen valor de verdad falso:

Si:


if not seq:

if seq:

No:


if len(seq):

if not len(seq):

No escribas strings literales que hagan uso de espacios en blanco al final de una línea. Esos espacios en blanco son visualmente indistingibles y algunos editores (o más recientemente, reindent.py) los eliminan.

No compares valores booleanso con True o False usando ==.

Si:

if greeting:

No:

if greeting == True:

Peor:

if greeting is True:


Terminando Python en Santa Fe 2008

Auditorio de la UTN FRSF, Lighting Talks desarrollándose (5 minutos por disertante para explicar ALGO).

Tuvismos:

    <li>Haciendo plata con Software Libre - John Lenton</li>
    
    <li>Python LEX &amp; YACC - Yo</li>
    
    <li>KSS -  Silvestre Huens</li>
    
    <li>svn +Track en USLA - David Casco</li>
    
    <li>Decoradores en Python -  Facundo Batista</li>
    
    <li>Fabián Ezequiel Gallina - Python-mode para Emacs</li>
    
    <li>Procesando Imágenes con Python - Esteban Peiro</li>
    
    <li>Reply (aprendizaje por refuerzo) - Lucio Torre y Ricardo Kirkner</li>
    
    <li>Defendiendo al Software Libre - Calos Miranda Bonina</li>
    
    <li>Creación colaborativa de empresas -  Leandro Monk</li>
    
    <li>PyWars - <span style="text-decoration: line-through;">Arturo Díaz Santor</span> Arturo Elias Antón</li>
    
    <li>Historia de conferencias de Python / PyCON Ar - Facundo Batista</li>
    

(Si le escribí mal el nombre a alguno, avise!)

Fin del post, el resto del día incluye: sorteos, entega de certificados y luego un sprint gastronómico.

Saludos!

Bonus Track: Una fotito del mediodía


AIMA en Python

Inteligencia Artificial -un enfoque moderno-, el libro del que estoy estudiando tiene muchos de los algorítmos que explica en pseudo código implementados en Python.

Uno de los autores cuenta que originalmente estaban escritos en Lisp, pero muchos estudiantes tenían problemas con este lenguaje. En especial, saltar del pseudo código del libro a una implementación que corra en sus computadoras. El primer intento fue usar Java, pero no tuvieron éxito:

However, one attempt at getting Java code up and running was largely unsuccesful. Then I discovered Python and Jython, and thought this might be a better way to go than Java.

cita


Decoradores en Python (I) - Introducción

Este artículo es el primero de un plan de 3 artículos. Empezamos con una introducción a los decoradores en Python.

Funciones

Cómo todo en Python, las funciones son objetos. La forma más común de crear un objeto de tipo <function> es mediante el keyword def:

def saludo():

    print "Hola"

Al realizar esta definición, el cuerpo de la función es compilado pero no ejecutado y el objeto de tipo <function> es asociado al nombre 'saludo'. Mediante este nombre podemos referirnos al objeto:

>>> saludo

<function saludo at 0xb7d82fb4>

y utilizando la notación de paréntesis podemos llamar a (ejecutar) la función.

>>> saludo()

Hola

Una función puede tener parámetros (los parámetros son nombres a los que podemos referirnos en el cuerpo de la función):

def saludo2(nombre):

    print "Hola %s" % nombre
def saludo3(nombre, apellido):

    print "Hola %s %s" % (nombre, apellido)

Cuando llamamos a la función con argumentos (los argumentos son valores que en principio se asocian uno a uno a los parámetros de la función):

>>> saludo2("Ceci")

Hola Ceci
>>> saludo3("Ceci", "Pucci")

Hola Ceci Pucci

Los últimos n parámetros pueden tener valores por defecto, entonces estas definiciones y sus consiguientes ejecuciones son válidas:

def saludo4(nombre, apellido="Conti"):

    print "Hola %s %s" % (nombre, apellido)
>>> saludo4("Juanjo")

Hola Juanjo Conti
>>> saludo4("Juanjo", "Garau")

Hola Juanjo Garau
def saludo5(nombre="Juanjo", apellido="Conti"):

    print "Hola %s %s" % (nombre, apellido)
>>> saludo5()

Hola Juanjo Conti
>>> saludo5("Mary")

Hola Mary Conti

Los últimos n argumentos pueden ser argumentos nombrados, es decir utilizando el nombre de los parámetros con los que el argumento se debe asociar. En las siguientes ejecuciones se pueden ver ejemplos de esto:

def saludo6(tratamiento, nombre, apellido):

    print "Hola %s %s %s" % (tratamiento, nombre, apellido)
>>> saludo6("Sr.", apellido="Conti", nombre="Juanjo")

Hola Sr. Juanjo Conti
>>> saludo6("Sr.", "Juanjo", apellido="Conti")

Hola Sr. Juanjo Conti

Los parámetros de una función pueden terminar con <nombre> (una tupla con los últimos argumentos posicionales) y/o *<nombre> (un diccionario con los últimos argumentos nombrados).

def saludo7(tratamiento, *args):

    print "Hola %s %s" % (tratamiento, " ".join(args))
>>> saludo7("Sr.", "Juanjo", "Conti")

Hola Sr. Juanjo Conti
>>> saludo7("Sr.", "Juanjo", "Conti", "Garau")

Hola Sr. Juanjo Conti Garau

Notemos que esta forma de definir una función es bastante útil cuando no sabemos el número de argumentos que se recibirán.

La siguiente es la forma más genérica de definir una función:

def saludo8(*args, **kwargs):

    pass

Decoradores

Un decorador es una función 'd' que recibe como argumento otra función 'a' y retorna una nueva función 'b'. La nueva función 'b' es la función 'a' decorada con 'd'.

Supongamos que queremos avisarle a un sistema de seguridad cada vez que se ejecutan las funciones abrir_puerta y cerrar_puerta. Para hacer una simplificación, el aviso simplemente será imprimir por un mensaje en la pantalla. Podemos escribir el siguiente 'decorador':

def avisar(f):

    def inner(*args, **kwargs):

        f(*args, **kwargs)

        print "Se ha ejecutado %s" % f.__name__

    return inner

Las siguientes son las funciones a decorar:

def abrir_puerta():

    print "Abrir puerta"



def cerrar_puerta():

    print "Cerrar puerta"
>>> abrir_puerta()

Abrir puerta

>>> cerrar_puerta()

Cerrar puerta

Y ahora solo nos limitamos a seguir la definción que di al principio de un decorador:

abrir_puerta = avisar(abrir_puerta)

cerrar_puerta = avisar(cerrar_puerta)

Listo!, ambas funciones han sido decoradas:

>>> abrir_puerta()

Abrir puerta

Se ha ejecutado abrir_puerta

>>> cerrar_puerta()

Cerrar puerta

Se ha ejecutado cerrar_puerta

Azúca sintáctica

En Python 2.3, la anterior era la forma de decorar una función. A partir de Python 2.4 se a añadido azúcar sintáctica al lenguaje que nos permite hacer lo mismo de esta forma:

@avisar

def abrir_puerta():

    print "Abrir puerta"



@avisar

def cerrar_puerta():

    print "Cerrar puerta"

Esta es una forma mucho más visual de hacerlo.

Encadenando decoradores

La decoración de funciones puede encadenarse. Para ejemplificarlo vamos a suponer ahora que solo usuarios autenticados en el sistema pueden ejecutar las funciones abrir_puerta y cerrar puerta.

Nuevamente hacemos una simplificación. Existe la variable AUTHENTICATED que indica el estado del usuario actual. Si el usuario no está autenticado y se intente ejecutar alguna de las funciones, una excepción es lanzada.

def autenticado(f):

    def inner(*args, **kwargs):

        if AUTHENTICATED:

            f(*args, **kwargs)

       else:

           raise Exception

    return inner

Luego, la definición de abrir_puerta y cerrar_puerta debería ser:

@autenticado

@avisar

def abrir_puerta():

    print "Abrir puerta"



@autenticado

@avisar

def cerrar_puerta():

    print "Cerrar puerta"

Con AUTHENTICATED = True:

>>> cerrar_puerta()

Cerrar puerta

Se ha ejecutado cerrar_puerta

Pero si AUTHENTICATED = False:

>>> cerrar_puerta()

Traceback (most recent call last):

File "<stdin> ", line 1, in <module>

File "<stdin> ", line 6, in inner

Exception

update: 2° entrega.


Soporte Unicode para pyrtf-ng

pyrtf-ng es un módulo para Python que permite generar archivos en formato RTF en forma dinámica. Esta semana empecé a probarlo con la idea de usarlo para generar documentos de texto con formato (Word-like) en forma dinámica.

Al intentar instalarlo tuve algunos problemas , se los comenté al autor y le sugerí soluciones. Rápidamente estas modificaciones estuvieron hechas en el repositorio del proyecto.

Una vez que tuve la librería instalada y los ejemplos corriendo, empecé a modificar algunos para experimentar un poco. Cuando quise generar un documento que contenga una palabra con tilde (cómo canción), el programa tiró una horrible excepción. Esto hacía que desechara totalmente la idea de usar este software.

Leí sobre la codificación utilizada por el formato RTF y descubrí que no soporta Unicode en forma nativa, pero si mediante una secuencia de escape de la forma: \uxxxx? en dónde xxxx es un entero con signo de 16 bits correspondiente al caracter que se quiere representar. Ejemplo: á.

Pedí instrucciones al autor de la librería, quien me indicó que partes del código tendría que revisar par incluir la funcionalidad de soportar Unicode. Luego de algunas horas empapándome en la arquitectura de clases de pyrtf, tenía un parche listo para ser aplicado e incorporar la funcionalidad. Hoy fue incluido en la versión en desarrollo de la librería.

Código

Un pequeño snippet que muestra el algoritmo básico para pasar de un string Unicode a su representación codificada en RTF:

encoded = ''.join(['\u%s?' % str(ord(b)) for b in base])

Datos

  • pyrtf-ng es mantenido por Duncan Mc Greggor y está basado en PyRTF, un proyecto que ya no es mantenido.
  • Se encuentra en activo desarrollo: modificación del estilo del código y refactoring.
  • También puede ser modificado mediante bazaar en lauchpad.


Serpientes y rubíes

Ja! Levanto el guante del desafío que plantea Gastón en su blog:

juanjo@albus:~$ python

Python 2.5.1 (r251:54863, Mar 7 2008, 03:41:45)

[GCC 4.1.2 (Ubuntu 4.1.2-0ubuntu4)] on linux2

Type "help", "copyright", "credits" or "license" for more information.

1. Dado un array con nombres de persona eliminar los nombre que comienzan con “Pe”:

>>> nombres = ['Pablo', 'Raul', 'Pedro', 'Pepe', 'Ariel', 'TerePe']

>>> [n for n in nombres if not n.startswith("Pe")]

['Pablo', 'Raul', 'Ariel', 'TerePe']

  1. Verificar si el mismo array contiene el nombre “Raul”:

>>> "Raul" in nombres

True

  1. Generar un string con todos los nombres unidos por “-”:

>>> "-".join(nombres)

'Pablo-Raul-Pedro-Pepe-Ariel-TerePe'

  1. Generar un segundo array con los nombres todos en minúsculas ordenado alfabéticamente:

>> sorted([n.lower() for n in nombres])

['ariel', 'pablo', 'pedro', 'pepe', 'raul', 'terepe']

  1. Desordenar el array:

>>> from random import shuffle

>>> shuffle(nombres)

>>> nombres

['Pepe', 'Pedro', 'Ariel', 'Raul', 'Pablo', 'TerePe']

  1. Averiguar si la lista siguiente tiene números pares:

>>> bool([n for n in numeros if n % 2 == 0])

True

  1. Averiguar si toda la lista son números pares:

>>> len(numeros) == len([n for n in numeros if n % 2 == 0])

False

  1. Obtener el producto de una lista de números:

>>> f = lambda x,y: x*y

>>> reduce(f, numeros)

120

  1. Obtener el factorial de 9999:

>>> reduce(f, xrange(1, 10000))

# la respuesta tiene 35656 caracteres.

  1. Averiguar si dos arrays son iguales:

>>> [1,2,3,4] == [1,2,3,4]

True

¿Conclusiones? Creo que las listas por comprensión de la serpiente le gana a los .metodos del rubí. Pero en 6 y 7 perdemos feo :-/ ¿Algún pythonista que reescriba esos ejercicios?


Destapando las cañerías de planet-planet

Palabras claves: planet-planet exception KeyError

Antes de venirme para Pellegrini recibí algunos avisos de que los planetas de PyAr y TecnoFe no se estaban actualizando. Estos dos sitios corren sobre dos instalacions de planet-planet, un agregador de feeds escrito en Python. El programa consiste en un script que cada vez que se ejecuta genera páginas html, actualizando su contenido entre corrida y corrida si corresponde.

Mediante reglas de cron hago que estos scripts corran cada 15 minutos, buscando novedades en los blogs agregados.

Mi primer intento de saber qué estaba pasando fue revisar crontab, pero no encontré nada raro allí. Lo siguiente fue ejecutar a manos los scripts en cuestión. Ambos tiraban exepciones similares:

Traceback (most recent call last):

File "planet.py", line 167, in ?

main()

File "planet.py", line 160, in main

my_planet.run(planet_name, planet_link, template_files, offline)

File "/home/.orithyia/jjconti/pyar.firebirds.com.ar/planet-2.0/planet/init.py", line 240, in run

channel = Channel(self, feed_url)

File "/home/.orithyia/jjconti/pyar.firebirds.com.ar/planet-2.0/planet/init.py", line 527, in init

self.cache_read_entries()

File "/home/.orithyia/jjconti/pyar.firebirds.com.ar/planet-2.0/planet/init.py", line 569, in cache_read_entries

item = NewsItem(self, key)

File "/home/.orithyia/jjconti/pyar.firebirds.com.ar/planet-2.0/planet/init.py", line 845, in init

self.cache_read()

File "/home/.orithyia/jjconti/pyar.firebirds.com.ar/planet-2.0/planet/cache.py", line 74, in cache_read

self._type[key] = self._cache[cache_key + " type"]

File "/usr/lib/python2.3/bsddb/init.py", line 116, in getitem

return self.db[key]

KeyError: 'tag:www.taniquetil.com.ar,2007-08-31:285 author type'

Este software tiene un cache de informacion para guardar los feeds que parsea. Basicamente es un directorio llamado cache con un archivo por cada feed agregado.

Solucioné el problema -más por instinto que por pericia- borrando, para ambos sitios, el archivo correspondiente al blog con problemas. Les dejo el tip por si les sucede en alguna instalación de planet-planet. Prometo que si me vuelve a pasar voy a investigar un poquito más para descubrir la razón del problema :)


Código Secreto (juego)

Una tarde estábamos jugando a este juego en su tablero y me dieron ganas de jugarlo en la compu también. Una búsqueda rápida no nos arrojó resultados y decidí programarlo para practicar mi PyGame. La idea fue no terminar con algo muy complejo, sino más bien simple que pueda programarlo rápido y que sirva también como ejemplo de uso de esta librería.

El juego es un juego lógico: hay que pensar para poder ganar ;-).

Para jugarlo pueden obtener la última versión desde el svn:

svn co http://svn.juanjoconti.com.ar/code/ SecretCode

A pedido de Lucio de PyAr dejo unas capturas de pantalla, espero que alcancen para entender el juego. Si no se entiende, podemos usar los comentarios de este post para aclarar dudas :).

Menú

Secret Code-Menu

Las reglas

Pantallazo-Secret Code-Reglas

Jugando

Secret Code-Jugando

Una jugada ganadora

Secret Code-Ganando

Créditos

Secret Code-Creditos