Inspeccionar el interior de las funciones

PythonPythonBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprenderás cómo explorar los aspectos internos de las funciones de Python. Las funciones en Python son objetos con sus propios atributos y métodos, y comprender estos puede brindar una comprensión más profunda de cómo funcionan las funciones. Este conocimiento te permitirá escribir código más potente y adaptable.

Aprenderás a inspeccionar los atributos y propiedades de las funciones, utilizar el módulo inspect para examinar las firmas de las funciones y aplicar estas técnicas de inspección para mejorar la implementación de una clase. El archivo que modificarás es structure.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/scope("Scope") python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/ModulesandPackagesGroup -.-> python/standard_libraries("Common Standard Libraries") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") subgraph Lab Skills python/function_definition -.-> lab-132511{{"Inspeccionar el interior de las funciones"}} python/arguments_return -.-> lab-132511{{"Inspeccionar el interior de las funciones"}} python/scope -.-> lab-132511{{"Inspeccionar el interior de las funciones"}} python/build_in_functions -.-> lab-132511{{"Inspeccionar el interior de las funciones"}} python/standard_libraries -.-> lab-132511{{"Inspeccionar el interior de las funciones"}} python/classes_objects -.-> lab-132511{{"Inspeccionar el interior de las funciones"}} end

Explorando los atributos de las funciones

En Python, las funciones se consideran objetos de primera clase. ¿Qué significa esto? Bueno, es similar a cómo en el mundo real hay diferentes tipos de objetos, como un libro o un bolígrafo. En Python, las funciones también son objetos y, al igual que otros objetos, tienen su propio conjunto de atributos. Estos atributos pueden darnos mucha información útil sobre la función, como su nombre, dónde está definida y cómo está implementada.

Comencemos nuestra exploración abriendo una shell interactiva de Python. Esta shell es como un campo de juego donde podemos escribir y ejecutar código de Python de inmediato. Para hacer esto, primero navegaremos al directorio del proyecto y luego iniciaremos el intérprete de Python. Aquí están los comandos para ejecutar en tu terminal:

cd ~/project
python3

Ahora que estamos en la shell interactiva de Python, definamos una función simple. Esta función tomará dos números y los sumará. Así es como podemos definirla:

def add(x, y):
    'Adds two things'
    return x + y

En este código, hemos creado una función llamada add. Toma dos parámetros, x y y, y devuelve su suma. La cadena 'Adds two things' se llama docstring, que se utiliza para documentar lo que hace la función.

Usando dir() para inspeccionar los atributos de una función

En Python, la función dir() es una herramienta muy útil. Se puede utilizar para obtener una lista de todos los atributos y métodos que tiene un objeto. Usémosla para ver qué atributos tiene nuestra función add. Ejecuta el siguiente código en la shell interactiva de Python:

dir(add)

Cuando ejecutes este código, verás una larga lista de atributos. Aquí tienes un ejemplo de cómo podría verse la salida:

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

Esta lista muestra todos los atributos y métodos asociados con la función add.

Accediendo a la información básica de una función

Ahora, echemos un vistazo más de cerca a algunos de los atributos básicos de una función. Estos atributos pueden darnos información importante sobre la función. Ejecuta el siguiente código en la shell interactiva de Python:

print(add.__name__)
print(add.__module__)
print(add.__doc__)

Cuando ejecutes este código, verás la siguiente salida:

add
__main__
Adds two things

Entendamos lo que significa cada uno de estos atributos:

  • __name__: Este atributo nos da el nombre de la función. En nuestro caso, la función se llama add.
  • __module__: Nos dice el módulo donde está definida la función. Cuando ejecutamos código en la shell interactiva, el módulo suele ser __main__.
  • __doc__: Esta es la cadena de documentación de la función, o docstring. Proporciona una breve descripción de lo que hace la función.

Examinando el código de una función

El atributo __code__ de una función es muy interesante. Contiene información sobre cómo está implementada la función, incluyendo su bytecode y otros detalles. Veamos qué podemos aprender de él. Ejecuta el siguiente código en la shell interactiva de Python:

print(add.__code__.co_varnames)
print(add.__code__.co_argcount)

La salida será:

('x', 'y')
2

Esto es lo que nos dicen estos atributos:

  • co_varnames: Es una tupla que contiene los nombres de todas las variables locales utilizadas por la función. En nuestra función add, las variables locales son x y y.
  • co_argcount: Este atributo nos dice el número de argumentos que espera la función. Nuestra función add espera dos argumentos, por lo que el valor es 2.

