Programación Orientada a Objetos#
https://en.wikipedia.org/wiki/Object-oriented_programming
Tenemos las siguientes definiciones:
Clase: es una plantilla (template) para crear objetos (objects).
Objeto: los objetos son tipos de variables que a su vez tienen member variables y member functions o métodos. Estos métodos representan comportamiento y/o funcionalidad asociada al objeto.
Ejemplos#
En Python, todo es un objeto.
x = 2
¿Cómo me doy cuenta? La variables x
tiene métodos, por ejemplo:
x.to_bytes(1, 'big')
b'\x02'
from datetime import date
fecha = date(2021, 8, 25)
El objeto fecha
, que es de tipo date
se construye con tres enteros positivos que representan el año, mes y día. Si uso una combinación inválida se genera un error.
date(2021, 2, 29) # El 2021 no es bisiesto.
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[4], line 1
----> 1 date(2021, 2, 29) # El 2021 no es bisiesto.
ValueError: day is out of range for month
El objeto fecha
tiene métodos.
fecha.isoformat()
'2021-08-25'
Las funciones son objetos.
import math
type(math.exp)
builtin_function_or_method
La función math.exp
tiene métodos.
math.exp.__doc__
'Return e raised to the power of x.'
Definir una Clase#
Podemos definir nuestras propias clases y objetos. En el siguiente ejemplo vamos a definir una clase que represente una coordenada cartesiana en el espacio.
class PointInSpace:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
El método __init__
es el constructor de la clase, el parámetro self
representa la instancia de la clase (un objeto concreto).
Nota nerd: en Python los métodos que empiezan y terminan con doble guión bajo __
se llaman dunder methods.
https://dbader.org/blog/python-dunder-methods
¿Cómo se da de alta (instantiate) un objeto de tipo PointInSpace
?
punto = PointInSpace(0, 0, 0)
type(punto)
__main__.PointInSpace
Luego, a través de punto
podemos acceder a sus coordenadas.
punto.x
0
punto.y
0
punto.z
0
Lo siguiente genera un error.
punto.w
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-a1eeaf757af9> in <module>
----> 1 punto.w
AttributeError: 'PointInSpace' object has no attribute 'w'
Podemos agregar más comportamiento o funcionalidad a la clase, y en consecuencia, a todos los objetos de esa clase.
class PointInSpace:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def norma(self):
return math.sqrt(self.x**2 + self.y**2 + self.z**2)
punto = PointInSpace(1, 2, 3)
punto.norma()
3.7416573867739413
Un objeto es mutable:
punto.x = 2
punto.x
2
Incluso puedo agregarle métodos:
def norma_geom(self):
return math.pow(self.x * self.y * self.z, 1./3)
PointInSpace.norma_geom = norma_geom
punto = PointInSpace(1, 2, 3)
punto.norma_geom()
1.8171205928321397
Otros dunder methods#
import pandas as pd
data = [
{
'nombre': 'Donald',
'especie': 'pato',
},
{
'nombre': 'Mickey',
'especie': 'ratón',
},
{
'nombre': 'Tribilín',
'especie': 'perro',
}
]
data_df = pd.DataFrame(data)
data_df
nombre | especie | |
---|---|---|
0 | Donald | pato |
1 | Mickey | ratón |
2 | Tribilín | perro |
print(data_df.__doc__)
Two-dimensional, size-mutable, potentially heterogeneous tabular data.
Data structure also contains labeled axes (rows and columns).
Arithmetic operations align on both row and column labels. Can be
thought of as a dict-like container for Series objects. The primary
pandas data structure.
Parameters
----------
data : ndarray (structured or homogeneous), Iterable, dict, or DataFrame
Dict can contain Series, arrays, constants, or list-like objects.
.. versionchanged:: 0.23.0
If data is a dict, column order follows insertion-order for
Python 3.6 and later.
.. versionchanged:: 0.25.0
If data is a list of dicts, column order follows insertion-order
for Python 3.6 and later.
index : Index or array-like
Index to use for resulting frame. Will default to RangeIndex if
no indexing information part of input data and no index provided.
columns : Index or array-like
Column labels to use for resulting frame. Will default to
RangeIndex (0, 1, 2, ..., n) if no column labels are provided.
dtype : dtype, default None
Data type to force. Only a single dtype is allowed. If None, infer.
copy : bool, default False
Copy data from inputs. Only affects DataFrame / 2d ndarray input.
See Also
--------
DataFrame.from_records : Constructor from tuples, also record arrays.
DataFrame.from_dict : From dicts of Series, arrays, or dicts.
read_csv
read_table
read_clipboard
Examples
--------
Constructing DataFrame from a dictionary.
>>> d = {'col1': [1, 2], 'col2': [3, 4]}
>>> df = pd.DataFrame(data=d)
>>> df
col1 col2
0 1 3
1 2 4
Notice that the inferred dtype is int64.
>>> df.dtypes
col1 int64
col2 int64
dtype: object
To enforce a single dtype:
>>> df = pd.DataFrame(data=d, dtype=np.int8)
>>> df.dtypes
col1 int8
col2 int8
dtype: object
Constructing DataFrame from numpy ndarray:
>>> df2 = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),
... columns=['a', 'b', 'c'])
>>> df2
a b c
0 1 2 3
1 4 5 6
2 7 8 9
print(data_df.__str__())
nombre especie
0 Donald pato
1 Mickey ratón
2 Tribilín perro
data_df.__repr__()
' nombre especie\n0 Donald pato\n1 Mickey ratón\n2 Tribilín perro'
data_df.__len__()
3
So?#
Vamos a desarrollar un pequeño ejemplo que nos de una mejor idea de las posibilidades que se abren cuando usamos objetos.
NOTA: este código es sólo de ejemplo y le falta mucho para poder ser usado en un entorno de producción.
class PosicionRF:
def __init__(self, nemotecnico, nominal, valor_mercado, tir_mercado, duracion):
self.nemotecnico = nemotecnico
self.nominal = nominal
self.valor_mercado = valor_mercado
self.tir_mercado = tir_mercado
self.duracion = duracion
def sensibilidad(self, pb):
_pb = pb / 10000
return self.valor_mercado * self.duracion * _pb / (1 + self.tir_mercado)
def __str__(self):
result = f'nemotecnico: {self.nemotecnico}\n'
result += f'nominal: {self.nominal}\n'
result += f'valor_mercado: {self.valor_mercado}\n'
result += f'tir_mercado: {self.tir_mercado}\n'
result += f'duracion: {self.duracion}'
return result
def __repr__(self):
result = f'nemotecnico: {self.nemotecnico}\n'
result += f'nominal: {self.nominal}\n'
result += f'valor_mercado: {self.valor_mercado}\n'
result += f'tir_mercado: {self.tir_mercado}\n'
result += f'duracion: {self.duracion}'
return result
class Cuenta:
def __init__(self, id_cliente):
self.id_cliente = id_cliente
self.posiciones = []
def agrega_posicion(self, posicion):
self.posiciones.append(posicion)
def valor_mercado(self):
if len(self.posiciones) > 0:
return sum([p.valor_mercado for p in self.posiciones])
return 0
def sensibilidad(self, pb):
if len(self.posiciones) > 0:
return round(sum([p.sensibilidad(pb) for p in self.posiciones]), 6)
return 0
def __len__(self):
return len(self.posiciones)
def __getitem__(self, index):
return self.posiciones[index]
def __iter__(self):
return (t for t in self.posiciones)
cuenta = Cuenta('alvaro')
posicion = PosicionRF('bono1', 100, 101, .01, 4)
len(cuenta)
0
cuenta.agrega_posicion(posicion)
len(cuenta)
2
cuenta[0]
nemotecnico: bono1
nominal: 100
valor_mercado: 101
tir_mercado: 0.01
duracion: 4
cuenta.valor_mercado()
101
cuenta.sensibilidad(100)
4.0
for p in cuenta:
print(p)
nemotecnico: bono1
nominal: 100
valor_mercado: 101
tir_mercado: 0.01
duracion: 4
nemotecnico: bono1
nominal: 100
valor_mercado: 101
tir_mercado: 0.01
duracion: 4
Hay mucho más y muchas cosas muy interesantes …