Классы в Python

Содержание
Введение
Атрибуты
Статические атрибуты
Объекты
Пример
Полиморфизм
Наследование
Далее по теме

Введение

Классы нужно называть с заглавной буквы и использовать CamelCase.

Создается новый класс с помощью class

Объект данного класса создаётся с помощью =

объект = ИмяКласса()

Самый простой пример использования

class Sample: pass my_sample = Sample() print(type(my_sample))

<class '__main__.Sample'>

Классы без атрибутов и методов могут использоваться для обработки ошибок

class MyAPIException(Exception): """ My API exception. """

Тем не менее основные преимущества ООП раскрываются именно через использование атрибутов и методов.

Атрибуты

Атрибуты это свойства объекта. Бытовой пример: у велосипеда есть страна производитель , масса, цвет, количество скоростей, цена и так далее.

У сайта в интернете есть, url, год делегирования домена, автор, основная тема.

Чтобы создавать атрибуты достаточно объявить класс и создать объект.

Синтаксис при создании и при доступе следующий

объект.имя_атрибута

Создадим класс Site и объект hh с адресом сайта.

class Site: pass hh = Site() hh.url = "https://www.heihei.ru"

class Employee: pass emp_1 = Employee() emp_2 = Employee() print(emp_1) print(emp_2) emp_1.first = 'Yuri' emp_1.last = 'Gagarin' emp_1.email = 'Yuri.Gagarin@vostok.su' emp_1.pay = 1000 emp_2.first = 'Test' emp_2.last = 'User' emp_2.email = 'Test.User@vostok.su' emp_2.pay = 1234 print(emp_1.email) print(emp_2.email)

python emp_ex.py

<__main__.Employee object at 0x7fca67c26fd0> <__main__.Employee object at 0x7fca67c26fa0> Yuri.Gagarin@vostok.su Test.User@vostok.su

Также чтобы создавать атрибуты нужно использьвать метод __init__() в этом случае каждый раз когда создаётся новый объект данного класса первым делом вызывается метод __init__()

Создадим класс Dog, у которого будет один атрибут - порода (breed).

Когда создается класс у него может быть любое название, но внутри класса, чтобы задать значение атрибута используется ключевое слово self

self.атрибут = значение

Или если вы делаете атрибут, который не должен быть доступен извне - добавьте пере именем _

С точки зрения защиты это ничего не даст так как это просто соглашение. Однако, линтеры смогут вам подсказать, если у атрибута будет неправильное применение.

self._атрибут = значение

Таким образом, если вы поменяете название класса, внутри переименовывать ничего не нужно.

class Cat: def __init__(self, breed): self.breed = breed

Переименовываем Cat на Dog

class Dog: def __init__(self, breed): self.breed = breed

Если у класса есть атрибут нужно внимательнее отнестить к созданию объекта

my_dog = Dog()

Уже не сработает

Traceback (most recent call last): File "/home/andrei/python/sample.py", line 6, in <module> my_dog = Dog() TypeError: __init__() missing 1 required positional argument: 'breed'

Теперь при создании объекта нужно задавать желаемое значение атрибута

my_dog = Dog("husky")

Либо в методе __init__() нужно указать значение по умолчанию

class Dog: def __init__(self, breed="aidi"): self.breed = breed

Новичкам может показаться странной строка

self.breed = breed

Здесь главное усвоить следующее:

Именно self.breed - задаёт название атрибута, которое будет у объекта данного класса.

В данном случае атрибут будет называться breed

breed - это название параметра у метода __init__().

В данном случае - первый аргумент, передаваемый вами в метод __init__() называется breed

Можно назвать его и по-другому, например arg1, суть от этого не изменится, но если их будет много - тяжелее станет запоминать какому атрибуту соответствует, скажем, arg99.

class Dog: def __init__(self, agr1): self.breed = arg1

Теперь попробую объяснить тоже самое, но немного по-другому и уже с двумя атрибутами

class Dog: def __init__(self, name, age): self.name = name self.age = age

То, что после точки - это название атрибута. Оно нужно, чтобы потом обращаться к этому атрибуту.

Зелёным я выделил названия параметров. Часто в примерах у атрибута и параметра одинаковые названия. Это не обязательное условие, которое может помешать пониманию сути.

Сравните следующие объявления классов.

Только формальные названия из которых ничего не понятно

class Dog: def __init__(self, arg1, arg2): self.attr1 = arg1 self.attr2 = arg2 pet = Dog("Tuzik", 2) print(pet.attr1) print(pet.attr2)

Понятные названия, но у атрибутов и у параметров __init__() они разные

class Dog: def __init__(self, name, age): self.nick = name self.years = age pet = Dog("Tuzik", 2) print(pet.nick) print(pet.year)

Понятные и одинаковые названия

class Dog: def __init__(self, name, age): self.name = name self.age = age pet = Dog("Tuzik", 2) print(pet.name) print(pet.age)

Результат вызова у всех трёх вариантов одинаковый

Tuzik
2

Пример с тремя атрибутами

class Dog: def __init__(self, breed, name, spots): self.breed = breed self.name = name self.spots = spots # my_dog = Dog() my_dog = Dog("husky", "Barbos", True) print(my_dog.breed, my_dog.name, my_dog.spots)

Статические атрибуты

Можно задавать атрибуты сразу для всех элементов класса. Они называются статическими (static attributes, class object attributes)