Si estás curioso por explorar más atributos del objeto __code__, puedes usar la función dir() de nuevo. Ejecuta el siguiente código:

dir(add.__code__)

Esto mostrará todos los atributos del objeto de código, que contienen detalles de bajo nivel sobre cómo está implementada la función.

Usando el módulo inspect

En Python, la biblioteca estándar incluye un módulo inspect muy útil. Este módulo es como una herramienta detective que nos ayuda a recopilar información sobre objetos en tiempo de ejecución (live objects) en Python. Los objetos en tiempo de ejecución pueden ser cosas como módulos, clases y funciones. En lugar de buscar manualmente a través de los atributos de un objeto para encontrar información, el módulo inspect proporciona formas más organizadas y de alto nivel de entender las propiedades de las funciones.

Sigamos usando la misma shell interactiva de Python para explorar cómo funciona este módulo.

Firmas de funciones

La función inspect.signature() es una herramienta muy útil. Cuando le pasas una función, devuelve un objeto Signature. Este objeto contiene detalles importantes sobre los parámetros de la función.

Aquí tienes un ejemplo. Supongamos que tenemos una función llamada add. Podemos usar la función inspect.signature() para obtener su firma:

import inspect
sig = inspect.signature(add)
print(sig)

Cuando ejecutes este código, la salida será:

(x, y)

Esta salida nos muestra la firma de la función, que nos dice qué parámetros puede aceptar la función.

Examinando detalles de los parámetros

Podemos ir un paso más allá y obtener información más detallada sobre cada parámetro de la función.

print(sig.parameters)

La salida de este código será:

OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">)])

Los parámetros de la función se almacenan en un diccionario ordenado. A veces, solo podríamos estar interesados en los nombres de los parámetros. Podemos convertir este diccionario ordenado en una tupla para extraer solo los nombres de los parámetros.

param_names = tuple(sig.parameters)
print(param_names)

La salida será:

('x', 'y')

Examinando parámetros individuales

También podemos echar un vistazo más detallado a cada parámetro individual. El siguiente código recorre cada parámetro de la función e imprime algunos detalles importantes sobre él.

for name, param in sig.parameters.items():
    print(f"Parameter: {name}")
    print(f"  Kind: {param.kind}")
    print(f"  Default: {param.default if param.default is not param.empty else 'No default'}")

Este código nos mostrará detalles sobre cada parámetro. Nos dice el tipo de parámetro (si es un parámetro posicional, un parámetro de palabra clave, etc.) y su valor predeterminado si tiene uno.

El módulo inspect tiene muchas otras funciones útiles para la introspección de funciones. Aquí tienes algunos ejemplos:

  • inspect.getdoc(obj): Esta función recupera la cadena de documentación de un objeto. Las cadenas de documentación son como notas que los programadores escriben para explicar lo que hace un objeto.
  • inspect.getfile(obj): Nos ayuda a averiguar el archivo donde está definido un objeto. Esto puede ser muy útil cuando queremos localizar el código fuente de un objeto.
  • inspect.getsource(obj): Esta función obtiene el código fuente de un objeto. Nos permite ver exactamente cómo está implementado el objeto.

Aplicando la inspección de funciones en clases

Ahora, vamos a tomar lo que hemos aprendido sobre la inspección de funciones y usarlo para mejorar la implementación de una clase. La inspección de funciones nos permite examinar el interior de las funciones y entender su estructura, como los parámetros que toman. En este caso, lo usaremos para hacer que nuestro código de clase sea más eficiente y menos propenso a errores. Modificaremos una clase Structure para que pueda detectar automáticamente los nombres de los campos a partir de la firma del método __init__.

Entendiendo la clase Structure

El archivo structure.py contiene una clase Structure. Esta clase actúa como una clase base, lo que significa que otras clases pueden heredar de ella para crear objetos de datos estructurados. Actualmente, para definir los atributos de los objetos creados a partir de clases que heredan de Structure, necesitamos establecer una variable de clase _fields.

Abriremos el archivo en el editor. Usaremos el siguiente comando para navegar al directorio del proyecto:

cd ~/project

Una vez que hayas ejecutado este comando, puedes encontrar y ver la clase Structure existente en el archivo structure.py dentro del WebIDE.

Creando una clase Stock

Vamos a crear una clase Stock que herede de la clase Structure. La herencia significa que la clase Stock obtendrá todas las características de la clase Structure y también puede agregar las suyas propias. Agregaremos el siguiente código al final del archivo structure.py:

class Stock(Structure):
    _fields = ('name', 'shares', 'price')

    def __init__(self, name, shares, price):
        self._init()

