Классы в Python

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

Введение

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

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

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

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

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

class Site: pass my_site = Site()

Создан класс Site и его экземпляр my_site

Заглянем немного в глубину. C помощью type() можно увидеть принадлежность my_site классу Site

print(type(my_site))

<class '__main__.Site'>

Пока экземляр класса совершенно пуст, можно с помощью dir() изучить какие у есть методы и аттрибуты по умолчанию

print(dir(hh))

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

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

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

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

Атрибуты

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

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

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

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

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

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

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

В предыдущей главе мы изучали пустой экземпляр. Теперь можно посмотреть какой эффект даёт создание аттрибута

print(dir(hh))

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

Видно, что к дефолтным атрибутам добавился атрибут url

В следующем примере создадим два экземпляра одного и того же класса Employee

class Employee: pass emp_1 = Employee() emp_2 = Employee() print(emp_1) print(emp_2)

python emp_ex.py

<__main__.Employee object at 0x7fca67c26fd0> <__main__.Employee object at 0x7fca67c26fa0>

Зададим несколько атрибутов

class Employee: pass emp_1 = Employee() emp_2 = Employee() 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

Yuri.Gagarin@vostok.su Test.User@vostok.su

__init__

Создавать атрибуты таким способом не всегда удобно. Поэтому чтобы создавать атрибуты обычно используют метод __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)

mammal mammal

# Переопределим значение bioclass для my_dog my_dog.bioclass = "super mammal" print(Dog.bioclass, my_dog.bioclass)

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__']

Пример

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

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

Прочитать про 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()

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

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

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

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

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

Похожие статьи
ООП в Python
Классы
Методы
class variables
class methods
Статические методы
Наследование
Специальные методы
Декоратор property
Python
Функции
super()

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: