Еще примеры с классами
Следующий пример состоит из двух классов. Класс Point — это модель точки в двумерном пространстве. Класс Line — это модель отрезка между двумя точками. Код ниже снабжен комментариями; прочитайте комментарии, чтобы понять, как работают классы.
import math
class Point:
""" The class represents a point in two-dimensional space """
def __init__(self, x: float, y: float):
# These attributes are public because any value is acceptable for x and y
self.x = x
self.y = y
# This class method returns a new Point at origo (0, 0)
# It is possible to return a new instance of the class from within the class
@classmethod
def origo(cls):
return Point(0, 0)
# This class method creates a new Point based on an existing Point
# The original Point can be mirrored on either or both of the x and y axes
# For example, the Point (1, 3) mirrored on the x-axis is (1, -3)
@classmethod
def mirrored(cls, point: "Point", mirror_x: bool, mirror_y: bool):
x = point.x
y = point.y
if mirror_x:
y = -y
if mirror_y:
x = -x
return Point(x, y)
def __str__(self):
return f"({self.x}, {self.y})"
class Line:
""" The class represents a line segment in two-dimensional space """
def __init__(self, beginning: Point, end: Point):
# These attributes are public because any two Points are acceptable
self.beginning = beginning
self.end = end
# This method uses the Pythagorean theorem to calculate the length of the line segment
def length(self):
sum_of_squares = (self.end.x - self.beginning.x) ** 2 + (self.end.y - self.beginning.y) ** 2
return math.sqrt(sum_of_squares)
# This method returns the Point in the middle of the line segment
def centre_point(self):
centre_x = (self.beginning.x + self.end.x) / 2
centre_y = (self.beginning.y + self.end.y) / 2
return Point(centre_x, centre_y)
def __str__(self):
return f"{self.beginning} ... {self.end}"point = Point(1,3)
print(point)
origo = Point.origo()
print(origo)
point2 = Point.mirrored(point, True, True)
print(point2)
line = Line(point, point2)
print(line.length())
print(line.centre_point())
print(line)(1, 3) (0, 0) (-1, -3) 6.324555320336759 (0.0, 0.0) (1, 3) ... (-1, -3)
Значения параметров по умолчанию
В программировании на Python обычно можно задать значение по умолчанию для любого параметра. Значения по умолчанию можно использовать и в функциях, и в методах.
Если у параметра есть значение по умолчанию, при вызове функции не обязательно передавать для него значение как аргумент. Если аргумент передан, значение по умолчанию игнорируется. Если нет, используется значение по умолчанию.
Значения по умолчанию часто используются в конструкторах. Если можно ожидать, что при создании объекта будет доступна не вся информация, лучше включить значение по умолчанию в определение метода-конструктора, чем заставлять клиента заботиться об этом. Это упрощает использование класса с точки зрения клиента, но также обеспечивает целостность объекта. Например, с заданным значением по умолчанию мы можем быть уверены, что "пустое" значение всегда одно и то же, если только клиент специально не хочет передать что-то другое. Если значение по умолчанию не задано, клиент сам должен предоставить "пустое" значение. Это может быть, например, пустая строка "", специальный пустой объект None или строка "not set".
Посмотрим еще один класс, представляющий студента. При создании нового объекта Student клиент должен предоставить имя и номер студента. Номер студента приватный и не должен изменяться позже. Кроме того, объект Student имеет атрибуты для учебных кредитов и заметок, для которых в конструкторе заданы значения по умолчанию. Новые значения можно передать как аргументы конструктору, но их также можно опустить, чтобы вместо них использовались значения по умолчанию. Посмотрите на комментарии в коде, чтобы луч ше понять, что делает каждый метод.
class Student:
""" This class models a student """
def __init__(self, name: str, student_number: str, credits: int = 0, notes: str = ""):
# calling the setter method for the name attribute
self.name = name
if len(student_number) < 5:
raise ValueError("A student number should have at least five characters")
self.__student_number = student_number
# calling the setter method for the credits attribute
self.credits = credits
self.__notes = notes
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
if name != "":
self.__name = name
else:
raise ValueError("The name cannot be an empty string")
@property
def student_number(self):
return self.__student_number
@property
def credits(self):
return self.__credits
@credits.setter
def credits(self, op):
if op >= 0:
self.__credits = op
else:
raise ValueError("The number of study credits cannot be below zero")
@property
def notes(self):
return self.__notes
@notes.setter
def notes(self, notes):
self.__notes = notes
def summary(self):
print(f"Student {self.__name} ({self.student_number}):")
print(f"- credits: {self.__credits}")
print(f"- notes: {self.notes}")# Passing only the name and the student number as arguments to the constructor
student1 = Student("Sally Student", "12345")
student1.summary()
# Passing the name, the student number and the number of study credits
student2 = Student("Sassy Student", "54321", 25)
student2.summary()
# Passing values for all the parameters
student3 = Student("Saul Student", "99999", 140, "extra time in exam")
student3.summary()
# Passing a value for notes, but not for study credits
# NB: the parameter must be named now that the arguments are not in order
student4 = Student("Sandy Student", "98765", notes="absent in academic year 20-21")
student4.summary()Student Sally Student (12345):
- credits: 0
- notes:
Student Sassy Student (54321):
- credits: 25
- notes:
Student Saul Student (99999):
- credits: 140
- notes: extra time in exam
Student Sandy Student (98765):
- credits: 0
- notes: absent in academic year 20-21
Обратите внимание: для атрибута student_number нет метода-сеттера, потому что номер студента не должен изменяться.
При использовании значений параметров по умолчанию есть одна довольно существенная ловушка. Следующий пример, моделирующий еще один вид студента, прояснит ситуацию:
class Student:
def __init__(self, name, completed_courses=[]):
self.name = name
self.completed_courses = completed_courses
def add_course(self, course):
self.completed_courses.append(course)student1 = Student("Sally Student")
student2 = Student("Sassy Student")
student1.add_course("ItP")
student1.add_course("ACiP")
print(student1.completed_courses)
print(student2.completed_courses)['ItP', 'ACiP'] ['ItP', 'ACiP']
Добавление завершенных курсов в список Sally также добавляет эти курсы в список Sassy. На самом деле это один и тот же список, потому что Python повторно использует ссылку, сохраненную в значении по умолчанию. Создание двух новых объектов Student в примере выше эквивалентно следующему:
courses = []
student1 = Student("Sally Student", courses)
student2 = Student("Sassy Student", courses)Значения параметров по умолчанию никогда не должны быть экземплярами более сложных, изменяемых структур данных, например списками. Проблему можно обойти, внеся следующие изменения в конструктор класса Student:
class Student:
def __init__(self, name, completed_courses=None):
self.name = name
if completed_courses is None:
self.completed_courses = []
else:
self.completed_courses = completed_courses
def add_course(self, course):
self.completed_courses.append(course)student1 = Student("Sally Student")
student2 = Student("Sassy Student")
student1.add_course("ItP")
student1.add_course("ACiP")
print(student1.completed_courses)
print(student2.completed_courses)['ItP', 'ACiP'] []
Большой финал
Хотя следующее упражнение завершает эту часть материала, все техники, необходимые для его решения, уже были рассмотрены в разделе объекты как атрибуты. В частности, в этом упражнении от вас не требуется использовать декоратор @property или значения параметров по умолчанию. Это упражнение очень похоже на упражнения коробка подарков и самый низкий человек в комнате.
Важная информация о следующем упражнении
Обратите внимание, что существует проблема, возникшая из-за обновления Python, которое конфликтует со встроенной библиотекой и исходным именем файла для этого упражнения. Если у вас возникнут какие-либо проблемы, мы рекомендуем заново скачать папку упражнения. После получения новых локальных тестовых файлов вы можете использовать либо "code.py", либо "code_1.py" как имя файла. При использовании Visual Studio Code вы можете получать уведомления о проблемах в тестовом файле. Однако эти уведомления можно безопасно игнорировать, поскольку они вызваны неспособностью теста импортировать из файлов "code.py" или "code_1.py".
Ответьте, пожалуйста, на короткую анкету по материалам этой недели.
Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.