Очень важная парадигма в программировании – объектно-ориентированное программирование (ООП).
Объекты создаются с помощью классов, основного понятия ООП.
Класс описывает объект, но является независимым от него. Иными словами, класс – это экземпляр, описание или определение объекта.
Вы можете использовать класс в качестве образца для создания различных объектов.
Классы оформляются с помощью ключевого слова class и в виде блока с отступом, содержащего методы класса (которые являются функциями).
Ниже приведен пример простого класса и его объектов.
class Cat: def __init__(self, color, legs): self.color = color self.legs = legs felix = Cat("ginger", 4) rover = Cat("dog-colored", 4) stumpy = Cat("brown", 3)
Во фрагменте кода вверху нами определен класс с именем Cat, у которого два атрибута: color и legs.
Затем класс используется для создания 3 отдельных объектов, принадлежащих этому классу.
Метод __init__ – самый важный метод класса.
Он вызывается, когда создается экземпляр (объект) класса; имя класса используется как функция.
Все методы должны иметь self в качестве своего первого параметра; хотя self непосредственно не передается, Python добавляет инструкцию self в список сам; также не нужно включать self, когда вы вызываете методы. В пределах определения метода, инструкция self относится к экземпляру класса, вызывающему метод.
Экземпляры класса берут атрибуты – фрагменты связанных с ними данных.
В нашем примере, экземпляры класса Cat имеют атрибуты color и legs. Их можно получить, указав точку и имя атрибута после экземпляра.
Таким образом внутри метода __init__ с помощью self.attribute можно задать начальное значение атрибутов экземпляра.
class Cat: def __init__(self, color, legs): self.color = color self.legs = legs felix = Cat("ginger", 4) print(felix.color)
В приведенном выше примере, метод __init__ принимает два аргумента и присваивает их объекту в качестве его атрибутов. Метод __init__ называют конструктор класса.
Можно использовать и другие методы, расширяющие функциональность классов.
Помните, что первым параметром всех методов должен быть self.
Используйте тот же синтаксис (dot), что и для атрибутов.
class Dog: def __init__(self, name, color): self.name = name self.color = color def bark(self): print("Woof!") fido = Dog("Fido", "brown") print(fido.name) fido.bark()
Попытка вызова атрибута экземпляра, который не был определен вызывает AttributeError. Такая же ошибка выдается при попытке вызова несуществующего метода.
С помощью наследования мы можем задать единую функциональность разным классам.
Допустим, у нас есть несколько классов: Cat, Dog, Rabbit и другие. Некоторые методы этих классов будут уникальными: только Dog будет иметь метод bark (англ. лай). Но другие методы будут одинаковыми: все классы будут иметь color и name.
Это сходство можно выразить c помощью функции наследования, так чтобы все классы наследовали общую функциональность от суперкласса Animal.
Наследование оформляется путем заключения в круглые скобки имени суперкласса, следующего за именем класса.
class Animal: def __init__(self, name, color): self.name = name self.color = color class Cat(Animal): def purr(self): print("Purr...") class Dog(Animal): def bark(self): print("Woof!") fido = Dog("Fido", "brown") print(fido.color) fido.bark()
Класс, наследующий атрибуты или методы другого класса, называется подклассом.
Класс, из которого наследуются атрибуты или методы, называется суперклассом.
Если наследуемый класс имеет такие же атрибуты или методы, что и класс-наследник, то класс-наследник переопределяет их.
class Wolf: def __init__(self, name, color): self.name = name self.color = color def bark(self): print("Grr...") class Dog(Wolf): def bark(self): print("Woof") husky = Dog("Max", "grey") husky.bark()
В примере вверху у нас суперкласс Wolf и подкласс Dog.
Наследование может быть также непрямым. Один класс может наследовать от другого класса, который в свою очередь может наследовать от третьего класса.
class A: def method(self): print("A method") class B(A): def another_method(self): print("B method") class C(B): def third_method(self): print("C method") c = C() c.method() c.another_method() c.third_method()
Функция super – полезная функция наследования, которая указывает на родительский класс. Предназначена для поиска метода по его имени в суперклассе объекта.
class A: def spam(self): print(1) class B(A): def spam(self): print(2) super().spam() B().spam()
super().spam() вызывает метод суперкласса spam.
Существует несколько магических методов, которые задают классам функциональность контейнеров.
__len__ для len()
__getitem__ для индексации
__setitem__ для присваивания значения индексированному элементу
__delitem__ для удаления индексированных элементов
__iter__ для перебора объектов (например, в циклах for)
__contains__ для in
Существует множество других магических методов, которые мы не будем рассматривать здесь. Например: __call__, используемый для вызова объектов как функций; __int__, __str__ и другие подобные им для преобразования объектов в родные для Python типы данных.
import random class VagueList: def __init__(self, cont): self.cont = cont def __getitem__(self, index): return self.cont[index + random.randint(-1, 1)] def __len__(self): return random.randint(0, len(self.cont)*2) vague_list = VagueList(["A", "B", "C", "D", "E"]) print(len(vague_list)) print(len(vague_list)) print(vague_list[2]) print(vague_list[2])
Мы переопределили функцию len() для класса VagueList так, чтобы она вернула случайное число.
Функция индексации также возвращает случайный элемент в заданном диапазоне со списка.
Создание, использование и уничтожение составляют жизненный цикл объекта.
Первый этап жизненного цикла объекта – определение класса, к которому он принадлежит.
Следующий этап – инстанцирование экземпляра, когда вызывается метод __init__. Выделяется память под хранение экземпляра. Непосредственно перед этим вызывается метод класса __new__. Это действие, как правило, отменяется только в редких случаях.
После этого объект готов к использованию.
Одним из ключевых понятий объектно-ориентированного программирования является инкапсуляция – упаковка в целях простоты использования связанных переменных и функций в один объект (экземпляр класса).
Сокрытие данных – близкое к инкапсуляции понятие, суть которого в том, что детали реализации класса должны быть скрыты, и чистый стандартный интерфейс должен быть представлен тем, кто будет использовать класс.
В других языках программирования это достигается с использованием частных методов и атрибутов, которые закрывают доступ извне к определенным методам и атрибутам класса.
Идеология Python несколько иначе. В сообществе Python часто звучит фраза «мы все взрослые и по своему согласию здесь», что означает, что не следует устанавливать свои ограничения на доступ к отдельным частям класса. Так как все равно невозможно обеспечить строгую частность метода или атрибута.
Условно частные методы и атрибуты оформляются с единым подчеркиванием в начале имени.
Это частные методы, которые не должны взаимодействовать со внешней частью программы. Но часто это правило условно; внешняя часть программы может получить к ним доступ.
Реальная особенность этих методов лишь в том, что from module_name import * не будет импортировать переменные, которые начинаются с единого подчеркивания.
class Queue: def __init__(self, contents): self._hiddenlist = list(contents) def push(self, value): self._hiddenlist.insert(0, value) def pop(self): return self._hiddenlist.pop(-1) def __repr__(self): return "Queue({})".format(self._hiddenlist) queue = Queue([1, 2, 3]) print(queue) queue.push(0) print(queue) queue.pop() print(queue) print(queue._hiddenlist)
Во фрагменте кода вверху атрибут _hiddenlist помечен как частный, но внешний код все же сможет получить к нему доступ.
Магический метод __repr__ возвращает экземпляр в виде строки.
Строго частные методы и атрибуты оформляются с двойным подчеркиванием в начале имени. Таким образом их имена искажаются, и внешняя часть программы не может получить к ним доступ.
Но это делается не для того, чтобы обеспечить их частность, а чтобы избежать ошибок, если где-либо в коде есть подклассы, которые имеют методы или атрибуты с такими же именами.
Методы с искаженными именами все же могут быть вызваны извне, но по другим именам. Метод __privatemethod класса Spam может быть вызван извне по имени _Spam__privatemethod.
class Spam: __egg = 7 def print_egg(self): print(self.__egg) s = Spam() s.print_egg() print(s._Spam__egg) print(s.__egg)
Методы объектов, рассмотренных нами до сих пор, вызываются экземпляром класса, который затем передается в параметр метода self.
Методы класса несколько другие: они вызываются классом, который передается параметру cls метода.
Чаще всего это используется в фабричных методах: создается экземпляр класса, при этом используются иные параметры, чем те, которые обычно передаются в конструктор класса.
Методы класса оформляются с декоратором classmethod.
class Rectangle: def __init__(self, width, height): self.width = width self.height = height def calculate_area(self): return self.width * self.height @classmethod def new_square(cls, side_length): return cls(side_length, side_length) square = Rectangle.new_square(5) print(square.calculate_area())
Статические методы похожи на методы класса с тем отличием, что они не берут никаких дополнительных аргументов; они аналогичны обычным функциям класса.
Они оформляются с декоратором staticmethod.
class Pizza: def __init__(self, toppings): self.toppings = toppings @staticmethod def validate_topping(topping): if topping == "pineapple": raise ValueError("No pineapples!") else: return True ingredients = ["cheese", "onions", "spam"] if all(Pizza.validate_topping(i) for i in ingredients): pizza = Pizza(ingredients)
Статические методы ведут себя как обычные функции с тем отличием, что вы можете вызывать их экземпляром класса.
В свойствах мы можем настроить доступ к атрибутам экземпляра.
Чтобы создать свойства, непосредственно перед методом помещается декоратор property: при вызове атрибута экземпляра с таким же именем, что и у метода, вместо него будет вызван метод.
Один из распространенных способов их применения – присвоение атрибуту свойства «только для чтения».
class Pizza: def __init__(self, toppings): self.toppings = toppings @property def pineapple_allowed(self): return False pizza = Pizza(["cheese", "tomato"]) print(pizza.pineapple_allowed) pizza.pineapple_allowed = True
Свойства также могут быть заданы с помощью функций setter/getter.
Функция setter устанавливает значение соответствующего свойства.
Функция getter возвращает значение.
Чтобы определить setter, используется декоратор с таким же именем, что и у свойства, с последующим ключевым словом setter, разделенные точкой.
Точно так же определяются функции getter.
class Pizza: def __init__(self, toppings): self.toppings = toppings self._pineapple_allowed = False @property def pineapple_allowed(self): return self._pineapple_allowed @pineapple_allowed.setter def pineapple_allowed(self, value): if value: password = input("Enter the password: ") if password == "Sw0rdf1sh!": self._pineapple_allowed = value else: raise ValueError("Alert! Intruder!") pizza = Pizza(["cheese", "tomato"]) print(pizza.pineapple_allowed) pizza.pineapple_allowed = True print(pizza.pineapple_allowed)