- Aprender
- Informática
- Programación
- Introducción a la programación con Python
- Programación orientada a objetos en Python
Introducción a la programación orientada a objetos en Python
==, se ordenen y se combinen con + mediante métodos especiales, construye programas en los que los objetos contengan otros objetos y los llamen, y valora cuándo una clase aporta más claridad que las funciones y los diccionarios.PrincipianteUnidad 10 de 10 del curso Curso de Introducción a la programación con Python
Lo que aprenderás
- Descubre que cada valor de Python es un objeto con sus propios datos y métodos, y después escribe tu propia clase y crea objetos a partir de ella.
- Reconoce los objetos que ya usasDescribe cadenas, listas y diccionarios como objetos que tienen un tipo, contienen datos y ofrecen métodos que llamas con notación de punto.
- Define una clase y crea instanciasEscribe una sentencia class, crea varios objetos independientes a partir de ella y asigna y lee sus atributos con notación de punto.
- Reconoce los objetos que ya usas
- Usa un constructor para dar a cada objeto nuevo los atributos que necesita, elige entre atributos de instancia y de clase e impide que se creen objetos no válidos.
- Escribe un constructor con __init__ y selfDefine
__init__para que cada objeto nuevo comience con sus propios atributos, tomados de los argumentos o establecidos con valores iniciales. - Elige entre atributos de instancia y atributos de claseDecide si un valor pertenece a cada objeto o a la clase en su conjunto y predice cómo encuentra y modifica Python cada tipo de atributo.
- Mantén los objetos en un estado válido desde el principioComprueba los argumentos que recibe un constructor, lanza una excepción clara para los valores incorrectos y mantén las reglas que dan sentido al objeto.
- Escribe un constructor con __init__ y self
- Escribe métodos que usen y modifiquen los datos de un objeto y protege esos datos de cambios que dejarían el objeto en un estado no válido.
- Define y llama a métodos de instanciaEscribe métodos que trabajen con
self, llámalos sobre objetos y devuelve resultados en lugar de mostrarlos. - Modifica el estado de un objeto con métodosEscribe métodos que actualicen atributos manteniendo el objeto en un estado válido y predice cuándo un cambio será visible a través de otros nombres del mismo objeto.
- Protege atributos con convenciones de nombres y propiedadesMarca los atributos como internos con un guion bajo y usa propiedades para comprobar valores nuevos o calcular valores que se mantengan actualizados.
- Define y llama a métodos de instancia
- Escribe métodos especiales que permitan a tus objetos mostrar texto legible, compararse con
==y<y funcionar con operadores y funciones incorporadas.- Define representaciones en cadena legibles para los objetosEscribe
__str__y__repr__para que los objetos muestren texto útil al visualizarlos, inspeccionarlos o almacenarlos en una lista. - Compara y combina objetos con operadoresDefine
__eq__,__lt__,__add__y__len__para que tus objetos funcionen con==, la ordenación,+ylen(), y decide cuándo un operador resulta más claro que un método con nombre.
- Define representaciones en cadena legibles para los objetos
- Combina objetos para que cada uno gestione sus propios datos, sigue la ejecución de programas donde los objetos se llaman entre sí y decide cuándo una clase mejora la claridad de un programa.
- Construye objetos que contengan otros objetosAlmacena objetos como atributos o en listas dentro de otro objeto y escribe métodos que trabajen con toda la colección.
- Haz que los objetos interactúen mediante métodosPasa objetos a métodos y funciones, devuelve objetos nuevos y sigue la ejecución de un programa breve en el que colaboran varios objetos.
- Decide cuándo una clase mejora la claridad de un programaValora cuándo resulta útil reunir datos y comportamiento en una clase, planifica una clase pequeña a partir de una descripción y reescribe código basado en diccionarios como una clase.
- Construye objetos que contengan otros objetos
Guía de estudio de Programación orientada a objetos en Python
1Comprende los objetos y define tu primera clase
Los objetos ya aparecen en cada programa de Python que escribes: una cadena tiene métodos, una lista cambia su contenido y un diccionario responde a consultas por clave. Interpretar esas operaciones conocidas como comportamiento de objetos te da un punto de partida para crear tus propias clases. Aprende cómo afectan a un programa los tipos, la identidad y las referencias compartidas; después define una clase pequeña y da a cada instancia sus propios atributos.
1.1 Reconoce los objetos que ya usas
Ya has usado objetos siempre que has trabajado con un número, una cadena, una lista o un diccionario. Un objeto tiene un tipo, un valor o estado y operaciones que admite. Incluso las funciones y None son objetos. Escribir tu propia clase amplía este sistema que ya conoces.
Averigua el tipo y los métodos de un objeto
type("hello") devuelve <class 'str'>; type([1, 2]) devuelve <class 'list'>. Las palabras tipo y clase describen aquí la misma función. isinstance(3.5, float) es True, mientras que isinstance("3.5", float) es False: el segundo valor es texto, aunque parezca numérico.
Un método es una función que proporciona un tipo. En "ada lovelace".title(), la cadena anterior al punto es el objeto, title es el método y los paréntesis lo llaman. El resultado es "Ada Lovelace". dir(value) enumera los nombres que ofrece un objeto; nombres como __len__ son métodos especiales que conectan los objetos con operaciones de Python como len(value).
Sigue el resultado y el original
Una llamada a método puede devolver un resultado, modificar un objeto o hacer ambas cosas. Comprueba el comportamiento del método antes de decidir qué asignar.
word = "level"
print(word.count("l")) # 2
print(word.upper()) # LEVEL
print(word) # level
nums = [3, 1, 2]
result = nums.sort()
print(nums) # [1, 2, 3]
print(result) # None
Las cadenas son inmutables: su contenido no puede cambiar. upper() devuelve otra cadena, por lo que word conserva su valor original. Para guardar el resultado en mayúsculas con ese nombre, escribe word = word.upper().
Las listas son mutables. sort() modifica la propia lista y devuelve None. Escribir nums = nums.sort() pierde la referencia a la lista que contenía nums y asigna None. Usa nums.sort() en su propia línea o nums = sorted(nums) para obtener una lista nueva ordenada. Para un diccionario, {"a": 1}.get("b", 0) devuelve 0 porque falta la clave solicitada.
Los paréntesis también importan. word.upper es un objeto método; word.upper() lo ejecuta. Muchos objetos pueden ofrecer el mismo comportamiento: "cat".upper() y "dog".upper() usan un mismo tipo de método con distintos datos de cadena.
Distingue igualdad e identidad
La igualdad comprueba si los valores coinciden. La identidad comprueba si dos nombres se refieren a un mismo objeto.
a = [1, 2]
b = [1, 2]
c = a
print(a == b) # True
print(a is b) # False
print(c is a) # True
c.append(3)
print(a) # [1, 2, 3]
print(b) # [1, 2]
a y b empiezan con contenidos iguales, pero son listas independientes. c = a crea un alias, otro nombre para la misma lista. Añadir un elemento a través de c modifica el objeto al que también se refiere a. No modifica la lista independiente que contiene b. Al seguir la ejecución de un programa, dibuja una caja por objeto y una flecha desde cada nombre hasta su caja.
Puntos clave
- Cada valor de Python es un objeto con un tipo, un estado y operaciones que admite.
- Llama a un método con paréntesis; una referencia al método por sí sola no lo ejecuta.
- Comprueba si un método devuelve un valor nuevo o modifica el objeto existente.
==compara valores;iscompara la identidad.- La asignación puede dar a dos nombres referencias al mismo objeto mutable.
1.2 Define una clase y crea instancias
Una clase define un tipo que puedes usar en tu propio programa. Cada objeto creado a partir de ella es una instancia. Una clase describe el tipo de cosa; las instancias contienen los datos de cosas concretas. Un formulario en blanco y sus copias rellenadas ofrecen una comparación útil.
Escribe una clase y llámala
Una sentencia class tiene la palabra reservada class, un nombre, dos puntos y un cuerpo con sangría. pass proporciona un cuerpo vacío mientras pruebas la estructura.
class Book:
pass
b1 = Book()
b2 = Book()
print(b1 is b2) # False
print(isinstance(b1, Book)) # True
Cada llamada Book() crea una instancia independiente. En un archivo de programa, type(b1) muestra <class '__main__.Book'>. La sentencia class debe ejecutarse antes de que el código intente usar Book. También puedes definir una clase en un módulo e importarla con from library import Book, y después crear instancias en el programa que la usa.
Sigue las convenciones de nombres de Python: Book, BankAccount y ShoppingCart usan CapWords, con cada palabra iniciada en mayúscula. Las variables y los métodos usan palabras en minúsculas separadas por guiones bajos, como book_count y add_item. Usa cuatro espacios por cada nivel de sangría.
Almacena datos en cada instancia
Un atributo es un valor con nombre al que se accede con un punto. Asignar un atributo lo almacena; leer el mismo nombre lo recupera.
b1.title = "Dune"
b2.title = "Emma"
b1.pages = 412
print(b1.title) # Dune
print(b2.title) # Emma
print(b1.pages) # 412
Los datos pertenecen a la instancia seleccionada. Dar a b1 un atributo pages no se lo da a b2. Leer ahora b2.pages lanza AttributeError, porque ese objeto no tiene un atributo llamado pages.
Las asignaciones pueden crear nombres nuevos sin comprobar qué querías hacer. Después de b1.titel = "X", el b1.title original sigue conteniendo "Dune"; el objeto ha adquirido un segundo atributo con un nombre mal escrito. Si parece que un valor no cambia, compara cómo está escrito donde se asigna y donde se lee.
Distingue la clase de sus instancias
| Expresión | Significado |
|---|---|
Book | El objeto clase, que define el tipo |
Book() | Una llamada que crea una instancia |
b1.title | El título almacenado en este libro |
Book.title | Un atributo solicitado a la propia clase |
b = Book da otro nombre a la clase. No crea un libro. Si los títulos solo se han asignado en las instancias, solicitar Book.title lanza AttributeError; no hay un único título compartido por el tipo.
Da a cada objeto un inicio fiable
Es posible asignar atributos desde fuera, pero eso deja la inicialización repartida por el programa. Un libro podría recibir un título y un número de páginas, mientras que otro solo recibe un título. El código posterior no puede dar por seguro que ambos tengan pages.
Un método de inicialización llamado __init__ coloca las asignaciones iniciales dentro de la clase. Cada llamada de creación sigue entonces el mismo proceso de inicialización. La clase puede exigir un título y crear otros atributos con valores iniciales adecuados, para que el código que la llama no tenga que recordar una secuencia aparte de asignaciones.
Puntos clave
- Una clase define un tipo; una instancia es un objeto creado a partir de ella.
- Usa
ClassName()para crear una instancia y la notación de punto para acceder a sus atributos. - Cada instancia puede contener datos distintos bajo los mismos nombres de atributo.
- Un atributo ausente lanza
AttributeError; una asignación mal escrita puede crear un atributo nuevo.
2Inicializa objetos con constructores y atributos
Un objeto necesita un estado inicial fiable antes de que sus métodos puedan realizar tareas útiles. La inicialización conecta los argumentos de una llamada a clase con los atributos almacenados en la instancia nueva. Elige qué valores pertenecen a un objeto y cuáles a la clase, y después valida los datos de entrada antes de aceptarlos. Seguir la búsqueda de atributos y los fallos de inicialización te ayudará a detectar datos compartidos por accidente y datos ausentes.
2.1 Escribe un constructor con __init__ y self
Cada cuenta recién creada necesita un titular y un saldo inicial. Colocar esa inicialización en __init__ da a cada objeto los atributos que esperan los métodos posteriores. Python crea primero la instancia y después llama a __init__ para inicializarla.
Conecta los argumentos con los atributos
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
self.transactions = []
acct = BankAccount("Maya", 50)
other = BankAccount("Leo")
En la primera llamada, Python pasa la cuenta nueva como self, "Maya" como owner y 50 como balance. Las asignaciones hacen que acct.owner sea igual a "Maya" y acct.balance sea igual a 50. La segunda llamada usa el saldo predeterminado, por lo que other.balance es 0.
self es el parámetro que recibe el objeto actual. Es una convención, no una palabra reservada de Python. Usa este nombre para que quien lea el código reconozca el objeto enseguida. El código que realiza la llamada lo omite: BankAccount("Maya", 50) proporciona dos argumentos, no tres.
Distingue los nombres locales de los datos almacenados
En self.owner = owner, el nombre de la derecha es un parámetro local. El nombre de la izquierda es un atributo almacenado en el objeto. Cuando termina la inicialización, otros métodos pueden seguir usando self.owner.
Escribir balance = balance solo reasigna el nombre local. No crea ningún atributo, por lo que leer acct.balance después falla. Pon self. delante de cada atributo que deba permanecer como parte del objeto.
Planifica la inicialización enumerando lo que necesita cada instancia. Los valores obligatorios se convierten en parámetros después de self; los opcionales pueden tener valores predeterminados. Los valores que siempre comienzan igual pueden asignarse en el cuerpo, como self.visits = 0. Una lista creada mediante self.transactions = [] se crea de nuevo en cada llamada.
Evita una lista predeterminada compartida
Un argumento predeterminado se evalúa cuando Python define el método. Con def __init__(self, items=[]), las llamadas que omiten items reciben la misma lista. Si el inicializador almacena esa lista directamente, modificar los elementos de un objeto cambia los elementos que ve otro.
Usa None como valor predeterminado y crea una lista nueva durante cada llamada:
class Cart:
def __init__(self, items=None):
self.items = [] if items is None else list(items)
a = Cart()
b = Cart()
a.items.append("pen")
print(b.items) # []
list(items) también crea una lista externa nueva cuando el código que realiza la llamada proporciona una. Los objetos de esa lista siguen siendo referencias compartidas; crear la lista no recrea cada elemento.
Interpreta los errores de inicialización
A BankAccount() le falta el argumento obligatorio owner y lanza TypeError. Compara la llamada con los parámetros que aparecen después de self.
El nombre __init__ tiene dos guiones bajos a cada lado. _init_ es un nombre de método normal y no realizará esta inicialización automáticamente. __init__ debe devolver None, así que normalmente se omite la sentencia return. Devolver un saldo lanza TypeError; la propia llamada a la clase proporciona la instancia nueva al código que la llama.
Puntos clave
__init__inicializa la instancia después de que Python la cree.- Python proporciona
self; el código que realiza la llamada proporciona los demás argumentos. self.name = namealmacena un parámetro como atributo de instancia.- Crea las listas de cada instancia dentro de la inicialización; usa
Noneen lugar de un valor predeterminado mutable. __init__debe devolverNone.
2.2 Elige entre atributos de instancia y atributos de clase
Algunos valores describen un objeto; otros describen el tipo en su conjunto. El tamaño de una pizza pertenece a esa pizza. Un límite de ingredientes o un recuento de pizzas creadas puede pertenecer a la clase. El lugar donde se realiza una asignación determina qué tipo de atributo crea.
Coloca el valor en el lugar adecuado
class Pizza:
MAX_TOPPINGS = 5
count = 0
def __init__(self, size):
self.size = size
self.toppings = []
Pizza.count += 1
p1 = Pizza("large")
p2 = Pizza("small")
size y toppings son atributos de instancia, asignados en self. Cada pizza tiene sus propios valores. MAX_TOPPINGS y count son atributos de clase, asignados en el cuerpo de la clase fuera de un método. Las mayúsculas indican que un valor se considera una constante; no impiden su reasignación.
Después de estas llamadas, Pizza.count es 2. Cada inicializador actualiza el mismo atributo de clase. p1.size es "large", mientras que p2.size es "small".
Sigue la búsqueda y la ocultación de nombres
Para atributos de datos normales como estos, leer p1.MAX_TOPPINGS comprueba primero la instancia y después la clase. Como no hay ningún valor almacenado en p1, Python encuentra Pizza.MAX_TOPPINGS, que es 5.
Asignar a través de una instancia tiene un efecto distinto:
p1.MAX_TOPPINGS = 8
Pizza.MAX_TOPPINGS = 6
print(p1.MAX_TOPPINGS) # 8
print(p2.MAX_TOPPINGS) # 6
print(Pizza.MAX_TOPPINGS) # 6
La primera línea crea un atributo de instancia en p1. Este oculta el valor de clase para ese objeto. Cambiar después el valor de clase llega a p2, que sigue leyendo de la clase, pero no sustituye el valor propio de p1.
| Asignación | Qué cambia |
|---|---|
self.size = size | Los datos de la instancia actual |
Pizza.count += 1 | El contador compartido de la clase |
p1.MAX_TOPPINGS = 8 | Un valor de instancia que oculta el valor de clase |
Vigila dos formas de compartir datos por accidente
Si el inicializador usara self.count += 1, Python leería el 0 de la clase, sumaría uno y almacenaría un atributo de instancia. Cada pizza nueva recibiría su propio count de 1, mientras que Pizza.count seguiría siendo 0. Usa el nombre de la clase al actualizar un contador de toda la clase.
Una lista en el cuerpo de la clase causa un problema distinto. Con toppings = [], todas las instancias que no tienen un atributo de ingredientes independiente encuentran la misma lista. p1.toppings.append("olives") modifica esa lista compartida; no asigna un atributo de instancia nuevo. Pon self.toppings = [] en __init__ cuando cada pizza necesite sus propios ingredientes.
Inspecciona los datos propios de un objeto
Para el p2 original, vars(p2) da {'size': 'small', 'toppings': []}. Los valores de clase no aparecen porque están en Pizza. Esto resulta útil al depurar una búsqueda inesperada: comprueba si la instancia tiene su propio valor y después inspecciona el valor de clase. Un ajuste compartido pertenece a la clase; un nombre, tamaño, saldo o colección personal pertenece a la instancia.
Puntos clave
- Los atributos de instancia contienen datos de un objeto; los de clase contienen ajustes compartidos o datos de toda la clase.
- Un atributo de datos de instancia puede ocultar un atributo de clase del mismo nombre.
- Actualiza un contador compartido a través del nombre de la clase.
- Modificar una lista de clase cambia la única lista compartida.
vars(obj)muestra sus atributos propios almacenados, no los atributos de clase que puede encontrar.
2.3 Mantén los objetos en un estado válido desde el principio
Un objeto debe comenzar en un estado que sus métodos puedan usar. Un rectángulo con anchura negativa no puede representar la figura prevista. Una regla que debe seguir siendo verdadera para todos los objetos de una clase es un invariante. La validación comprueba esas reglas antes de aceptar datos.
Comprueba antes de almacenar
class Rectangle:
def __init__(self, width, height):
if width <= 0:
raise ValueError("width must be positive")
if height <= 0:
raise ValueError("height must be positive")
self.width = width
self.height = height
Rectangle(3, 4) supera ambas comprobaciones y devuelve un objeto inicializado. Rectangle(-2, 4) lanza ValueError en la primera comprobación. Las asignaciones posteriores nunca se ejecutan. Si el programa no captura esa excepción, la ejecución se detiene ahí.
Supón que r ya se refiere a un rectángulo válido. En r = Rectangle(-2, 4), la llamada de la derecha falla antes de la asignación. r conserva su valor anterior. Si r no tenía valor antes de la llamada fallida, la asignación no crea ninguno.
Elige una excepción útil
| Excepción | Problema que describe |
|---|---|
ValueError | Un tipo de entrada aceptable tiene un valor inaceptable |
TypeError | La entrada tiene un tipo incorrecto para la operación |
Una anchura numérica negativa es un error de valor. Proporcionar texto donde se requiere un número es un error de tipo. La comparación con 0 de este ejemplo ya lanza TypeError para texto. Una clase también puede comprobar un tipo explícitamente con isinstance cuando necesita un mensaje más claro.
Escribe mensajes que indiquen la regla, como "width must be positive". Un mensaje que solo diga "bad input" obliga al código que realiza la llamada a adivinar qué valor debe corregirse.
Deja que el código que realiza la llamada gestione la recuperación
while True:
try:
width = float(input("Width: "))
room = Rectangle(width, 3)
break
except ValueError as error:
print("Try again:", error)
Introducir abc falla en float; introducir -2 supera la conversión, pero falla en Rectangle. Ambos lanzan ValueError, así que este bucle gestiona los dos casos. La clase establece su regla de datos, mientras que el código que realiza la llamada decide volver a preguntar.
Mostrar una advertencia dentro de __init__ no rechaza la llamada. Si la ejecución continúa, el programa puede recibir un objeto no válido y fallar después al usarlo. Lanzar una excepción detiene la inicialización fallida donde se produce el problema.
Limpia los datos de forma deliberada y mantén la regla
Una limpieza documentada puede ser útil. self.name = name.strip() elimina los espacios de los extremos; valida el nombre limpio si no se permiten nombres en blanco. self.items = list(items) crea una lista externa independiente, de modo que añadir elementos a la lista del código que realiza la llamada no los añade a esta. Cambiar una anchura no válida por su valor absoluto alteraría la intención del código que realiza la llamada, así que necesita una regla de diseño explícita.
Las comprobaciones de inicialización se ejecutan al crear el objeto. Una asignación simple como r.width = -5 sigue saltándoselas después. Los métodos que cambian una anchura deben comprobarla de nuevo. Una propiedad puede incluir una comprobación en la propia asignación del atributo, lo que permite ejecutar la misma regla durante la inicialización y en actualizaciones posteriores.
Puntos clave
- Un invariante es una regla que debe cumplir el estado válido de un objeto.
- Valida antes de aceptar datos y lanza una excepción clara si hay un fallo.
- Una llamada de creación fallida no asigna un objeto nuevo al nombre usado por el código que la llama.
- Las comprobaciones del constructor también necesitan apoyo en los métodos o propiedades posteriores que cambian el estado.
3Define el comportamiento de los objetos con métodos de instancia
Los métodos convierten los datos almacenados en comportamiento útil. Un rectángulo puede calcular su área y una cuenta puede actualizar su saldo mientras registra una transacción. Sigue cómo pasa Python el objeto receptor a cada método y después distingue un resultado devuelto de un cambio de estado. Las propiedades permiten que una clase mantenga un acceso legible a los atributos mientras comprueba asignaciones o calcula valores cuando se solicitan.
3.1 Define y llama a métodos de instancia
Un método de instancia es una función definida dentro de una clase que trabaja con el objeto pasado como self. El objeto proporciona sus datos almacenados; los parámetros adicionales proporcionan cualquier otra información necesaria para la acción.
Lee los atributos a través de self
Añade estos métodos dentro de la clase Rectangle con validación, al mismo nivel de sangría que __init__:
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
def is_square(self):
return self.width == self.height
Con r = Rectangle(3, 4), r.area() devuelve 12, r.perimeter() devuelve 14 y r.is_square() devuelve False. Cada método lee los atributos del mismo objeto. Un segundo rectángulo usa las mismas definiciones de métodos con su propia anchura y altura.
Dentro de un método, self.width nombra un atributo. width por sí solo nombra una variable local u otra variable accesible; Python no la busca automáticamente en el objeto actual. Si no existe tal variable, return width * height lanza NameError. Escribir self. deja explícito a qué objeto pertenecen los datos.
Sigue la llamada
Para esta clase, r.area() funciona como Rectangle.area(r). Python encuentra la función en la clase y proporciona r como primer argumento. Una referencia a método como r.area es un método vinculado: recuerda la función y este rectángulo. Añadir () realiza la llamada.
Si un método se define como def resize(self, factor), llámalo como r.resize(2). r ocupa self y 2 ocupa factor. No vuelves a pasar r. Si un método de instancia se escribe por accidente como def area():, una llamada sobre r lanza TypeError porque Python sigue proporcionando el objeto a una función que no acepta argumentos.
Un método puede usar otro a través de self:
def double_area(self):
return self.area() * 2
Aquí self.area() calcula usando el mismo rectángulo. Omitir self. solicitaría una función independiente llamada area, no el método del objeto.
Devuelve resultados al código que realiza la llamada
Un método de cálculo normalmente debe devolver su resultado. Devolver proporciona al código que realiza la llamada un valor que puede almacenar, combinar, comprobar o mostrar:
r1 = Rectangle(3, 4)
r2 = Rectangle(2, 5)
total = r1.area() + r2.area()
print(total) # 22
Si area solo mostrara su respuesta, cada llamada devolvería None. La suma fallaría entonces con TypeError, aunque hubieran aparecido dos números en la pantalla. Mostrar es una acción; devolver proporciona un valor a la expresión que contiene la llamada.
Usa nombres de métodos que indiquen la tarea. deposit y resize nombran acciones; is_square y has_room sugieren comprobaciones booleanas. Un método no necesita parámetros para los datos que ya están almacenados en su objeto. Necesita parámetros para entradas nuevas, como un factor de cambio de tamaño o el importe de un depósito.
Puntos clave
- Pon
selfprimero en un método de instancia; Python proporciona el objeto receptor. - Lee atributos y llama a otros métodos a través de
self.. - Los paréntesis ejecutan un método vinculado.
- Devuelve los valores calculados para que el código que realiza la llamada pueda combinarlos, comprobarlos o mostrarlos.
3.2 Modifica el estado de un objeto con métodos
El estado de un objeto es el conjunto actual de valores de sus atributos. Un método de consulta informa sobre ese estado. Un método modificador lo cambia. Un método modificador útil comprueba sus entradas y mantiene todos los atributos relacionados, para que el código que lo llama no tenga que coordinar varias actualizaciones.
Valida primero todo el cambio
Añade estos métodos dentro de la clase BankAccount, que tiene titular, saldo y lista de transacciones:
def deposit(self, amount):
if amount <= 0:
raise ValueError("deposit must be positive")
self.balance += amount
self.transactions.append(("deposit", amount))
def withdraw(self, amount):
if amount <= 0:
raise ValueError("withdrawal must be positive")
if amount > self.balance:
raise ValueError("insufficient funds")
self.balance -= amount
self.transactions.append(("withdraw", amount))
Un depósito cambia el saldo y registra la operación. Una retirada comprueba tanto el signo del importe como el saldo disponible antes de restar. Estas comprobaciones impiden que una retirada rechazada cambie el saldo o su historial.
Partiendo de BankAccount("Maya", 50), depositar 25 da 75 y retirar 30 da 45. Un intento posterior de retirar 100 lanza ValueError; deja el saldo en 45 y no añade ninguna transacción. Colocar la resta antes de la comprobación de fondos cambiaría el estado incluso en caso de fallo.
Distingue un cambio de un valor devuelto
Estos métodos modificadores no tienen sentencia return, por lo que devuelven None. Después de new_balance = acct.deposit(25), el depósito se ha realizado, pero new_balance es None. Lee acct.balance después para obtener el número actualizado.
Una clase puede ofrecer otro estilo de método que devuelva un objeto nuevo. resize sugiere cambiar un rectángulo; resized puede elegirse para un método que devuelva un rectángulo nuevo y deje intacto el original. Lee el contrato del método: el nombre ayuda, pero su definición determina el comportamiento.
Sigue las referencias compartidas
a = BankAccount("Maya", 50)
b = a
b.deposit(10)
print(a.balance) # 60
print(a is b) # True
La asignación vincula un nombre a un objeto; no copia ese objeto. Ambos nombres acceden a la cuenta cuyo saldo ha cambiado. Pasar a a una función también proporciona acceso a ese objeto. Por tanto, una función que llame a account.withdraw(5) puede cambiar la cuenta que ve el código que la llama.
Para obtener una cuenta independiente con el mismo titular y saldo, crea BankAccount(a.owner, a.balance). Ese constructor también proporciona un historial nuevo y vacío. Es una cuenta nueva, no una copia completa de su historial.
copy.copy(a) crea un objeto externo nuevo, pero conserva las referencias a los mismos objetos internos, incluida la lista de transacciones. copy.deepcopy(a) copia también los objetos anidados. Cuando la independencia sea importante, comprueba tanto los objetos dentro de los atributos como la instancia externa.
Asignar directamente acct.balance = -500 se salta las comprobaciones de los dos métodos. Las convenciones de atributos internos y las propiedades ayudan a dejar clara la vía prevista de actualización y a mantener la validación cerca de los cambios de estado.
Puntos clave
- Comprueba un cambio solicitado antes de actualizar los atributos.
- Un método modificador puede cambiar el estado y devolver
None. - La asignación y el paso de argumentos conservan referencias al mismo objeto.
- Una copia superficial puede seguir compartiendo objetos mutables anidados.
- Mantén juntas las actualizaciones de estado relacionadas en el método responsable de ellas.
3.3 Protege atributos con convenciones de nombres y propiedades
El código que realiza las llamadas necesita una forma clara de leer y modificar un objeto. La encapsulación mantiene las reglas de sus datos junto a los métodos que los gestionan. Python usa convenciones y propiedades para expresar esa interfaz.
Marca el almacenamiento interno
Un guion bajo inicial en _balance indica un atributo interno. El código que usa la cuenta debe utilizar sus métodos, pero Python sigue permitiendo el acceso directo. No hay ninguna palabra reservada private que bloquee el acceso al valor.
Un nombre como __balance, con dos guiones bajos iniciales y sin dos guiones bajos finales, activa la transformación de nombres. Dentro de class Acc, self.__balance se almacena con el nombre _Acc__balance. Fuera, obj.__balance falla, pero obj._Acc__balance puede seguir funcionando. La transformación evita conflictos de nombres; no protege datos sensibles.
Ejecuta un método mediante la sintaxis de atributos
Una propiedad hace que se ejecute un método al leer un atributo. La línea @property es un decorador: cambia cómo Python expone el método. Una propiedad sin método de asignación permite leer, pero rechaza la asignación.
class Account:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
@property
def balance(self):
return self._balance
Después de acct = Account("Maya", 50), lee acct.balance sin paréntesis. Asignar acct.balance = 10 lanza AttributeError porque esta propiedad no tiene método de asignación. Un método de depósito dentro de la clase podría actualizar _balance después de sus comprobaciones.
Comprueba la asignación con un método de asignación
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not 0 <= value <= 100:
raise ValueError("score must be between 0 and 100")
self._score = value
Los métodos de consulta y de asignación comparten el nombre score; el valor almacenado usa _score. Al crear el objeto, self.score = score ejecuta el método de asignación.
Para s = Student("Ana", 88), asignar s.score = 92 almacena 92. Asignar s.score = -1 lanza ValueError antes de almacenar, por lo que la puntuación sigue siendo 92. Student("Ana", 105) falla durante la inicialización.
Dentro del método de asignación, self.score = value volvería a llamar al método, repitiéndose hasta producir RecursionError. El método de consulta tiene el mismo riesgo si devuelve self.score. Usa el nombre de almacenamiento independiente _score en ambos cuerpos.
Calcula los valores cuando se leen
Una propiedad puede calcular un valor a partir de otros atributos:
@property
def area(self):
return self.width * self.height
Colocada dentro de una clase de rectángulos, permite usar r.area. Para una anchura de 3 y una altura de 4, el valor es 12. Cambiar la anchura a 5 hace que la siguiente lectura devuelva 20. Almacenar un área una sola vez durante la inicialización dejaría un valor antiguo después de cambiar la anchura.
Empieza con un atributo simple cuando baste con almacenar el valor.
Puntos clave
- Un guion bajo inicial marca el almacenamiento interno por convención; no bloquea el acceso.
- La transformación de nombres cambia el nombre de un atributo para evitar conflictos.
- Una propiedad ejecuta un método de consulta mediante la sintaxis de atributos.
- Un método de asignación puede validar la inicialización y las asignaciones posteriores en un mismo lugar.
- Usa un atributo de almacenamiento independiente para evitar llamar a la propiedad de forma recursiva.
- Una propiedad calculada obtiene su valor al leerla.
4Usa métodos especiales para mostrar y comparar objetos adecuadamente
Un objeto útil debe encajar de forma natural en las expresiones habituales de Python. Sus métodos especiales pueden proporcionar texto legible, igualdad de valores, un orden, resultados aritméticos o una longitud. Aprende qué método solicita Python en cada situación y qué debe devolver ese método. Presta especial atención a la visualización dentro de contenedores, a las claves estables de diccionario y a la diferencia entre una operación y su forma reflejada.
4.1 Define representaciones en cadena legibles para los objetos
Un objeto resulta más fácil de inspeccionar cuando puede describir sus propios datos. Los métodos especiales permiten que Python solicite esa descripción mediante acciones conocidas como print, str y repr. Sus nombres empiezan y terminan con dos guiones bajos y suelen llamarse nombres dunder.
Elige texto para dos públicos
__str__ proporciona texto legible para los usuarios de un programa. __repr__ proporciona una descripción precisa para quienes programan. Ambos devuelven cadenas; ninguno debe mostrar su resultado en lugar de devolverlo.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p) # (3, 4)
print(repr(p)) # Point(3, 4)
str(p) devuelve la cadena "(3, 4)". repr(p) devuelve "Point(3, 4)". Cuando es posible, una representación se parece a una llamada a la clase que recrearía el valor. Sigue siendo texto: llamar a repr no crea por sí mismo otro punto.
Sin una representación personalizada, una instancia simple suele mostrar un nombre de clase y un identificador parecido a una dirección. Eso ayuda a distinguir objetos, pero no muestra sus datos útiles. Rara vez llamas directamente a estos métodos especiales; defínelos una vez y usa las operaciones habituales de visualización de Python.
Predice qué muestra un contenedor
| Expresión | Representación solicitada |
|---|---|
print(p) o str(p) | __str__ |
repr(p) | __repr__ |
f"{p}" | La forma habitual de cadena para esta clase |
f"{p!r}" | __repr__ |
print([p]) | La visualización de la lista, que usa repr para su elemento |
Con la clase anterior, print([Point(3, 4), Point(0, 0)]) muestra [Point(3, 4), Point(0, 0)]. La lista usa las representaciones de sus elementos, aunque cada elemento también tenga __str__.
Si defines solo __repr__, el __str__ predeterminado también lo usa. Definir solo __str__ no cambia repr ni la visualización de los contenedores. Por tanto, al añadir un único método de visualización, __repr__ sirve para más situaciones de inspección.
Haz que los campos de cadena sean inequívocos
Para un libro que ya almacena title, author y year, estos métodos ofrecen distintas vistas útiles:
def __str__(self):
return f"{self.title} by {self.author} ({self.year})"
def __repr__(self):
return f"Book({self.title!r}, {self.author!r}, {self.year})"
Para el libro con título "Dune", autor "Frank Herbert" y año 1965, el primer método da "Dune by Frank Herbert (1965)". El segundo da "Book('Dune', 'Frank Herbert', 1965)". !r solicita las representaciones de los campos de cadena, incluidas las comillas.
Un cuerpo que solo contiene print(self.x) devuelve None, por lo que str(p) falla con TypeError. Devolver un número entero también falla. Construye el texto necesario y después devuelve esa cadena.
Puntos clave
__str__proporciona texto para los usuarios;__repr__proporciona una representación útil para quienes programan.- Los contenedores muestran sus elementos con
repr. __repr__puede proporcionar la forma de cadena predeterminada;__str__no sustituye arepr.- Ambos métodos deben devolver cadenas.
- Usa
!ren una cadena formateada para mostrar representaciones entrecomilladas de los campos de cadena.
4.2 Compara y combina objetos con operadores
Los métodos especiales conectan una clase con operadores y funciones incorporadas. Define solo operaciones con un significado claro para ese tipo: sumar vectores se entiende; añadir un jugador a un equipo resulta más claro como team.add_player(player).
Compara valores y mantén los hashes seguros
Dos instancias simples independientes se consideran distintas de forma predeterminada. Añade igualdad de valores a una clase de puntos con:
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
Ahora dos puntos en (3, 4) se consideran iguales aunque sus identidades sean distintas. in, count, index y remove en listas pueden encontrar esos valores iguales. El resultado habitual de != se obtiene a partir de __eq__.
NotImplemented es un valor de retorno que permite a Python probar el otro operando o una alternativa predeterminada. No es una excepción que debas lanzar. Cuando ninguno de los operandos admite la igualdad de valores, == recurre a la identidad; un punto y la cadena "3,4" se consideran distintos.
Definir __eq__ sin __hash__ hace que las instancias no sean aptas para hash. Entonces no pueden ser elementos de conjuntos ni claves de diccionarios. Los objetos iguales deben tener hashes iguales y el hash de una clave debe mantenerse estable. Si los atributos comparados nunca cambian, un hash compatible puede devolver hash((self.x, self.y)). Mantén los objetos de valor mutables sin aptitud para hash cuando puedan cambiar sus campos de igualdad.
Define un orden
__lt__ proporciona <. Una clase de estudiantes puede comparar puntuaciones con return self.score < other.score. Las puntuaciones 88, 72 y 95 se ordenan entonces como 72, 88, 95. min elige la más baja; max elige la más alta usando > y su comparación reflejada <.
Con solo __lt__, a > b puede usar b < a. Python no deduce <= ni >= a partir de esa definición; necesitan sus propios métodos. Si un orden solo resulta útil para una tarea, pasa una función de clave de ordenación:
def by_score(student):
return student.score
ordered = sorted(students, key=by_score)
Devuelve resultados nuevos en las operaciones aritméticas
Dentro de una clase de vectores con atributos x e y:
def __add__(self, other):
if not isinstance(other, Vector):
return NotImplemented
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, factor):
if not isinstance(factor, (int, float)):
return NotImplemented
return Vector(self.x * factor, self.y * factor)
def __rmul__(self, factor):
return self * factor
Vector(1, 2) + Vector(3, 4) produce un vector nuevo (4, 6). Multiplicar el primer vector por 3 produce (3, 6) y deja intacto el original. __mul__ gestiona el vector a la izquierda; __rmul__ admite un número a la izquierda. Python no supone que ambos órdenes signifiquen lo mismo.
Proporciona una longitud con sentido
Para una lista de reproducción que almacena self.songs, define __len__ para que devuelva len(self.songs). El resultado debe ser un número entero no negativo. len(playlist) cuenta entonces las canciones. En esta clase, una longitud de cero también hace que if playlist: sea falso; un __bool__ personalizado tendría prioridad sobre la longitud al comprobar el valor de verdad.
Puntos clave
__eq__define la igualdad de valores; devuelveNotImplementedpara los tipos de operando no admitidos.- Los objetos iguales aptos para hash necesitan hashes iguales y estables.
__lt__permite ordenar, pero no proporciona<=ni>=.- Usa una función de clave de ordenación para un orden específico de una tarea.
- Los métodos aritméticos pueden crear objetos nuevos sin modificar los operandos.
__rmul__permite multiplicar con el objeto a la derecha.__len__devuelve un número entero no negativo y puede determinar el valor de verdad cuando no existe__bool__.
5Construye programas con objetos que colaboran
Los programas más grandes conectan objetos con distintas tareas. Un carrito puede pedir subtotales a sus artículos; una cuenta puede pedir a otra que reciba un depósito. Sigue las referencias y la secuencia de llamadas para ver qué estado cambia y dónde un error detiene la ejecución. Después usa esas ideas para elegir un diseño claro, planificar una clase pequeña y probar su comportamiento público en pasos manejables.
5.1 Construye objetos que contengan otros objetos
Los atributos de un objeto pueden referirse a otros objetos. Un carrito contiene artículos, una biblioteca contiene libros y un curso contiene estudiantes. La composición construye un objeto mayor a partir de otros más pequeños mediante esta relación tiene.
Da a cada objeto su propia responsabilidad
class Item:
def __init__(self, name, price, quantity):
self.name = name
self.price = price
self.quantity = quantity
def subtotal(self):
return self.price * self.quantity
class ShoppingCart:
def __init__(self):
self.items = []
def add(self, item):
if not isinstance(item, Item):
raise TypeError("a cart holds Item objects")
self.items.append(item)
def total(self):
total = 0
for item in self.items:
total += item.subtotal()
return total
El carrito tiene una lista independiente creada durante la inicialización. Su método add comprueba el objeto recibido antes de añadirlo. cart.add("pen") lanza TypeError en lugar de almacenar texto que fallará cuando se solicite un subtotal.
Crea los artículos antes de las llamadas o durante ellas:
cart = ShoppingCart()
cart.add(Item("notebook", 2.50, 4))
cart.add(Item("pen", 1.25, 3))
print(cart.total()) # 13.75
print(cart.items[0].name) # notebook
Los subtotales son 10.0 y 3.75; el carrito los suma para obtener 13.75. La cadena cart.items[0].name accede primero a la lista del carrito, después a su primer objeto y después al nombre de ese objeto.
Delega el trabajo en el objeto que sabe hacerlo
El carrito pide subtotal() a cada artículo. Esto es delegación: un objeto llama al método de otro objeto para realizar parte de una tarea. Si un descuento por volumen cambia cómo se calcula un subtotal, esa regla pertenece a Item.subtotal. El carrito puede seguir sumando los subtotales devueltos sin duplicar las reglas de precios.
Pasar Item en lugar de Item(...) pasa la clase, no un artículo comprado. La comprobación de tipo rechaza ese objeto clase. Realiza todas las adiciones a través de la vía con comprobación; acceder directamente a cart.items.append(...) se saltaría add.
Busca un objeto
Añade este método dentro de ShoppingCart:
def find(self, name):
for item in self.items:
if item.name == name:
return item
return None
La primera coincidencia devuelve el artículo existente. Si no hay coincidencia, devuelve None. Comprueba found is None antes de usar found.name; None no tiene ese atributo. Un método __len__ que devuelva len(self.items) también podría hacer que len(cart) contara los objetos artículo almacenados, mientras que __str__ podría unir las líneas de los artículos para formar un recibo.
Sigue las referencias dentro de las colecciones
Una lista almacena referencias, por lo que colocar el mismo artículo en dos carritos no lo copia. Actualizar el precio de ese artículo afecta al siguiente total que calculen ambos carritos. Las listas de carrito independientes evitan compartir listas por accidente, pero no hacen independientes los objetos que contienen. Crea artículos separados cuando los carritos deban contener datos independientes.
Puntos clave
- La composición almacena objetos dentro de otro objeto.
- La delegación pide a un objeto contenido que realice su parte de una tarea.
- Comprueba un objeto antes de almacenarlo cuando los métodos posteriores requieran su tipo.
- Una búsqueda puede devolver el objeto coincidente o
None. - Los contenedores independientes pueden seguir refiriéndose al mismo objeto contenido.
5.2 Haz que los objetos interactúen mediante métodos
Los objetos pueden ser argumentos de métodos y valores de retorno igual que los números o las cadenas. Sigue por separado el objeto receptor, los argumentos y el resultado: un método puede modificar un objeto existente, devolver uno nuevo o simplemente informar de un valor.
Usa otro objeto como entrada
Dentro de una clase de puntos con x e y, un método puede leer ambos puntos:
def midpoint(self, other):
return Point((self.x + other.x) / 2,
(self.y + other.y) / 2)
Para p = Point(3, 4) y q = Point(1, 0), middle = p.midpoint(q) crea un tercer punto en (2.0, 2.0). Durante la llamada, self se refiere a p y other se refiere a q. El método calcula coordenadas y construye un punto nuevo; no asigna nada a ninguno de los originales.
Con import math, un método de distancia puede devolver math.hypot(self.x - other.x, self.y - other.y). Point(0, 0).distance_to(Point(3, 4)) da entonces 5.0. Esta llamada devuelve un número, mientras que midpoint devuelve un objeto.
Deja que cada objeto receptor gestione sus cambios
Dentro de una clase de cuentas con métodos withdraw y deposit que realizan comprobaciones:
def transfer_to(self, other, amount):
self.withdraw(amount)
other.deposit(amount)
Si Maya tiene 100 y Leo tiene 20, maya.transfer_to(leo, 30) primero retira de la cuenta de Maya, dejándola en 70, y después deposita en la de Leo, dejándola en 50. En la primera llamada, self es la cuenta de Maya. En la llamada de depósito, su propio self es la cuenta de Leo.
Una transferencia posterior de 500 falla en withdraw porque Maya solo tiene 70. La ejecución se detiene antes de deposit, por lo que ambos saldos conservan sus valores anteriores. El orden de las llamadas importa. Python no deshace un cambio de estado anterior si una llamada posterior lanza una excepción; este método breve depende de que el depósito de la cuenta receptora pueda aceptar el importe.
Llamar a other.deposit(amount) ejecuta las reglas de la cuenta receptora en un mismo lugar. Cambiar directamente other.balance se las saltaría.
Sigue los objetos, no solo los nombres de las variables
Dibuja cada instancia como una caja que contiene sus atributos. Dibuja una flecha desde cada variable hasta su caja. En una llamada, identifica qué caja recibe self y qué cajas se pasan como argumentos. Actualiza la caja afectada después de cada línea que cambie el estado correctamente. Dos flechas hacia una caja significan que un cambio a través de cualquiera de los nombres llega al mismo objeto.
Una función normal también puede trabajar con varios objetos. total_balance(accounts) puede recorrer las cuentas y sumar sus saldos. Ese cálculo sobre toda la lista no tiene por qué pertenecer a una sola cuenta.
Representa una relación ausente
Un libro puede empezar con self.borrower = None y después almacenar un objeto miembro. Antes de leer un atributo del miembro, comprueba la relación:
if book.borrower is not None:
print(book.borrower.name)
De lo contrario, book.borrower.name falla cuando el libro no tiene prestatario. Las clases pueden definirse encima de una función main() que crea y conecta los objetos. Llamar a main() bajo if __name__ == "__main__": impide que esas acciones se ejecuten al importar las clases.
Puntos clave
- Identifica el objeto receptor como
selfy el argumento de tipo objeto mediante su propio parámetro. - Un método puede devolver un objeto nuevo y dejar intactas sus entradas.
- Las llamadas ocurren en orden; una excepción detiene las llamadas posteriores, pero no deshace los cambios anteriores.
- Usa los métodos públicos de otro objeto para aplicar sus reglas.
- Comprueba si hay
Noneantes de seguir una relación opcional entre objetos. - Una función puede organizar cálculos que abarquen varios objetos.
5.3 Decide cuándo una clase mejora la claridad de un programa
Una clase se justifica cuando resulta más fácil entender juntos los datos relacionados y las operaciones repetidas. No necesitas convertir cada función o registro en una clase. Elige la estructura que deje claras las responsabilidades del programa.
Busca datos y comportamiento que vayan juntos
Las clases ayudan cuando varias funciones reciben continuamente el mismo grupo de valores, muchos registros comparten una estructura y los cambios de estado deben seguir reglas comunes. Varias cuentas que admiten depósitos y retiradas encajan bien. Un cálculo aislado puede seguir siendo una función. Un registro fijo de solo lectura puede seguir siendo una tupla o un diccionario.
Una clase que solo tiene inicialización y una operación que se realiza una vez suele añadir más formalidades que estructura útil. Pregúntate qué representa una instancia y qué acciones repetidas le corresponden. Si no puedes responder esas preguntas de forma sencilla, reconsidera el diseño.
Mueve una operación junto a sus datos
Una cuenta basada en un diccionario podría empezar así:
account = {"owner": "Maya", "balance": 50}
def deposit(account, amount):
account["balance"] += amount
La versión con clase puede colocar la misma operación junto a su estado y sus reglas:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("deposit must be positive")
self.balance += amount
def __repr__(self):
return f"BankAccount({self.owner!r}, {self.balance})"
Después de acct = BankAccount("Maya", 50) y acct.deposit(25), su representación es BankAccount('Maya', 75). La regla de depósito positivo se ejecuta ahora siempre que el código usa ese método. Leer account["balence"] lanzaría KeyError; leer acct.balence lanza AttributeError. Una clase no elimina las erratas, pero da a los datos y a sus operaciones un lugar con nombre.
Planifica a partir de una descripción breve
En «Una biblioteca presta libros a sus miembros. Un miembro puede tener prestados como máximo 3 libros», los sustantivos sugieren Book, Member y Library. Los verbos sugieren métodos de préstamo y devolución.
- Enumera lo que sabe cada instancia: el título, autor y prestatario de un libro; el nombre y los libros prestados de un miembro; los libros y miembros de una biblioteca.
- Identifica las reglas: un libro no puede estar ya prestado y un miembro no puede superar
MAX_LOANS = 3. - Coloca cada regla en la operación que cambia la relación, como
Library.check_out(book, member). - Escribe la inicialización y una representación útil; después añade y prueba una acción cada vez.
Los títulos distintos necesitan instancias distintas de Book, no clases separadas con el nombre de cada título. Usa sustantivos en singular para los objetos individuales, como Book; nombra una colección por lo que representa, como Library. Mantén la tarea de cada clase lo bastante clara como para describirla en una frase.
Prueba el comportamiento observable
acct = BankAccount("Maya", 50)
acct.deposit(25)
assert acct.balance == 75
assert lanza AssertionError si su condición es falsa. Prueba también las acciones rechazadas: captura la excepción esperada y verifica que el estado no haya cambiado. Usa validación explícita con raise para los datos de entrada del código que realiza la llamada; las aserciones son comprobaciones durante el desarrollo y pueden desactivarse cuando Python se ejecuta con optimización.
Puntos clave
- Elige una clase cuando el estado relacionado y las operaciones repetidas se beneficien de estar juntos.
- Mantén un cálculo sencillo como función cuando no se necesite un estado de objeto persistente.
- Planifica los atributos a partir de los datos y los métodos a partir de las acciones y las reglas.
- Modela los distintos valores de datos como instancias de una misma clase.
- Prueba tanto las actualizaciones correctas como los cambios rechazados.
Ejemplos de Programación orientada a objetos en Python
1De los valores conocidos a tu primera clase
11 pasos
- 1IntroducciónEmpieza con objetos que conoces
Una lista, una cadena y un número son objetos. Sus tipos determinan qué operaciones admiten y cada valor tiene su propia identidad.
Empieza siguiendo un método de cadena y uno de lista. Después usa las mismas ideas para crear una clase sencilla cuyas instancias almacenen datos distintos. Sigue haciéndote dos preguntas: ¿a qué objeto accede este nombre y qué devuelve esta llamada?
- 2EjemploPregunta a un valor por su tipo
Python puede decirte qué tipo de objeto es un valor.
text = "river" values = [4, 2] print(type(text)) # <class 'str'> print(isinstance(values, list)) # TrueLa cadena y la lista ofrecen métodos distintos porque tienen tipos distintos. Puedes usar
dir(values)para inspeccionar los nombres disponibles en la lista, incluidos los métodos normales y los especiales. - 3EjemploSigue una cadena devuelta
Un método de cadena puede producir un valor nuevo sin sustituir el original.
name = "robin" result = name.title() print(result) # Robin print(name) # robintitle()devuelve una cadena nueva. La asignación almacena ese resultado con el nombreresult, mientras quenamesigue refiriéndose a"robin". Escribirname = name.title()haría, en cambio, quenamese refiriera a la cadena devuelta. - 4EjemploSigue un cambio sobre la propia lista
Compara la llamada sobre la cadena con la ordenación de una lista.
values = [4, 2] result = values.sort() print(values) # [2, 4] print(result) # NoneLa propia lista cambia y el valor de retorno es
None. Asignar la llamada de nuevo avaluessustituiría la referencia a la lista de ese nombre porNone. Usavalues.sort()por sí solo cuando quieras modificar la lista existente. - 5Desafío¿Dónde aparece el elemento añadido?
Predice las dos listas que se muestran.
a = [7] b = a c = [7] b.append(2) print(a, c)---reveal---
La salida es
[7, 2] [7].aybse refieren a una misma lista, por lo que añadir un elemento a través debmodifica la lista que se ve a través dea.ccontiene una lista independiente. Las listas eran iguales al principio, peroa is cera falso. - 6ConsejoDibuja las referencias antes de seguir los cambios
Dibuja una caja por cada lista creada por separado. Haz que tanto
acomobapunten a la primera caja y quecapunte a la segunda. Añadir un elemento modifica la caja; una asignación cambia adónde apunta un nombre.Distingue también un método de su llamada:
name.titleda un objeto método, mientras quename.title()lo ejecuta. Los paréntesis pueden marcar la diferencia entre tener un comportamiento y obtener su resultado. - 7AnalogíaUn formulario, varias copias rellenadas
Piensa en una clase como un formulario en blanco que describe la información que contiene un tipo de objeto. Una instancia es una copia rellenada con sus propios datos.
Una clase
Badgepuede representar todas las credenciales de un programa. Una instancia contiene el nombre de una persona visitante; otra contiene un nombre distinto. La clase describe el tipo de objeto compartido. No necesitas una clase nueva para cada valor escrito en el formulario. - 8EjemploDefine el tipo y crea dos instancias
Usa una clase mínima mientras aprendes cómo funcionan las instancias.
class Badge: pass first = Badge() second = Badge() first.name = "Rae" second.name = "Ivo"passproporciona un cuerpo vacío. Cada llamada a la clase crea una instancia y las asignaciones con punto almacenan datos en el objeto seleccionado.first is secondes falso.first = Badgedaría, en cambio, otro nombre a la clase sin crear una instancia. - 9DesafíoEncuentra un atributo mal escrito
Continúa con las dos credenciales:
first.nmae = "Tess" print(first.name, second.name)¿Qué aparece?
---reveal---
La salida es
Rae Ivo. La asignación crea un atributo independiente llamadonmae; no cambianame. Leer un atributo que no existe lanzaAttributeError, pero se permite asignar un nombre nuevo. Revisa la escritura en la asignación y en la lectura cuando una lectura dé un resultado inesperado. - 10DesafíoCombina instancias y alias
Continúa con
firstyseconddel ejemplo de las credenciales:copy_name = first copy_name.name = "Noor" print(first.name, second.name)---reveal---
La salida es
Noor Ivo.copy_namees un alias de la primera credencial, por lo que la asignación modifica ese objeto. La segunda credencial sigue siendo independiente. Crear instancias controla cuántos objetos existen; asignar nombres controla a qué objetos acceden esos nombres. - 11RepasoSigue otro programa en papel
Prueba un programa breve con dos llamadas a la clase y un tercer nombre asignado a la primera instancia. Da atributos distintos a los objetos y después cambia uno a través del alias.
Antes de ejecutarlo, dibuja las cajas de los objetos y predice cada lectura. Si una predicción difiere de la salida, inspecciona la escritura de los atributos y las flechas desde los nombres hasta los objetos. Esto distingue un error de inicialización de un error de referencias.
2Construye objetos con datos iniciales fiables
14 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónPrepara una colección de tareas
- 2ExplicaciónDale un inicializador a Python
- 3EjemploRelaciona una llamada con sus parámetros
- 4DesafíoDetecta el atributo ausente
- 5EjemploAcepta una lista inicial opcional
- 6ConsejoComprueba hasta dónde llega la copia
- 7ExplicaciónAñade datos sobre la clase
- 8EjemploSigue una asignación que oculta un atributo
- 9DesafíoCorrige el contador compartido
- 10ExplicaciónRechaza un límite que no puede funcionar
- 11EjemploElige el error y la vía de recuperación
- 12Desafío¿Mostrar un mensaje rechaza la inicialización?
- 13DesafíoConserva el objeto anterior si hay un fallo
- 14RepasoComprueba la inicialización desde el código que la llama
3Controla el estado mediante métodos y propiedades
16 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónHaz útil un contador de existencias
- 2EjemploEmpieza con una cantidad almacenada
- 3ExplicaciónDeja que el objeto receptor proporcione self
- 4DesafíoHaz que un resultado pueda reutilizarse
- 5EjemploAñade existencias después de comprobar la cantidad
- 6EjemploRechaza una retirada antes de restar
- 7DesafíoSigue un cambio a través de un alias
- 8ConexiónCopia el nivel de estado adecuado
- 9ExplicaciónMarca el almacenamiento interno
- 10EjemploLee a través de una propiedad
- 11EjemploPermite la asignación pública con comprobaciones
- 12DesafíoEvita un método de asignación recursivo
- 13Idea claveCalcula un valor cuando se lee
- 14DesafíoCombina un método de asignación con un alias
- 15ConsejoLee juntas la definición y la llamada
- 16RepasoPrueba una operación cada vez
4Haz que los objetos encajen en las expresiones de Python
17 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónElige qué debe significar un objeto
- 2EjemploEmpieza con un punto
- 3EjemploAñade texto para la inspección
- 4DesafíoPredice cómo se muestra una lista
- 5ConsejoDevuelve texto en ambos métodos de visualización
- 6EjemploCompara los valores de las coordenadas
- 7Idea claveDeja a Python las comparaciones no admitidas
- 8Desafío¿Pueden entrar pares iguales en un conjunto?
- 9EjemploElige un orden para las coordenadas
- 10ConexiónEl orden puede pertenecer a una tarea
- 11EjemploSuma sin modificar los operandos
- 12EjemploAdmite ambos órdenes de multiplicación
- 13DesafíoDistingue un resultado nuevo de un alias
- 14ExplicaciónUsa la longitud para una colección
- 15DesafíoPredice la rama de la cola vacía
- 16ConsejoMantén visible el significado
- 17RepasoComprueba cada expresión por separado
5Conecta objetos con tareas claras
15 pasos
Guarda esta unidad para seguir cada Lección Interactiva paso a paso.
- 1IntroducciónConstruye un pedido a partir de partes más pequeñas
- 2EjemploDale a una línea su propio cálculo
- 3EjemploAlmacena líneas en un pedido
- 4EjemploDelega cada cálculo de coste
- 5DesafíoSigue una línea compartida por dos pedidos
- 6ConsejoComprueba si falta el resultado de la búsqueda
- 7ExplicaciónPasa un objeto a otro método
- 8EjemploSigue el objeto receptor y el resultado
- 9Desafío¿Dónde se detiene una combinación fallida?
- 10ConexiónSigue llamadas que modifican dos objetos
- 11ExplicaciónDecide qué comportamientos deben estar juntos
- 12DesafíoEvita una clase para cada producto
- 13EjemploPrueba el punto de entrada público con comprobaciones
- 14DesafíoElige una prueba para el diseño combinado
- 15RepasoPlanifica tu propia colaboración pequeña
Ejercicios de Programación orientada a objetos en Python
Set 1: Métodos, cambios de estado y propiedades10 Preguntas de Práctica
Pregunta 1 de 10
self cuando el programa llama a panel.area()?Mostrar respuestaOcultar respuesta
panel proporciona self. El método puede leer entonces los atributos de ese panel. La clase contiene la definición del método, pero el objeto clase no es el objeto receptor que proporciona esta llamada de instancia.Pregunta 2 de 10
¿Qué cambio permite al código que realiza la llamada calcular double?
class Pace:
def __init__(self, minutes):
self.minutes = minutes
def seconds(self):
print(self.minutes * 60)
p = Pace(3)
double = p.seconds() * 2
Mostrar respuestaOcultar respuesta
seconds() muestra actualmente un número y devuelve None, por lo que multiplicar su resultado por 2 falla. Devolver self.minutes * 60 proporciona el número al código que realiza la llamada y hace que double sea igual a 360. Quitar los paréntesis daría un objeto método, que tampoco es el número calculado.Pregunta 3 de 10
"12,14"?Mostrar respuestaOcultar respuesta
TypeError comunica esa incompatibilidad. ValueError encajaría con una lista cuyo contenido incumpliera una regla de valores permitidos, pero la cadena ni siquiera cumple el tipo de entrada requerido.Pregunta 4 de 10
¿Qué muestra este código?
class Tank:
def __init__(self):
self.level = 5
def add(self, amount):
self.level += amount
a = Tank()
b = a
result = b.add(4)
print(a.level, result)
Mostrar respuestaOcultar respuesta
a y b se refieren al mismo tanque, así que añadir a través de b cambia a 9 el nivel que se ve a través de a. El método no tiene sentencia return, por lo que result es None. Un atributo modificado no se convierte automáticamente en el valor de retorno del método.Pregunta 5 de 10
¿Qué muestra este código?
import copy
class Log:
def __init__(self):
self.entries = []
a = Log()
b = copy.copy(a)
b.entries.append("open")
b.entries = ["closed"]
print(a.entries, b.entries, a is b)
Mostrar respuestaOcultar respuesta
entries. Añadir el elemento modifica esa lista compartida y después la asignación da a b una lista diferente. Esperar que a.entries siga vacía confunde una copia superficial con una copia de todos los objetos mutables anidados.Pregunta 6 de 10
name?Mostrar respuestaOcultar respuesta
name original antes de limpiar los espacios acepta una cadena que solo contiene espacios, porque una cadena no vacía es verdadera en una condición.Pregunta 7 de 10
¿Por qué asignar a reading.value llama repetidamente a este método de asignación?
class Reading:
@property
def value(self):
return self._value
@value.setter
def value(self, number):
self.value = number
Mostrar respuestaOcultar respuesta
self.value pasa por el mismo método de asignación de la propiedad, así que el método se llama a sí mismo hasta que la recursión falla. Almacena el valor con un nombre distinto, como self._value. El método de consulta ya usa ese nombre de almacenamiento; cambiarlo a self.value también crearía recursión en las lecturas.Pregunta 8 de 10
remove(n) debe rechazar cantidades no positivas y retiradas mayores que el valor actual. ¿Qué secuencia deja intacto el estado ante cualquiera de los dos rechazos?Mostrar respuestaOcultar respuesta
n sea positivo y que haya suficiente valor disponible hace que una retirada fallida deje intacto el contador. Restar antes de la última comprobación puede dejar un valor negativo o aumentado incluso cuando el método lanza una excepción.Pregunta 9 de 10
¿Qué muestra este código?
class Dial:
def __init__(self, value):
self.value = value
@property
def value(self):
return self._value
@value.setter
def value(self, number):
if not 0 <= number <= 9:
raise ValueError("out of range")
self._value = number
d = Dial(4)
try:
d.value = 12
except ValueError:
pass
print(d.value)
Mostrar respuestaOcultar respuesta
4 inicial supera la comprobación, pero 12 lanza una excepción antes de asignar _value, por lo que el valor mostrado sigue siendo 4. El método de asignación rechaza la entrada; no la ajusta al máximo de 9.Pregunta 10 de 10
area con una propiedad de solo lectura a partir de width y height. Un objeto tiene anchura 6 y altura 2; después se asigna 8 a su anchura. ¿Qué devuelve leer obj.area?Mostrar respuestaOcultar respuesta
8 * 2, es decir, 16. El resultado anterior 12 solo permanecería si el área se almacenara una vez y nunca se actualizara. Solo lectura describe la asignación a la propiedad, no si su resultado calculado puede variar.Set 2: Objetos, referencias e inicialización fiable10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
¿Qué contiene first.name al final?
class Badge:
pass
first = Badge()
second = Badge()
first.name = "Rae"
second.name = "Ivo"
first.nmae = "Sol"
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Qué muestra este código?
label = "cedar"
label.upper()
print(label)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Qué muestra este código?
a = [6, 2]
b = a
a = sorted(a)
b.append(9)
print(a, b)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
¿Qué muestra este código?
class Counter:
def __init__(self, start=4):
self.value = start
c = Counter(9)
print(c.value)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
¿Por qué falla la última línea?
class Lamp:
def __init__(self, brightness):
brightness = brightness
lamp = Lamp(7)
print(lamp.brightness)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
¿Qué muestra este código?
class Folder:
def __init__(self, tags=[]):
self.tags = tags
a = Folder()
b = Folder()
a.tags.append("urgent")
a.tags = []
print(b.tags, a.tags)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
¿Qué muestra este código?
class Ticket:
fee = 3
x = Ticket()
y = Ticket()
x.fee = 8
Ticket.fee = 5
print(x.fee, y.fee)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
¿Qué muestra este código?
class Job:
count = 0
def __init__(self):
self.count += 1
a = Job()
Job.count = 10
b = Job()
print(a.count, b.count, Job.count)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
¿Qué muestra este código?
class Batch:
def __init__(self, size):
if size <= 0:
raise ValueError("size must be positive")
self.size = size
batch = Batch(6)
try:
batch = Batch(0)
except ValueError:
pass
print(batch.size)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 3: Representaciones legibles y operadores útiles10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
__str__ como __repr__. ¿Qué método proporciona el texto de una instancia cuando aparece dentro de una lista mostrada?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Qué muestra este código?
class Tag:
def __init__(self, text):
self.text = text
def __repr__(self):
return f"Tag({self.text!r})"
t = Tag("ok")
print(t)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
str(marker) lanza TypeError cuando su método __str__ solo llama a print(self.code)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
Label define la igualdad por su texto y no define __hash__. Dos etiquetas contienen el mismo texto. ¿Qué ocurre cuando se introducen en un conjunto?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Mark solo define __lt__, que compara puntuaciones numéricas, y ninguna otra comparación personalizada. Para a con puntuación 2 y b con puntuación 7, ¿qué ocurre?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
Vector define __mul__ para vector * number, pero ningún método de multiplicación reflejada. ¿Qué método debe añadirse para admitir también number * vector?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
__eq__ usando code y __hash__ como hash(self.code). Sus objetos se usan como claves de diccionario. ¿Qué cambio de diseño hace fiable el comportamiento de las claves?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Queue define __len__ como el número de trabajos pendientes y no define __bool__. Sin trabajos pendientes, ¿cuáles son los valores de len(q) y bool(q)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
¿Qué muestra la última línea?
class Offset:
def __init__(self, value):
self.value = value
def __add__(self, other):
return Offset(self.value + other.value)
a = Offset(6)
b = a
a = a + Offset(4)
print(a.value, b.value, a is b)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
Rank.__lt__ ordena registros por prioridad. Un informe debe ordenar esos registros por fecha y hora de creación sin cambiar su orden habitual. ¿Qué enfoque encaja?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 4: Objetos que colaboran y diseños que mantienen la claridad10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
Workshop almacena objetos Tool en self.tools. ¿Qué lee workshop.tools[0].label?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
line.cost() para cada línea almacenada. Un descuento por volumen cambia cómo se calcula el coste de cada línea. ¿Dónde debe ir esa regla?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Qué total muestra el código?
class Line:
def __init__(self, rate, count):
self.rate = rate
self.count = count
def cost(self):
return self.rate * self.count
x = Line(4, 3)
a = [x]
b = [x, Line(2, 5)]
a[0].count = 6
print(sum(line.cost() for line in b))
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
find(name) de una colección devuelve un objeto si tiene éxito y None si falla. ¿Qué código gestiona ambos casos antes de leer result.name?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Position.midpoint(other) devuelve una posición nueva cuyas coordenadas son las medias. Si a está en (2, 8) y b en (10, 4), ¿qué deja c = a.midpoint(b)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
send_to(other, n) llama a self.remove(n) y después a other.add(n). remove rechaza una cantidad mayor que las existencias del emisor antes de cambiar nada; add acepta cantidades positivas. Partiendo de existencias de 9 en el emisor y 4 en el receptor, un envío de 3 se realiza correctamente; después se intenta un envío de 8 y se captura su excepción. ¿Qué existencias quedan?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
add del receptor. El receptor rechaza la cantidad antes de cambiar sus propias existencias. No hay código que revierta los cambios y el código que realiza la llamada captura la excepción. ¿Qué estado queda?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
La estantería debe rechazar un tercer libro y conservar sus dos libros existentes. Después del error capturado a continuación, ¿qué aserción revela la implementación incorrecta?
class Shelf:
def __init__(self):
self.limit = 2
self.books = []
def add(self, title):
self.books.append(title)
if len(self.books) > self.limit:
raise ValueError("shelf full")
shelf = Shelf()
shelf.add("Atlas")
shelf.add("Birch")
try:
shelf.add("Cove")
except ValueError:
pass
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Set 5: Aplica las ideas en situaciones nuevas10 Preguntas de Práctica
Guarda esta unidad para ver todas las respuestas y practicar cada set.
Pregunta 1 de 10
items, ¿qué devuelve isinstance(items, list)?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 2 de 10
¿Qué muestra este código?
class Tray:
labels = []
a = Tray()
b = Tray()
a.labels.append("red")
b.labels = ["blue"]
Tray.labels.append("green")
print(a.labels, b.labels)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 3 de 10
¿Qué muestra este código?
class Kit:
def __init__(self, parts=None):
self.parts = [] if parts is None else list(parts)
source = [[1]]
k = Kit(source)
source.append([2])
source[0].append(3)
print(k.parts)
Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 4 de 10
_reading. Su inicializador asigna directamente a self._reading, así que Meter(-2) funciona. ¿Qué cambio hace que la creación use la misma comprobación que las asignaciones públicas posteriores?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 5 de 10
Key compara la igualdad solo por name y su hash también usa name. Dos instancias distintas empiezan con el nombre "north"; una se convierte en clave de diccionario. ¿Por qué debe mantenerse estable el nombre de esa clave?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 6 de 10
members independiente, pero ambas listas contienen la misma instancia de Member. Un club cambia el nombre de ese miembro. Ahora los clubes necesitan nombres de miembro independientes. ¿Qué cambio hace falta?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 7 de 10
__str__ útil, pero una lista de mensajes mostrada sigue presentando descripciones de objeto predeterminadas. ¿Qué añadido mejora esas entradas de la lista?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 8 de 10
a.combine(b) debe dejar intactas ambas entradas. Su cuerpo llama a self.parts.extend(other.parts) y después devuelve self. ¿Qué comportamiento tiene realmente esa implementación?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 9 de 10
class Vault, la inicialización asigna self.__code = "x". ¿Qué explica que vault.__code falle mientras que vault._Vault__code puede funcionar?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
Pregunta 10 de 10
take de una clase usa assert n > 0 como única comprobación de los datos del código que lo llama. La regla debe cumplirse incluso cuando Python se ejecute con optimización. ¿Qué cambio hace falta?Mostrar respuestaOcultar respuesta
Guarda esta unidad para ver esta respuesta.
¿Te atascas con una pregunta tuya? Ayuda con Tareas
Términos Clave
A
Alias
backup = entries, añadir elementos a través de backup también modifica la lista a la que se accede mediante entries.Aptitud para hash
__eq__ sin __hash__ deja de ser apta para hash, aunque sus instancias funcionen en una lista.Argumento de tipo objeto
other en p.midpoint(q) se refiere a q, cuyas coordenadas lee el método para calcular su resultado.Argumento predeterminado
balance=0 en el inicializador, Account("Rae") comienza con cero sin proporcionar un argumento de saldo.Aserción
AssertionError si su condición es falsa; Python puede omitirla en una ejecución con optimización.assert acct.balance == 75 comprueba el comportamiento; las entradas rechazadas del código que realiza la llamada siguen necesitando validación explícita.Atributo
book.title = "Emma" almacena un título que después puede leerse evaluando book.title.Atributo de almacenamiento de una propiedad
score lee _score; almacenar en score dentro de su propio método de asignación llamaría repetidamente a ese método.Atributo de clase
MAX_TOPPINGS de toda la clase puede proporcionar el límite predeterminado que se lee a través de muchos objetos pizza distintos.Atributo de instancia
size distinto y una lista de ingredientes independiente mediante asignaciones en self.Atributo interno
balance, mientras que los métodos propios de la clase gestionan el valor almacenado _balance.B
Búsqueda de atributos
p no tiene un limit propio, leer p.limit puede encontrar el valor compartido limit definido en su clase.C
Clase
Book puede crear muchos libros, cada uno con un título distinto, pero con las mismas definiciones de métodos.Composición
Condición de ejecución directa
main() bajo if __name__ == "__main__": para que otro programa pueda importar las clases sin iniciarlo.Contenedor
cart.items[0] permite acceder a su primer artículo almacenado.Copia profunda
copy.deepcopy(account) también copia esa lista, por lo que añadir una entrada al historial de la copia deja intacto el original.Copia superficial
copy.copy(account) puede seguir compartiendo esa lista con la original.D
Delegación
Diseño de clases
E
Encapsulación
Estado
F
Función de clave de ordenación
sorted(records, key=by_date) sin cambiar el método de comparación habitual de la clase de registros.I
Identidad
is comprueba si dos referencias acceden a ese mismo objeto.a == b mientras que a is b es falso porque sus identidades difieren.Igualdad de valores
__eq__, en lugar de exigir que tengan la misma identidad de objeto.Inicialización
Instancia
Book() y Book() crean dos instancias de libro independientes, aunque ambas tengan el mismo tipo.Invariante
0 y 100 durante la inicialización y las asignaciones posteriores.M
Método
"cedar".upper() ejecuta un método de cadena, mientras que scores.append(7) ejecuta un método sobre una lista.Método de asignación de una propiedad
score que rechaza 105 antes de asignar _score deja intacta una puntuación anterior válida.Método de consulta
area() de un rectángulo lee su anchura y altura y devuelve un número sin cambiar su tamaño.Método de instancia
self.r.area(), Python proporciona r al método para que pueda multiplicar la anchura y la altura de ese rectángulo.Método especial
__str__ al convertir un objeto a su forma de cadena legible y __len__ para obtener su longitud.Método modificador
Método vinculado
r.area obtiene un método vinculado; añadir paréntesis como en r.area() lo ejecuta y devuelve el área calculada.Mutabilidad
upper() a una cadena devuelve una cadena nueva.N
None
None es el valor único de Python para un resultado ausente o no disponible y también sirve para una relación que todavía no tiene ningún objeto asignado.borrower = None hasta que un miembro lo tome prestado, así que el código lo comprueba antes de leer el nombre del prestatario.Notación de punto
cart.items, el nombre anterior al punto identifica el objeto y el nombre posterior selecciona su atributo de artículos.NotImplemented
NotImplemented es un valor de retorno especial que indica que un método de comparación o numérico no admite los operandos proporcionados.NotImplemented para una cadena y dejar que Python gestione el par no admitido.O
Objeto
"hello" y la lista [2, 5] son objetos, aunque sus tipos ofrecen operaciones distintas.Objeto receptor
self del método.sender.transfer_to(receiver, 5), sender recibe la llamada de transferencia; el argumento de cuenta es el destino.Ocultación de nombres
p.limit = 8, cambiar Pizza.limit no sustituye el límite independiente ya almacenado en p.Operación reflejada
3 * vector, Python puede usar el __rmul__ del vector cuando la multiplicación de enteros no puede gestionar ese vector.P
Propiedad
student.score puede llamar a un método de consulta y asignar student.score = 90 puede invocar un método de asignación que valide el valor.Prueba de comportamiento
R
Referencia
Representación alternativa predeterminada
__repr__ personalizado cuando una clase no define un __str__ personalizado.__repr__ puede mejorar tanto print(point) como la apariencia del punto dentro de una lista mostrada.Representación en cadena
__str__, normalmente destinado a los usuarios del programa."(3, 4)" al visualizarlo directamente, dejando intactos sus atributos numéricos de coordenadas.Representación para programación
__repr__ y que se usa para la inspección y dentro de la visualización de contenedores."Book('Emma', 'Jane Austen', 1815)", incluidos los campos de texto entrecomillados.Responsabilidad
Resultado de búsqueda
None que indica que no se ha encontrado ninguna coincidencia.found is not None antes de leer found.name en una búsqueda de artículos del carrito.S
self
self recibe por convención la instancia actual y permite que un método lea o modifique los atributos de ese objeto.self.owner = owner, el lado izquierdo almacena datos en la instancia y el derecho lee un parámetro local.Sobrecarga de operadores
__add__ de un vector puede hacer que a + b produzca un vector nuevo formado al sumar sus coordenadas correspondientes.T
Tipo
type([2, 5]) devuelve la clase list e isinstance([2, 5], list) confirma que el valor es una lista.Transformación de nombres
Acc, self.__balance se convierte en _Acc__balance, al que se puede seguir accediendo con el nombre transformado.TypeError
TypeError indica que una operación ha recibido un tipo inadecuado o se ha llamado con argumentos que su definición no acepta.TypeError porque esa comparación no puede ordenar texto y cero.V
Validación
Valor de retorno
r1.area() + r2.area() sume los resultados, mientras que solo mostrarla deja None como resultado de cada llamada.Valor de verdad en un contexto booleano
__bool__, una longitud definida de cero hace que sea falso.__len__ y sin un __bool__ personalizado hace que if playlist: omita su cuerpo.Valor predeterminado mutable
items=[] se almacena directamente, dos objetos creados sin ese argumento pueden referirse a la misma lista.ValueError
ValueError indica que un argumento tiene un tipo aceptable, pero su valor está fuera de lo que acepta la operación.-2 puede lanzar ValueError con el mensaje "width must be positive".#
__init__
__init__ inicializa una instancia después de que Python la cree y debe devolver None cuando termina la inicialización.Counter(5) pasa la instancia nueva y 5 a su inicializador antes de devolver el contador configurado.__lt__
__lt__ implementa la comparación menor que y proporciona un orden que pueden usar operaciones como sorted.__lt__ basado en la puntuación permite ordenar una lista de objetos estudiante de menor a mayor puntuación.Sobre esta unidad
Crea tus propios Materiales de Estudio
Convierte tus notas, diapositivas y documentos en Guías de Estudio, Preguntas de Práctica, Flashcards y 6 herramientas más.