Добавление функций в классы Python

Классы в Python могут создаваться просто как коллекции функций. Функции можно определить внутри класса обычным образом, так же как и внутри любой программы. А вот для вызова такой функции нужно действовать через данный класс.

От редакции Pythonist: предлагаем также почитать статьи «Классы в Python» и «Переменные класса и экземпляра в Python».

Приведенный ниже пример класса с именем Greetings принимает имя и возвращает приветствие человеку, вызывающему этот класс.

class Greetings:
    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}')
        
        
Greetings.good_afternoon('John')
Greetings.good_morning('Peter')
Greetings.good_evening('Jane') 

Результат:

Good afternoon John
Good morning Peter
Good evening Jane

Распространенная ошибка

Пока данный класс ведет себя так, как мы и планировали. Но давайте теперь добавим в него магический метод __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.