Пример проекта на Python, который использует ООП
| Введение | |
| Структура | |
| Взаимодействие двух классов | |
| Полиморфизм | |
| Наследование | |
| Далее по теме |
Введение
Разберём пример создание классов для описания перелётов на пассажирских авиалиниях.
Если вы ищете дешёвые билеты - воспользуйтесь
Авиасейлз
Прочитать про
isalpha()
,
isupper()
и как
обрезаются строки
можно в статье
string
Подробный разбор способа, которым
печатается схема мест в самолёте
вы можете изучить
здесь
Структура
Создадим два файла airtravel.py , в котором будут классы для работы с бронированием и search.py из которого мы будет делать запросы.
airtravel/ |-- airtravel.py `-- search.py
"""(airtravel.py) Model for aircraft flights.""" class Flight: def __init__(self, number): if not number[:2].isalpha(): raise ValueError(f"No airline code in '{number}'") if not number[:2].isupper(): raise ValueError(f"Invalid airline code '{number}'") if not (number[2:].isdigit() and int(number[2:]) <= 9999): raise ValueError(f"Invalid route number '{number}'") self._number = number def number(self): return "SN060"
# search.py from airtravel import Flight f = Flight("SN060") print(dir(f)) print(f.number())
['__class__', … ,'_number', 'number'] SN060
У экземпляра f есть атрибут _number и метод number() который был успешно вызван.
Разницу между _number и number(), который в этом примере возвращает всё время "SN060" легко увидеть обратившись к обоим по очереди
# search.py from airtravel import Flight f = Flight("AB060") print(f.number()) print(f._number)
SN060 AB060
Можно прямо в интерактивном режиме проверить, что рейс создается только с номером вида от AA1 до ZZ9999
>>> from airtravel import Flight >>> f = Flight("SN060") >>> f = Flight("060") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 7, in __init__ raise ValueError(f"No airline code in '{number}'") ValueError: No airline code in '060' >>> f = Flight("sn060") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 10, in __init__ raise ValueError(f"Invalid airline code '{number}'") ValueError: Invalid airline code 'sn060' >>> f = Flight("snabc") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 10, in __init__ raise ValueError(f"Invalid airline code '{number}'") ValueError: Invalid airline code 'snabc' >>> f = Flight("SN12345") Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 13, in __init__ raise ValueError(f"Invalid route number '{number}'") ValueError: Invalid route number 'SN12345'
Добавим класс Aircraft для самолётов
class Aircraft: def __init__(self, registration, model, num_rows, num_seats_per_row): self._registration = registration self._model = model self._num_rows = num_rows self._num_seats_per_row = num_seats_per_row def registration(self): return self._registration def model(self): return self._model # Letter I is not in use to avoid # collisions with number 1 def seating_plan(self): return (range(1, self._num_rows + 1), "ABCDEFGHJK"[:self._num_seats_per_row])
>>> from airtravel import * >>> a = Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6) >>> a.registration() 'G-EUPT' >>> a.model() 'Airbus A319' >>> a.seating_plan() (range(1, 23), 'ABCDEF')
Взаимодействие двух классов
Свяжем два класса вместе - теперь объект класса Flight будет получать
новый атрибут aircraft, который будет объектом класса Aircraft.
Метод Flight.number() будет возвращать self._number вместо константы.
"""Model for aircraft flights.""" class Flight: """ A flight with a particular passenger aircraft.""" def __init__(self, number, aircraft): if not number[:2].isalpha(): raise ValueError(f"No airline code in '{number}'") if not number[:2].isupper(): raise ValueError(f"Invalid airline code '{number}'") if not (number[2:].isdigit() and int(number[2:]) <= 9999): raise ValueError(f"Invalid route number '{number}'") self._number = number self._aircraft = aircraft def aircraft_model(self): return self._aircraft.model() def number(self): return self._number def airline(self): return self._number[:2] class Aircraft: def __init__(self, registration, model, num_rows, num_seats_per_row): self._registration = registration self._model = model self._num_rows = num_rows self._num_seats_per_row = num_seats_per_row def registration(self): return self._registration def model(self): return self._model # Letter I is not in use to avoid # collisions with number 1 def seating_plan(self): return (range(1, self._num_rows + 1), "ABCDEFGHJK"[:self._num_seats_per_row])
>>> from airtravel import * >>> f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) >>> f.aircraft_model() 'Airbus A319'
Видно, что объект f класса Flight успешно переиспользует метод model() класса Aircraft но под своим названием aircraft_model()
Прежде чем двигаться к более сложному коду убедимся, что всё понятно в уже написанном
Подробный разбор способа, которым
печатается схема мест в самолёте
вы можете изучить
здесь
Добавим в метод Flight.__init__() создание схемы самолёта в зависимости от модели.
rows, seats = self._aircraft.seating_plan() self._seating = [None] + [{letter: None for letter in seats} for _ in rows]
Теперь полный код выглядит следующим образом
"""Model for aircraft flights.""" class Flight: """ A flight with a particular passenger aircraft.""" def __init__(self, number, aircraft): if not number[:2].isalpha(): raise ValueError(f"No airline code in '{number}'") if not number[:2].isupper(): raise ValueError(f"Invalid airline code '{number}'") if not (number[2:].isdigit() and int(number[2:]) <= 9999): raise ValueError(f"Invalid route number '{number}'") self._number = number self._aircraft = aircraft rows, seats = self._aircraft.seating_plan() self._seating = [None] + [{letter: None for letter in seats} for _ in rows] def aircraft_model(self): return self._aircraft.model() def number(self): return self._number def airline(self): return self._number[:2] class Aircraft: def __init__(self, registration, model, num_rows, num_seats_per_row): self._registration = registration self._model = model self._num_rows = num_rows self._num_seats_per_row = num_seats_per_row def registration(self): return self._registration def model(self): return self._model # Letter I is not in use to avoid # collisions with number 1 def seating_plan(self): return (range(1, self._num_rows + 1), "ABCDEFGHJK"[:self._num_seats_per_row])
from pprint import pp from airtravel import * f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) pp(f._seating)
[None, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}]
Добавим классу Flight метод, позволяющий занимать места в салоне самолёта
def allocate_seat(self, seat, passenger): """Allocate a seat to a passenger. Args: seat: A seat designator such as '12C' or '21F'. passenger: The passenger name. Raises: ValueError: If the seat is unavailable. """ rows, seat_letters = self._aircraft.seating_plan() letter = seat[-1] if letter not in seat_letters: raise ValueError(f"Invalid seat letter {letter}") row_text = seat[:-1] try: row = int(row_text) except ValueError: raise ValueError(f"Invalid seat row {row_text}") if row not in rows: raise ValueError(f"Invalid row number {row}") if self._seating[row][letter] is not None: raise ValueError(f"Seat {seat} already occupied") self._seating[row][letter] = passenger
Протестируем проверку правильности ввода места
from airtravel import * f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) f.allocate_seat("12A", "Guido van Rossum") f.allocate_seat("12A", "Rasmus Lerdorf")
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 56, in allocate_seat raise ValueError(f"Seat {seat} already occupied") ValueError: Seat 12A already occupied
f.allocate_seat("E27", "Yukihiro Matsumoto")
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 44, in allocate_seat raise ValueError(f"Invalid seat letter {letter}") ValueError: Invalid seat letter 7
f.allocate_seat("DD", "Larry Wall")
Traceback (most recent call last): File "/home/andrei/airtravel/airtravel.py", line 48, in allocate_seat row = int(row_text) ValueError: invalid literal for int() with base 10: 'D' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 50, in allocate_seat raise ValueError(f"Invalid seat row {row_text}") ValueError: Invalid seat row D
Рассадим несколько пассажиров и проверим схему рассадки
from pprint import pp from airtravel import * f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) f.allocate_seat("15F", "Bjarne Stroustrup") f.allocate_seat("15E", "Anders Hejlsberg") f.allocate_seat("1C", "John McCarthy") f.allocate_seat("1D", "Richard Hickey") pp(f._seating)
[None, {'A': None, 'B': None, 'C': 'John McCarthy', 'D': 'Richard Hickey', 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': 'Guido van Rossum', 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': 'Anders Hejlsberg', 'F': 'Bjarne Stroustrup'}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}, {'A': None, 'B': None, 'C': None, 'D': None, 'E': None, 'F': None}]
Добавим метод
relocate_passenger()
, позволяющий пересаживать пассажиров с их мест на другие свободные.
Сперва выделим проверку места в отдельный метод
_parse_seat()
def allocate_seat(self, seat, passenger): """Allocate a seat to a passenger. Args: seat: A seat designator such as '12C' or '21F'. passenger: The passenger name. Raises: ValueError: If the seat is unavailable. """ row, letter = self._parse_seat(seat) if self._seating[row][letter] is not None: raise ValueError(f"Seat {seat} already occupied") self._seating[row][letter] = passenger def _parse_seat(self, seat): rows, seat_letters = self._aircraft.seating_plan() letter = seat[-1] if letter not in seat_letters: raise ValueError(f"Invalid seat letter {letter}") row_text = seat[:-1] try: row = int(row_text) except ValueError: raise ValueError(f"Invalid seat row {row_text}") if row not in rows: raise ValueError(f"Invalid row number {row}") return row, letter def relocate_passenger(self, from_seat, to_seat): """Relocate a passenger to a different seat. Args: from_seat: The existing seat designator for the passenger to be moved. to_seat: The new seat designator """ from_row, from_letter = self._parse_seat(from_seat) if self._seating[from_row][from_letter] is None: raise ValueError(f"No passenger to relocate in seat {from_seat}") to_row, to_letter = self._parse_seat(to_seat) if self._seating[to_row][to_letter] is not None: raise ValueError(f"Seat {to_seat} already occupied") self._seating[to_row][to_letter] = self._seating[from_row][from_letter] self._seating[from_row][from_letter] = None
Также напишем для search.py вспомогательную функцию make_flight() с помощью которой будем быстро создавать тестовый рейс
# search.py from pprint import pp from airtravel import * def make_flight(): f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) f.allocate_seat("12A", "Guido van Rossum") f.allocate_seat("15F", "Bjarne Stroustrup") f.allocate_seat("15E", "Anders Hejlsberg") f.allocate_seat("1C", "John McCarthy") f.allocate_seat("1D", "Rich Hickey") return f if __name__ == "__main__": f = make_flight() f.relocate_passenger("12A", "15D") pp(f._seating)
После релокации Guido оказывается в одном ряду с Anders и Bjarne
… {'A': None, 'B': None, 'C': None, 'D': 'Guido van Rossum', 'E': 'Anders Hejlsberg', 'F': 'Bjarne Stroustrup'}, …
Напишем метод, который показывает число свободных мест
def num_available_seats(self): return sum(sum(1 for s in row.values() if s is None) for row in self._seating if row is not None)
# search.py … if __name__ == "__main__": f = make_flight() print(f.num_available_seats())
127
Добавим возможность печатать посадочные талоны.
В класс Flight добавим методы
make_boarding_cards() и _passenger_seats()
def make_boarding_cards(self, card_printer): for passenger, seat in sorted(self._passenger_seats()): card_printer(passenger, seat, self.number(), self.aircraft_model()) def _passenger_seats(self): """An iterable series of passenger seating locations.""" row_numbers, seat_letters = self._aircraft.seating_plan() for row in row_numbers: for letter in seat_letters: passenger = self._seating[row][letter] if passenger is not None: yield (passenger, f"{row}{letter}")
И отдельно от класса создадим функцию console_card_printer()
def console_card_printer(passenger, seat, flight_number, aircraft): output = f"| Name: {passenger}" \ f" Flight: {flight_number}" \ f" Seat: {seat}" \ f" Aircraft: {aircraft}" \ " |" banner = "+" + "-" * (len(output) - 2) + "+" border = "|" + " " * (len(output) - 2) + "|" lines = [banner, border, output, border, banner] card = "\n".join(lines) print(card) print()
>>> from airtravel import console_card_printer, make_flight >>> f = make_flight() >>> f.make_boarding_cards(console_card_printer) +-------------------------------------------------------------------------+ | | | Name: Anders Hejlsberg Flight: BA758 Seat: 15E Aircraft: Airbus A319 | | | +-------------------------------------------------------------------------+ +--------------------------------------------------------------------------+ | | | Name: Bjarne Stroustrup Flight: BA758 Seat: 15F Aircraft: Airbus A319 | | | +--------------------------------------------------------------------------+ +-------------------------------------------------------------------------+ | | | Name: Guido van Rossum Flight: BA758 Seat: 12A Aircraft: Airbus A319 | | | +-------------------------------------------------------------------------+ +---------------------------------------------------------------------+ | | | Name: John McCarthy Flight: BA758 Seat: 1C Aircraft: Airbus A319 | | | +---------------------------------------------------------------------+ +-------------------------------------------------------------------+ | | | Name: Rich Hickey Flight: BA758 Seat: 1D Aircraft: Airbus A319 | | | +-------------------------------------------------------------------+
Полный код версия 1 (пропустить)
"""Model for aircraft flights.""" class Flight: """ A flight with a particular passenger aircraft.""" def __init__(self, number, aircraft): if not number[:2].isalpha(): raise ValueError(f"No airline code in '{number}'") if not number[:2].isupper(): raise ValueError(f"Invalid airline code '{number}'") if not (number[2:].isdigit() and int(number[2:]) <= 9999): raise ValueError(f"Invalid route number '{number}'") self._number = number self._aircraft = aircraft rows, seats = self._aircraft.seating_plan() self._seating = [None] + [{letter: None for letter in seats} for _ in rows] def aircraft_model(self): return self._aircraft.model() def number(self): return self._number def airline(self): return self._number[:2] def allocate_seat(self, seat, passenger): """Allocate a seat to a passenger. Args: seat: A seat designator such as '12C' or '21F'. passenger: The passenger name. Raises: ValueError: If the seat is unavailable. """ row, letter = self._parse_seat(seat) if self._seating[row][letter] is not None: raise ValueError(f"Seat {seat} already occupied") self._seating[row][letter] = passenger def _parse_seat(self, seat): rows, seat_letters = self._aircraft.seating_plan() letter = seat[-1] if letter not in seat_letters: raise ValueError(f"Invalid seat letter {letter}") row_text = seat[:-1] try: row = int(row_text) except ValueError: raise ValueError(f"Invalid seat row {row_text}") if row not in rows: raise ValueError(f"Invalid row number {row}") return row, letter def relocate_passenger(self, from_seat, to_seat): """Relocate a passenger to a different seat. Args: from_seat: The existing seat designator for the passenger to be moved. to_seat: The new seat designator """ from_row, from_letter = self._parse_seat(from_seat) if self._seating[from_row][from_letter] is None: raise ValueError(f"No passenger to relocate in seat {from_seat}") to_row, to_letter = self._parse_seat(to_seat) if self._seating[to_row][to_letter] is not None: raise ValueError(f"Seat {to_seat} already occupied") self._seating[to_row][to_letter] = self._seating[from_row][from_letter] self._seating[from_row][from_letter] = None def num_available_seats(self): return sum(sum(1 for s in row.values() if s is None) for row in self._seating if row is not None) def make_boarding_cards(self, card_printer): for passenger, seat in sorted(self._passenger_seats()): card_printer(passenger, seat, self.number(), self.aircraft_model()) def _passenger_seats(self): """An iterable series of passenger seating locations.""" row_numbers, seat_letters = self._aircraft.seating_plan() for row in row_numbers: for letter in seat_letters: passenger = self._seating[row][letter] if passenger is not None: yield (passenger, f"{row}{letter}") class Aircraft: def __init__(self, registration, model, num_rows, num_seats_per_row): self._registration = registration self._model = model self._num_rows = num_rows self._num_seats_per_row = num_seats_per_row def registration(self): return self._registration def model(self): return self._model # Letter I is not in use to avoid # collisions with number 1 def seating_plan(self): return (range(1, self._num_rows + 1), "ABCDEFGHJK"[:self._num_seats_per_row]) def console_card_printer(passenger, seat, flight_number, aircraft): output = f"| Name: {passenger}" \ f" Flight: {flight_number}" \ f" Seat: {seat}" \ f" Aircraft: {aircraft}" \ " |" banner = "+" + "-" * (len(output) - 2) + "+" border = "|" + " " * (len(output) - 2) + "|" lines = [banner, border, output, border, banner] card = "\n".join(lines) print(card) print() def make_flight(): f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) f.allocate_seat("12A", "Guido van Rossum") f.allocate_seat("15F", "Bjarne Stroustrup") f.allocate_seat("15E", "Anders Hejlsberg") f.allocate_seat("1C", "John McCarthy") f.allocate_seat("1D", "Rich Hickey") return f if __name__ == '__main__': f = make_flight() print(f.num_available_seats())
Полиморфизм
Функция make_boarding_cards() не полагается на конкретные типы данных. Только на порядок
аргументов что является довольно абстрактным описанием интерфейса.
console_card_printer() можно заменить на другую функцию, которая будет возвращать
похожие объекты в том же порядке и make_boarding_cards() не сломается.
Можно или нельзя использовать объект определяется при использовании. Этим Python отличается
от статически типизированных языков вроде
Java
Временно удалим класс Aircraft и вместо него создадим два класса для конкретных моделей.
class AirbusA319: def __init__(self, registration): self._registration = registration def registration(self): return self._registration def model(self): return "Airbus A319" def seating_plan(self): return range(1, 23), "ABCDEF" class Boeing777: def __init__(self, registration): self._registration = registration def registration(self): return self._registration def model(self): return "Boeing 777" def seating_plan(self): # For simplicity's sake, we ignore complex # seating arrangement fro first-class return range(1, 56), "ABCDEFGHJK"
Также заменим функцию make_flight() на новую - make_flights()
def make_flights(): f = Flight("BA758", AirbusA319("G-EUPT")) f.allocate_seat("12A", "Guido van Rossum") f.allocate_seat("15F", "Bjarne Stroustrup") f.allocate_seat("15E", "Anders Hejlsberg") f.allocate_seat("1C", "John McCarthy") f.allocate_seat("1D", "Rich Hickey") g = Flight("AF72", Boeing777("F-GSPS")) g.allocate_seat("55K", "Larry Wall") g.allocate_seat("33G", "Yukihiro Matsumoto") g.allocate_seat("4B", "Brian Kernigan") g.allocate_seat("4A", "Dennis Ritchie") return f, g
>>> from airtravel import * >>> f, g = make_flights() >>> f.aircraft_model() 'Airbus A319' >>> g.aircraft_model() 'Boeing 777' >>> f.num_available_seats() 127 >>> g.num_available_seats() 546 >>> g.relocate_passenger("55K", "13G") >>> g.make_boarding_cards(console_card_printer) +--------------------------------------------------------------------+ | | | Name: Brian Kernigan Flight: AF72 Seat: 4B Aircraft: Boeing 777 | | | +--------------------------------------------------------------------+ +--------------------------------------------------------------------+ | | | Name: Dennis Ritchie Flight: AF72 Seat: 4A Aircraft: Boeing 777 | | | +--------------------------------------------------------------------+ +-----------------------------------------------------------------+ | | | Name: Larry Wall Flight: AF72 Seat: 13G Aircraft: Boeing 777 | | | +-----------------------------------------------------------------+ +-------------------------------------------------------------------------+ | | | Name: Yukihiro Matsumoto Flight: AF72 Seat: 33G Aircraft: Boeing 777 | | | +-------------------------------------------------------------------------+
Наследование
Подробно про наследование читайте
здесь
Далее разберем на примере наших авиаперелётов.
Если нужен метод, который будед показыать сколько всего место в самолёте - его можно реализовать так
def num_seats(self): rows, row_seats = self.seating_plan() return len(rows) * len(row_seats)
И затем добавить в класс AirbusA319 и в класс Boeing777 и потом во все новые классы для других моделей. Получится, что один и тот же код будет повторяться из раза в раз. Это плохая практика, хорошей является создание класса Aircraft и наследование этого метода от него.
Вернём класс Aircraft, уже в новом исполнении
class Aircraft: def num_seats(self): rows, row_seats = self.seating_plan() return len(rows) * len(row_seats)
>>> from airtravel import * >>> a = Aircraft() >>> a.num_seats() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/andrei/airtravel/airtravel.py", line 112, in num_seats rows, row_seats = self.seating(plan) AttributeError: 'Aircraft' object has no attribute 'seating'
Как видите, сам по себе этот класс не работает - он является абстрактным. Нужно чтобы классы AirbusA319 и Boeing777 наследовали от него, а это мы ещё не сделали.
class Aircraft: def num_seats(self): rows, row_seats = self.seating_plan() return len(rows) * len(row_seats) class AirbusA319(Aircraft): def __init__(self, registration): self._registration = registration def registration(self): return self._registration def model(self): return "Airbus A319" def seating_plan(self): return range(1, 23), "ABCDEF" class Boeing777(Aircraft): def __init__(self, registration): self._registration = registration def registration(self): return self._registration def model(self): return "Boeing 777" def seating_plan(self): # For simplicity's sake, we ignore complex # seating arrangement fro first-class return range(1, 56), "ABCDEFGHJK"
>>> from airtravel import * >>> a = AirbusA319("G-EZBT") >>> a.num_seats() 132 >>> b = Boeing777("N717AN") >>> b.num_seats() 550
Методы __init__() и registration() одинаковы в обоих классах, поэтому их можно перенести в родительский класс Aircraft
class Aircraft: def __init__(self, registration): self._registration = registration def num_seats(self): rows, row_seats = self.seating_plan() return len(rows) * len(row_seats) def registration(self): return self._registration class AirbusA319(Aircraft): def model(self): return "Airbus A319" def seating_plan(self): return range(1, 23), "ABCDEF" class Boeing777(Aircraft): def model(self): return "Boeing 777" def seating_plan(self): # For simplicity's sake, we ignore complex # seating arrangement fro first-class return range(1, 56), "ABCDEFGHJK"
Окончательный вариант
"""Model for aircraft flights.""" class Flight: """ A flight with a particular passenger aircraft.""" def __init__(self, number, aircraft): if not number[:2].isalpha(): raise ValueError(f"No airline code in '{number}'") if not number[:2].isupper(): raise ValueError(f"Invalid airline code '{number}'") if not (number[2:].isdigit() and int(number[2:]) <= 9999): raise ValueError(f"Invalid route number '{number}'") self._number = number self._aircraft = aircraft rows, seats = self._aircraft.seating_plan() self._seating = [None] + [{letter: None for letter in seats} for _ in rows] def aircraft_model(self): return self._aircraft.model() def number(self): return self._number def airline(self): return self._number[:2] def allocate_seat(self, seat, passenger): """Allocate a seat to a passenger. Args: seat: A seat designator such as '12C' or '21F'. passenger: The passenger name. Raises: ValueError: If the seat is unavailable. """ row, letter = self._parse_seat(seat) if self._seating[row][letter] is not None: raise ValueError(f"Seat {seat} already occupied") self._seating[row][letter] = passenger def _parse_seat(self, seat): rows, seat_letters = self._aircraft.seating_plan() letter = seat[-1] if letter not in seat_letters: raise ValueError(f"Invalid seat letter {letter}") row_text = seat[:-1] try: row = int(row_text) except ValueError: raise ValueError(f"Invalid seat row {row_text}") if row not in rows: raise ValueError(f"Invalid row number {row}") return row, letter def relocate_passenger(self, from_seat, to_seat): """Relocate a passenger to a different seat. Args: from_seat: The existing seat designator for the passenger to be moved. to_seat: The new seat designator """ from_row, from_letter = self._parse_seat(from_seat) if self._seating[from_row][from_letter] is None: raise ValueError(f"No passenger to relocate in seat {from_seat}") to_row, to_letter = self._parse_seat(to_seat) if self._seating[to_row][to_letter] is not None: raise ValueError(f"Seat {to_seat} already occupied") self._seating[to_row][to_letter] = self._seating[from_row][from_letter] self._seating[from_row][from_letter] = None def num_available_seats(self): return sum(sum(1 for s in row.values() if s is None) for row in self._seating if row is not None) def make_boarding_cards(self, card_printer): for passenger, seat in sorted(self._passenger_seats()): card_printer(passenger, seat, self.number(), self.aircraft_model()) def _passenger_seats(self): """An iterable series of passenger seating locations.""" row_numbers, seat_letters = self._aircraft.seating_plan() for row in row_numbers: for letter in seat_letters: passenger = self._seating[row][letter] if passenger is not None: yield (passenger, f"{row}{letter}") class Aircraft: def __init__(self, registration): self._registration = registration def registration(self): return self._registration def num_seats(self): rows, row_seats = self.seating_plan() return len(rows) * len(row_seats) class AirbusA319(Aircraft): def model(self): return "Airbus A319" def seating_plan(self): return range(1, 23), "ABCDEF" class Boeing777(Aircraft): def model(self): return "Boeing 777" def seating_plan(self): return range(1, 56), "ABCDEFGHJK" def console_card_printer(passenger, seat, flight_number, aircraft): output = f"| Name: {passenger}" \ f" Flight: {flight_number}" \ f" Seat: {seat}" \ f" Aircraft: {aircraft}" \ " |" banner = "+" + "-" * (len(output) - 2) + "+" border = "|" + " " * (len(output) - 2) + "|" lines = [banner, border, output, border, banner] card = "\n".join(lines) print(card) print() def make_flights(): f = Flight("BA758", AirbusA319("G-EUPT")) f.allocate_seat("12A", "Guido van Rossum") f.allocate_seat("15F", "Bjarne Stroustrup") f.allocate_seat("15E", "Anders Hejlsberg") f.allocate_seat("1C", "John McCarthy") f.allocate_seat("1D", "Rich Hickey") g = Flight("AF72", Boeing777("F-GSPS")) g.allocate_seat("55K", "Larry Wall") g.allocate_seat("33G", "Yukihiro Matsumoto") g.allocate_seat("4B", "Brian Kernigan") g.allocate_seat("4A", "Dennis Ritchie") return f, g if __name__ == '__main__': f = make_flight() print(f.num_available_seats())
Объекты класса (class objects) и экземпляры класса (instance of class)
это разные вещи.
class создаёт именованную ссылку на объект класса.
Автор статьи: Андрей Олегович
| ООП в Python | |
| Классы | |
| Методы | |
| class variables | |
| class methods | |
| Статические методы | |
| Наследование | |
| super() | |
| Специальные методы | |
| dataclass | |
| __slots__ | |
| Декоратор property | |
| Полиморфизм |