Sin embargo, hay un problema con este enfoque. Tenemos que definir tanto la tupla _fields como el método __init__ con los mismos nombres de parámetros. Esto es redundante porque esencialmente estamos escribiendo la misma información dos veces. Si olvidamos actualizar una cuando cambiamos la otra, puede causar errores.

Agregando un método de clase set_fields

Para solucionar este problema, agregaremos un método de clase set_fields a la clase Structure. Este método detectará automáticamente los nombres de los campos a partir de la firma de __init__. Aquí está el código que necesitamos agregar a la clase Structure:

@classmethod
def set_fields(cls):
    ## Get the signature of the __init__ method
    import inspect
    sig = inspect.signature(cls.__init__)

    ## Get parameter names, skipping 'self'
    params = list(sig.parameters.keys())[1:]

    ## Set _fields attribute on the class
    cls._fields = tuple(params)

Este método utiliza el módulo inspect, que es una herramienta poderosa en Python para obtener información sobre objetos como funciones y clases. Primero, obtiene la firma del método __init__. Luego, extrae los nombres de los parámetros, pero omite el parámetro self porque self es un parámetro especial en las clases de Python que se refiere a la instancia misma. Finalmente, establece la variable de clase _fields con estos nombres de parámetros.

Modificando la clase Stock

Ahora que tenemos el método set_fields, podemos simplificar nuestra clase Stock. Reemplaza el código anterior de la clase Stock con lo siguiente:

class Stock(Structure):
    def __init__(self, name, shares, price):
        self._init()

## Call set_fields to automatically set _fields from __init__
Stock.set_fields()

De esta manera, no tenemos que definir manualmente la tupla _fields. El método set_fields se encargará de ello por nosotros.

Probando la clase modificada

Para asegurarnos de que nuestra clase modificada funcione correctamente, crearemos un script de prueba simple. Crea un nuevo archivo llamado test_structure.py y agrega el siguiente código:

from structure import Stock

def test_stock():
    ## Create a Stock object
    s = Stock(name='GOOG', shares=100, price=490.1)

    ## Test string representation
    print(f"Stock representation: {s}")

    ## Test attribute access
    print(f"Name: {s.name}")
    print(f"Shares: {s.shares}")
    print(f"Price: {s.price}")

    ## Test attribute modification
    s.shares = 50
    print(f"Updated shares: {s.shares}")

    ## Test attribute error
    try:
        s.share = 50  ## Misspelled attribute
        print("Error: Did not raise AttributeError")
    except AttributeError as e:
        print(f"Correctly raised: {e}")

if __name__ == "__main__":
    test_stock()

Este script de prueba crea un objeto Stock, prueba su representación como cadena, accede a sus atributos, modifica un atributo y trata de acceder a un atributo mal escrito para verificar si genera el error correcto.

Para ejecutar el script de prueba, usa el siguiente comando:

python3 test_structure.py

Deberías ver una salida similar a esta:

Stock representation: Stock('GOOG',100,490.1)
Name: GOOG
Shares: 100
Price: 490.1
Updated shares: 50
Correctly raised: No attribute share

Cómo funciona

  1. El método set_fields utiliza inspect.signature() para obtener los nombres de los parámetros del método __init__. Esta función nos da información detallada sobre los parámetros del método __init__.
  2. Luego, establece automáticamente la variable de clase _fields en función de estos nombres de parámetros. Así, no tenemos que escribir los mismos nombres de parámetros en dos lugares diferentes.
  3. Esto elimina la necesidad de definir manualmente tanto _fields como __init__ con nombres de parámetros coincidentes. Hace que nuestro código sea más mantenible porque si cambiamos los parámetros en el método __init__, _fields se actualizará automáticamente.

Este enfoque utiliza la inspección de funciones para hacer que nuestro código sea más mantenible y menos propenso a errores. Es una aplicación práctica de las capacidades de introspección de Python, que nos permiten examinar y modificar objetos en tiempo de ejecución.

✨ Revisar Solución y Practicar

Resumen

En este laboratorio, has aprendido cómo inspeccionar el interior de las funciones de Python. Has examinado directamente los atributos de las funciones utilizando métodos como dir() y has accedido a atributos especiales como __name__, __doc__ y __code__. También has utilizado el módulo inspect para obtener información estructurada sobre las firmas y parámetros de las funciones.

La inspección de funciones es una característica poderosa de Python que te permite escribir código más dinámico, flexible y mantenible. La capacidad de examinar y manipular las propiedades de las funciones en tiempo de ejecución ofrece posibilidades para la metaprogramación, la creación de código auto - documentado y la construcción de marcos avanzados.