class Dog: # CLASS OBJECT ATTRIBUTE # SAME FOR ANY INSTANCE OF A CLASS bioclass = "mammal" def __init__(self, breed, name, spots): self.breed = breed self.name = name self.spots = spots # При создании объекта static атрибут можно не указывать явно my_dog = Dog("husky", "Barbos", True) # Проверим значение bioclass заданное по умолчанию print(Dog.bioclass, my_dog.bioclass) # Переопределим значение bioclass для my_dog my_dog.bioclass = "super mammal" print(Dog.bioclass, my_dog.bioclass)

mammal mammal
mammal super mammal

Объекты

Все классы в Python кроме Exception наследуются от object

>>> o = object()
>>> dir(o)

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

Пример

Разберём пример создание классов для описания перелётов на пассажирских авиалиниях.

Если вы ищете дешёвые билеты - воспользуйтесь Авиасейлз

"""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"

>>> 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.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.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.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.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

"""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

>>> 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()

Прежде чем двигаться к более сложному коду убедимся, что всё понятно в уже написанном

Как печатается схема мест в самолёте:

seats = "ABCDEF" rows = range(1, 10) for r in rows: s = '' for seat in seats: s = s + seat print(r, s)

1 ABCDEF 2 ABCDEF 3 ABCDEF 4 ABCDEF 5 ABCDEF 6 ABCDEF 7 ABCDEF 8 ABCDEF 9 ABCDEF

Немного усложним

num_rows = 9 num_seats_per_row = 6 seats = "ABCDEFGHJK"[:num_seats_per_row] rows = range(1, num_rows + 1) for r in rows: s = '' for seat in seats: s = s + seat print(r, s)

1 ABCDEF 2 ABCDEF 3 ABCDEF 4 ABCDEF 5 ABCDEF 6 ABCDEF 7 ABCDEF 8 ABCDEF 9 ABCDEF

Ещё немного

num_rows = 9 num_seats_per_row = 6 seating_plan = (range(1, num_rows + 1), "ABCDEFGHJK"[:num_seats_per_row]) rows, seats = seating_plan for r in rows: s = '' for seat in seats: s = s + seat print(r, s)

1 ABCDEF 2 ABCDEF 3 ABCDEF 4 ABCDEF 5 ABCDEF 6 ABCDEF 7 ABCDEF 8 ABCDEF 9 ABCDEF

Создадим пустой список

from pprint import pp num_rows = 9 num_seats_per_row = 6 seating_plan = (range(1, num_rows + 1), "ABCDEFGHJK"[:num_seats_per_row]) rows, seats = seating_plan empty_airplane_plan = [[None for letter in seats] for _ in rows] pp(empty_airplane_plan)

[[None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None], [None, None, None, None, None, None]]

Теперь вместо None будем делать словарь вида A: None, B: None и т.д.

from pprint import pp num_rows = 9 num_seats_per_row = 6 seating_plan = (range(1, num_rows + 1), "ABCDEFGHJK"[:num_seats_per_row]) rows, seats = seating_plan empty_airplane_plan = [None] + [{letter: None for letter in seats} for _ in rows] pp(empty_airplane_plan)

[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}]

"""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 airtravel import * >>> f = Flight("BA758", Aircraft("G-EUPT", "Airbus A319", num_rows=22, num_seats_per_row=6)) >>> from pprint import pp >>> 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 * >> from pprint import pp >>> 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.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.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.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.py", line 50, in allocate_seat raise ValueError(f"Invalid seat row {row_text}") ValueError: Invalid seat row D

Рассадим несколько пассажиров

>>> 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

Также напишем вспомогательную функцию make_flight() с помощью которой будем быстро создавать тестовый рейс

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

>>> from airtravel import make_flight >>> f = make_flight() >>> f <airtravel.Flight object at 0x7f99e1a920a0> >>> f.relocate_passenger("12A", "15D") >>> from pprint import pp >>> pp(f._seating) [None, {'A': None, 'B': None, 'C': 'John McCarthy', 'D': 'Rich 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': 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': 'Guido van Rossum', '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}]

Напишем функцию, которая показывает число свободных мест

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)

>>> from airtravel import make_flight >>> f = make_flight() >>> 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.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 создаёт именованную ссылку на объект класса.

Рассмотрим класс Resolver из статьи про кэширование

import socket class Resolver: def __init__(self): self._cache = {} def __call__(self, host): if host not in self._cache: self._cache[host] = socket.gethostbyname(host) return self._cache[host] def clear(self): self._cache.clear() def has_host(self, host): return host in self._cache

Импортируем в REPL класс Resolver

python >>> from resolver import Resolver

Если с помощью REPL выполнить (evaluate) Resolver

>>> Resolver

REPL покажет представление (representation) объекта класса (class object)

<class 'resolver.Resolver'>

Этот объект класса является вызываемым (callable) чем мы и пользуемся

resolve = Resolver()

Аргументы, которые передаются в объект класса перенаправляются в метод __init__() этого класса, если он определён.

Классы создают новые сущности в момент вызова.

О создании сущностей мы поговорим позже.

def main(): seq = sequence_class(immutable=True) t = seq("Timbuktu") print(t) print(type(t)) def sequence_class(immutable): if immutable: cls = tuple else: cls = list return cls if __name__ == "__main__": main()

python sequence_class.py

('T', 'i', 'm', 'b', 'u', 'k', 't', 'u') <class 'tuple'>

def main(): seq = sequence_class(immutable=False) t = seq("Timbuktu") print(t) print(type(t)) def sequence_class(immutable): return tuple if immutable else list if __name__ == "__main__": main()

python sequence_class.py

['T', 'i', 'm', 'b', 'u', 'k', 't', 'u'] <class 'list'>