Part 9

Объекты как атрибуты

Мы уже видели примеры классов, у которых списки были атрибутами. Раз ничто не мешает нам включать изменяемые объекты в качестве атрибутов в наши классы, мы точно так же можем использовать экземпляры наших собственных классов как атрибуты в других классах, которые мы определили сами. В следующих примерах мы определим классы Course, Student и CompletedCourse. Завершенный курс использует первые два класса. Определения классов очень короткие и простые, чтобы легче было сосредоточиться на технике использования экземпляров наших собственных классов как атрибутов.

Будем считать, что каждый класс определен в отдельном файле.

Сначала определим класс Course в файле с именем course.py:

class Course:
    def __init__(self, name: str, code: str, credits: int):
        self.name = name
        self.code = code
        self.credits = credits

Затем класс Student в файле с именем student.py:

class Student:
    def __init__(self, name: str, student_number: str, credits: int):
        self.name = name
        self.student_number = student_number
        self.credits = credits

Наконец, класс CompletedCourse определяется в файле с именем completedcourse.py. Поскольку он использует два других класса, их нужно импортировать перед использованием:

from course import Course
from student import Student

class CompletedCourse:
    def __init__(self, student: Student, course: Course, grade: int):
        self.student = student
        self.course = course
        self.grade = grade

Вот пример главной функции, которая добавляет несколько завершенных курсов в список:

from completedcourse import CompletedCourse
from course import Course
from student import Student

# Create a list of students
students = []
students.append(Student("Ollie", "1234", 10))
students.append(Student("Peter", "3210", 23))
students.append(Student("Lena", "9999", 43))
students.append(Student("Tina", "3333", 8))

# Create a course named Introduction to Programming
itp = Course("Introduction to Programming", "itp1", 5)

# Add completed courses for each student, with grade 3 for all
completed = []
for student in students:
    completed.append(CompletedCourse(student, itp, 3))

# Print out the name of the student for each completed course
for course in completed:
    print(course.student.name)
Пример вывода

Ollie Peter Lena Tina

Что именно происходит со всеми точками в строке print(course.student.name)?

  • course — это экземпляр класса CompletedCourse
  • student ссылается на атрибут объекта CompletedCourse, который является объектом типа Student
  • атрибут name в объекте Student содержит имя студента

Когда нужен импорт?

В примерах выше инструкция import появлялась довольно много раз:

from completedcourse import CompletedCourse
from course import Course
from student import Student

# rest of the main function

Инструкция import нужна только тогда, когда используется код, определенный где-то вне текущего файла (или вне текущей сессии интерпретатора Python). Сюда входят и ситуации, когда мы хотим использовать что-то, определенное в стандартной библиотеке Python. Например, модуль math содержит некоторые математические операции:

import math

x = 10
print(f"the square root of {x} is {math.sqrt(x)}")

В примере выше мы предполагали, что три класса определены каждый в отдельном файле, а главная функция запускается из еще одного файла. Именно поэтому инструкции import были необходимы.

Если весь код программы написан в одном файле, как советуют делать в большинстве упражнений этого курса, вам не понадобятся инструкции import, чтобы использовать классы, которые вы определили.

Если вы ловите себя на том, что пишете что-то вроде

from person import Person

# more code goes here

скорее всего, что-то пошло не так. Если нужно освежить память, инструкция import впервые вводилась в части 7 материалов этого курса.

Loading

Список объектов как атрибут объекта

В примерах выше мы использовали отдельные экземпляры других классов как атрибуты: у Person есть один Pet как атрибут, а у CompletedCourse есть один Student и один Course как атрибуты.

В объектно-ориентированном программировании часто бывает так, что мы хотим иметь коллекцию объектов в качестве атрибута. Например, связь между спортивной командой и ее игроками следует именно этому шаблону:

class Player:
    def __init__(self, name: str, goals: int):
        self.name = name
        self.goals = goals

    def __str__(self):
        return f"{self.name} ({self.goals} goals)"

class Team:
    def __init__(self, name: str):
        self.name = name
        self.players = []

    def add_player(self, player: Player):
        self.players.append(player)

    def summary(self):
        goals = []
        for player in self.players:
            goals.append(player.goals)
        print("Team:", self.name)
        print("Players:", len(self.players))
        print("Goals scored by each player:", goals)

Пример работы нашего класса:

ca = Team("Campus Allstars")
ca.add_player(Player("Eric", 10))
ca.add_player(Player("Emily", 22))
ca.add_player(Player("Andy", 1))
ca.summary()
Пример вывода

Team: Campus Allstars Players: 3 Goals scored by each player: [10, 22, 1]

Loading

None: ссылка в никуда

В программировании на Python все инициализированные переменные ссылаются на объект. Однако неизбежно бывают ситуации, когда нужно сослаться на что-то, чего не существует, не вызывая ошибок. Ключевое слово None представляет именно такой "пустой" объект.

Продолжая пример с Team и Player выше, предположим, что мы хотим добавить метод для поиска игроков команды по имени игрока. Если такой игрок не найден, разумно вернуть None:

class Player:
    def __init__(self, name: str, goals: int):
        self.name = name
        self.goals = goals

    def __str__(self):
        return f"{self.name} ({self.goals} goals)"

class Team:
    def __init__(self, name: str):
        self.name = name
        self.players = []

    def add_player(self, player: Player):
        self.players.append(player)

    def find_player(self, name: str):
        for player in self.players:
            if player.name == name:
                return player
        return None

Проверим нашу функцию:

ca = Team("Campus Allstars")
ca.add_player(Player("Eric", 10))
ca.add_player(Player("Amily", 22))
ca.add_player(Player("Andy", 1))

player1 = ca.find_player("Andy")
print(player1)
player2 = ca.find_player("Charlie")
print(player2)
Пример вывода

Andy (1 goals) None

Однако будьте осторожны с None. Иногда он создает больше проблем, чем решает. Распространенная ошибка программирования — попытаться обратиться к методу или атрибуту через ссылку, которая имеет значение None:

ca = Team("Campus Allstars")
ca.add_player(Player("Eric", 10))

player = ca.find_player("Charlie")
print(f"Goals by Charlie: {player.goals}")

Выполнение кода выше вызовет ошибку:

Пример вывода
Traceback (most recent call last): File "", line 1, in AttributeError: 'NoneType' object has no attribute 'goals'

Хорошая идея — проверять значение на None перед тем, как пытаться обращаться к атрибутам или методам возвращаемых значений:

ca = Team("Campus Allstars")
ca.add_player(Player("Eric", 10))

player = ca.find_player("Charlie")
if player is not None:
    print(f"Goals by Charlie: {player.goals}")
else:
    print(f"Charlie doesn't play in Campus Allstars :(")
Пример вывода

Charlie doesn't play in Campus Allstars :(

Loading
Вы дошли до конца этого раздела! Перейти к следующему разделу:

Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.