Personalizar el comportamiento dinámico de Python

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

Varios aspectos del comportamiento de Python pueden personalizarse a través de métodos especiales o llamados "mágicos". Esta sección presenta esa idea. Además, se discuten el acceso dinámico a atributos y los métodos vinculados.

Introducción

Las clases pueden definir métodos especiales. Estos tienen un significado especial para el intérprete de Python. Siempre están precedidos y seguidos por __. Por ejemplo __init__.

class Stock(object):
    def __init__(self):
     ...
    def __repr__(self):
     ...

Hay docenas de métodos especiales, pero solo veremos algunos ejemplos específicos.

Métodos especiales para conversiones de cadenas

Los objetos tienen dos representaciones en forma de cadena.

>>> from datetime import date
>>> d = date(2012, 12, 21)
>>> print(d)
2012-12-21
>>> d
datetime.date(2012, 12, 21)
>>>

La función str() se utiliza para crear una salida imprimible agradable:

>>> str(d)
'2012-12-21'
>>>

La función repr() se utiliza para crear una representación más detallada para los programadores.

>>> repr(d)
'datetime.date(2012, 12, 21)'
>>>

Esas funciones, str() y repr(), utilizan un par de métodos especiales en la clase para producir la cadena que se va a mostrar.

class Date(object):
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    ## Utilizado con `str()`
    def __str__(self):
        return f'{self.year}-{self.month}-{self.day}'

    ## Utilizado con `repr()`
    def __repr__(self):
        return f'Date({self.year},{self.month},{self.day})'

Nota: La convención para __repr__() es devolver una cadena que, cuando se le pasa a eval(), volverá a crear el objeto subyacente. Si esto no es posible, se utiliza en su lugar una representación fácil de leer.

Métodos especiales para matemáticas

Los operadores matemáticos implican llamadas a los siguientes métodos.

a + b       a.__add__(b)
a - b       a.__sub__(b)
a * b       a.__mul__(b)
a / b       a.__truediv__(b)
a // b      a.__floordiv__(b)
a % b       a.__mod__(b)
a << b      a.__lshift__(b)
a >> b      a.__rshift__(b)
a & b       a.__and__(b)
a | b       a.__or__(b)
a ^ b       a.__xor__(b)
a ** b      a.__pow__(b)
-a          a.__neg__()
~a          a.__invert__()
abs(a)      a.__abs__()

Métodos especiales para el acceso a elementos

Estos son los métodos para implementar contenedores.

len(x)      x.__len__()
x[a]        x.__getitem__(a)
x[a] = v    x.__setitem__(a,v)
del x[a]    x.__delitem__(a)

Puedes utilizarlos en tus clases.

class Sequence:
    def __len__(self):
     ...
    def __getitem__(self,a):
     ...
    def __setitem__(self,a,v):
     ...
    def __delitem__(self,a):
     ...

Invocación de métodos

La invocación de un método es un proceso en dos pasos.

  1. Búsqueda: El operador .
  2. Llamada de método: El operador ()
>>> s = stock.Stock('GOOG',100,490.10)
>>> c = s.cost  ## Búsqueda
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()         ## Llamada de método
49010.0
>>>

Métodos vinculados

Un método que aún no ha sido invocado por el operador de llamada de función () se conoce como un método vinculado. Opera en la instancia en la que se originó.

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> s
<Stock object at 0x590d0>
>>> c = s.cost
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()
49010.0
>>>

Los métodos vinculados a menudo son una fuente de errores inadvertidos y no obvios. Por ejemplo:

>>> s = stock.Stock('GOOG', 100, 490.10)
>>> print('Cost : %0.2f' % s.cost)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: float argument required
>>>

O un comportamiento engañoso que es difícil de depurar.

f = open(filename, 'w')
...
f.close     ## Ay, no hizo nada en absoluto. `f` todavía está abierto.

En ambos casos, el error se produce por olvidarse de incluir los paréntesis finales. Por ejemplo, s.cost() o f.close().

Acceso a atributos

Existe una forma alternativa de acceder, manipular y administrar atributos.

getattr(obj, 'name')          ## Lo mismo que obj.name
setattr(obj, 'name', value)   ## Lo mismo que obj.name = value
delattr(obj, 'name')          ## Lo mismo que del obj.name
hasattr(obj, 'name')          ## Comprueba si el atributo existe

Ejemplo:

if hasattr(obj, 'x'):
    x = getattr(obj, 'x'):
else:
    x = None

*Nota: getattr() también tiene un valor predeterminado útil *arg*.

x = getattr(obj, 'x', None)

Ejercicio 4.9: Mejor salida para la impresión de objetos

Modifica el objeto Stock que definiste en stock.py de modo que el método __repr__() produzca una salida más útil. Por ejemplo:

>>> goog = stock.Stock('GOOG', 100, 490.1)
>>> goog
Stock('GOOG', 100, 490.1)
>>>

Observa lo que sucede cuando lees una cartera de acciones y visualizas la lista resultante después de haber hecho estos cambios. Por ejemplo:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> portfolio
... ver cuál es la salida...
>>>
✨ Revisar Solución y Practicar

Ejercicio 4.10: Un ejemplo de uso de getattr()

getattr() es un mecanismo alternativo para leer atributos. Puede usarse para escribir código extremadamente flexible. Para comenzar, prueba este ejemplo:

>>> import stock
>>> s = stock.Stock('GOOG', 100, 490.1)
>>> columns = ['name','shares']
>>> for colname in columns:
        print(colname, '=', getattr(s, colname))

name = GOOG
shares = 100
>>>

Observa detenidamente que los datos de salida se determinan enteramente por los nombres de atributos enumerados en la variable columns.

En el archivo tableformat.py, toma esta idea y amplíala en una función generalizada print_table() que imprima una tabla que muestra atributos especificados por el usuario de una lista de objetos arbitrarios. Al igual que la función print_report() anterior, print_table() también debe aceptar una instancia de TableFormatter para controlar el formato de salida. Aquí es cómo debería funcionar:

>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('txt')
>>> print_table(portfolio, ['name','shares'], formatter)
      name     shares
---------- ----------
        AA        100
       IBM         50
       CAT        150
      MSFT        200
        GE         95
      MSFT         50
       IBM        100

>>> print_table(portfolio, ['name','shares', 'price'], formatter)
      name     shares      price
---------- ---------- ----------
        AA        100       32.2
       IBM         50       91.1
       CAT        150      83.44
      MSFT        200      51.23
        GE         95      40.37
      MSFT         50       65.1
       IBM        100      70.44
>>>
✨ Revisar Solución y Practicar

Resumen

¡Felicitaciones! Has completado el laboratorio de Métodos Especiales. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.