2.21. Métodos y atributos¶
Un método es una función definida dentro de una clase. El primer parámetro es la instancia sobre la que se llama al método; la convención es nombrarlo self.
2.21.1. Métodos de instancia¶
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def magnitude(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
p = Point(3, 4)
print(p.magnitude())
Salida:
5.0
La llamada p.magnitude() pasa p como self automáticamente. Los cuerpos de los métodos leen y actualizan atributos a través de self de la misma forma que las funciones leen y actualizan nombres a través de los parámetros.
2.21.2. Atributos de instancia frente a atributos de clase¶
Los atributos asignados a self (normalmente dentro de __init__) pertenecen a esa única instancia:
a = Point(1, 2)
b = Point(10, 20)
a.x = 99
print(a.x, b.x) # 99 10
a.x y b.x son almacenamientos distintos; mutar uno no afecta al otro.
Los nombres asignados en el cuerpo de la clase, fuera de cualquier método, son compartidos por todas las instancias de la clase:
class Counter:
kind = "tally" # class attribute -- shared
def __init__(self):
self.value = 0 # instance attribute -- per instance
a = Counter()
b = Counter()
print(a.kind, b.kind) # tally tally
Los atributos de clase residen en la propia clase y son compartidos. Los atributos de instancia residen en cada instancia.¶
A un atributo de clase se accede de la misma manera que a uno de instancia (a.kind); Python busca primero en la instancia y luego en la clase. Asignar a a.kind crearía un nuevo atributo de instancia que oculta el de la clase, dejando b.kind intacto.
Usa atributos de clase para constantes y valores por defecto que todas las instancias deban ver de la misma forma. Usa atributos de instancia para el estado del que cada objeto deba tener su propia copia.
2.21.3. __str__ y __repr__¶
Dos métodos especiales controlan cómo se imprime una instancia. __str__ devuelve la cadena «amigable» que usa print(); __repr__ devuelve la cadena «de desarrollador» que se usa en el REPL y dentro de las visualizaciones de contenedores:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "(" + str(self.x) + ", " + str(self.y) + ")"
def __repr__(self):
return "Point(" + str(self.x) + ", " + str(self.y) + ")"
p = Point(3, 4)
print(p) # uses __str__
print([p, p]) # uses __repr__ inside the list
Salida:
(3, 4)
[Point(3, 4), Point(3, 4)]
Si solo se define __repr__, print() recurre a él, de modo que un único __repr__ bien escrito suele bastar. Define también __str__ solo cuando la forma amigable deba verse distinta de la forma de desarrollador. Procura que el __repr__ se parezca a la llamada que recrearía el objeto; depurar resulta muchísimo más fácil cuando los valores impresos no son ambiguos.