Добавление функций в классы Python
Распространенная ошибка
Пока данный класс ведет себя так, как мы и планировали. Но давайте теперь добавим в него магический метод __init__.
class Greetings:
def __init__(self):
pass
def good_morning(name):
print(f'Good morning {name}')
def good_afternoon(name):
print(f'Good afternoon {name}')
def good_evening(name):
print(f'Good evening {name}')
g = Greetings()
Мы также создали экземпляр класса Greeting и поместили его в переменную g. Если мы попытаемся вызвать функцию класса как в прошлый раз, только через экземпляр класса, то получим следующую ошибку:
g.good_afternoon('John')
# Результат:
# TypeError: good_afternoon() takes 1 positional argument but 2 were given
Может быть не вполне понятно, что сейчас произошло. Возвращенная выше ошибка TypeError сообщает, что мы передали функции 2 аргумента вместо одного. А с виду кажется, что был передан только один аргумент. Чтобы понять, что произошло, давайте попробуем вызвать нашу функцию вообще без аргумента:
g.good_afternoon() # Результат: # Good afternoon <__main__.Greetings object at 0x00000284083D1B88>
Что же тут происходит?
Когда мы создаем экземпляр класса, то первым аргументом, передаваемым функции, является сам этот экземпляр. Таким образом, причина, по которой мы получаем ошибку TypeError, заключается в том, что Python считывает функцию g.good_afternoon('John') как g.good_afternoon(g, 'John'). Это может показаться запутанным, но в следующих секциях мы разберем, почему такое происходит.
Методы экземпляров класса
Рассмотрим новый пример класса под названием Student, который принимает в качестве параметров имя, фамилию, возраст и специальность.
class Student:
def __init__(self, first, last, age, major):
self.first = first
self.last = last
self.age = age
self.major = major
def profile(self):
print(f"Student name {self.first + ' ' + self.last}")
print(f"Student age: {self.age}")
print(f"Major: {self.major}")
s = Student('Sally' , 'Harris', 20, 'Biology')
s.profile()
Результат:
Student name Sally Harris Student age: 20 Major: Biology
При определении методов экземпляра мы должны передавать в качестве первого аргумента ключевое слово self. Это решает проблему передачи экземпляра класса функциям в качестве первого аргумента.
Давайте создадим текущий класс и добавим функциональность регистрации, чтобы показать способность методов экземпляра класса взаимодействовать с атрибутами.
class Student:
def __init__(self, first, last, age, major):
self.first = first
self.last = last
self.age = age
self.major = major
self.courses = []
def profile(self):
print(f"Student name {self.first + ' ' + self.last}")
print(f"Student age: {self.age}")
print(f"Major: {self.major}")
def enrol(self, course):
self.courses.append(course)
print(f"enrolled {self.first} in {course}")
def show_courses(self):
print(f"{self.first + '' + self.last} is taking the following courses")
for course in self.courses:
print(course)
s = Student('Sally' , 'Harris', 20, 'Biology')
s.enrol('Biochemistry I')
# enrolled Sally in Biochemistry I
s.enrol('Literature')
# enrolled Sally in Literature
s.enrol('Mathematics')
# enrolled Sally in Mathematics
s.show_courses()
# SallyHarris is taking the following courses
# Biochemistry I
# Literature
# Mathematics
Все вышеперечисленные методы были привязаны к экземпляру класса Student, который мы сохранили в переменную s. Мы можем проверить это, используя ключевое слово dir для нашего экземпляра класса, чтобы увидеть все атрибуты и методы, привязанные к нему.
dir(s) # Результат: ['__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__', 'age', 'courses', 'enrol', 'first', 'last', 'major', 'profile', 'show_courses']
В рамках класса мы можем сочетать функции, привязанные к экземплярам класса и обычные функции, которые мы привели в начале данной статьи. Давайте ниже добавим функцию, которая будет выводить на экран текущий учебный год.
import datetime as dt
class Student:
def __init__(self, first, last, age, major):
self.first = first
self.last = last
self.age = age
self.major = major
self.courses = []
def profile(self):
print(f"Student name {self.first + ' ' + self.last}")
print(f"Student age: {self.age}")
print(f"Major: {self.major}")
def enrol(self, course):
self.courses.append(course)
print(f"enrolled {self.first} in {course}")
def show_courses(self):
print(f"{self.first + '' + self.last} is taking the following courses")
for course in self.courses:
print(course)
def academic_year():
now = dt.datetime.now()
s = now.year, now.year -1
print(f"Current academic year is { str(s[0]) + '/' + str(s[1]) }")
Однако мы все равно получим ошибку, если попытаемся вызвать эту новую функцию из экземпляра класса, так как вызов методов/функций из экземпляров класса всегда передает сам экземпляр класса в качестве первого аргумента. Таким образом, если мы хотим вызвать функцию academic_year(), это можно сделать это следующим образом:
Student.academic_year() # Результат: Current academic year is 2020/2019
Заключение
При вызове функции из класса используется синтаксис ClassName.FunctionName().
При вызове функции, привязанной к экземпляру класса, первым аргументом, передаваемым функции, является сам экземпляр класса. Такие функции также называют методами.
Чтобы метод правильно работал, мы должны при его написании в качестве первого аргумента указать ключевое слово self.
Данные методы дают нам возможность взаимодействовать с атрибутами, связанными с экземплярами класса.
Перевод статьи Adding Functions to Python Classes.