>>> class EmailPerson(Person):
…·····def __init__(self, name, email):
…·········super().__init__(name)
…·········self.email = email
Когда вы определяете метод __init__() для своего класса, вы заменяете метод __init__() родительского класса, который больше не вызывается автоматически. В результате вам нужно вызывать его явно. Происходит следующее.
• Метод super() получает определение родительского класса Person.
• Метод __init__() вызывает метод Person.__init__(). Последний заботится о том, чтобы передать аргумент self суперклассу, поэтому вам нужно лишь передать опциональные аргументы. В нашем случае единственным аргументом класса Person() будет name.
• Строка self.email = email — это новый код, который отличает класс EmailPerson от класса Person.
Теперь создадим одну персону:
>>> bob = EmailPerson('Bob Frapples', 'bob@frapples.com')
Мы должны иметь доступ к атрибутам name и email:
>>> bob.name
'Bob Frapples'
>>> bob.email
'bob@frapples.com'
Почему бы нам просто не определить новый класс так, как показано далее?
>>> class EmailPerson(Person):
…·····def __init__(self, name, email):
…·········self.name = name
…·········self.email = email
Мы могли бы сделать это, но в таком случае потеряли бы возможность применять наследование. Мы использовали метод super(), чтобы создать объект, который работает примерно так же, как и объект класса Person. Есть и другое преимущество: если определение класса Person в будущем изменится, с помощью метода super() мы сможем гарантировать, что атрибуты и методы, которые класс EmailPerson наследует от класса Person, отреагируют на изменения.
Используйте метод super(), когда потомок делает что-то самостоятельно, но ему все еще нужно что-то от предка (как и в реальной жизни).
В защиту self
Python критикуют за то, что, помимо применения пробелов, необходимо включать self в качестве первого аргумента методов экземпляра класса (методов, которые вы видели в предыдущем примере). Python использует аргумент self, чтобы найти атрибуты и методы правильного объекта. Например, я покажу, как вы можете вызвать метод объекта и что Python сделает при этом за кулисами.
Помните класс Car из предыдущих примеров? Снова вызовем метод exclaim():
>>> car = Car()
>>> car.exclaim()
I'm a Car!
Вот что происходит за кулисами Python.
• Выполняется поиск класса (Car) объекта car.
• Объект car передается методу exclaim() класса Car как параметр self.
Ради забавы вы и сами можете запустить пример таким образом, и он сработает точно так же, как и нормальный синтаксис (car.exclaim()):
>>> Car.exclaim(car)
I'm a Car!
Однако нет причин использовать такой более длинный стиль.
Получаем и устанавливаем значение атрибутов с помощью свойств
Отдельные объектно-ориентированные языки поддерживают закрытые атрибуты объектов, к которым нельзя получить доступ непосредственно; программистам зачастую приходится писать геттеры и сеттеры, чтобы считать и записать значения таких атрибутов.
В Python геттеры и сеттеры не нужны, поскольку все атрибуты и методы являются открытыми, а от вас ожидается примерное поведение. Если прямой доступ к атрибутам заставляет вас нервничать, вы, конечно, можете написать геттеры и сеттеры. Но сделайте это более характерным для Python способом — используйте свойства.
В этом примере мы определим класс Duck, имеющий один атрибут hidden_name. (В следующем разделе я покажу вам более удачный способ именовать атрибуты, которые вы хотите оставить закрытыми.) Мы не хотим, чтобы люди обращались к атрибуту напрямую, поэтому определим два метода: геттер (get_name()) и сеттер (set_name()). Я добавил выражение print() в каждый из них, чтобы показать момент его вызова. Наконец, мы определим эти методы как свойства атрибута name:
>>> class Duck():
…·····def __init__(self, input_name):
…·········self.hidden_name = input_name
…·····def get_name(self):
…·········print('inside the getter')
…·········return self.hidden_name
…·····def set_name(self, input_name):
…·········print('inside the setter')
…·········self.hidden_name = input_name
…·····name = property(get_name, set_name)
Новые методы действуют как обычные геттеры и сеттеры до последней строки, где они указываются как свойства атрибута name. Первый аргумент функции property() — это геттер, а второй — это сеттер. Теперь, когда вы обращаетесь к атрибуту name любого объекта Duck, вызывается метод get_name(), который возвращает его:
>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
Вы все еще можете вызвать метод get_name() непосредственно, как обычный геттер:
>>> fowl.get_name()
inside the getter
'Howard'
Когда вы присваиваете значение атрибуту name, вызывается метод set_name():
>>> fowl.name = 'Daffy'
inside the setter
>>> fowl.name
inside the getter
'Daffy'
Метод set_name() вы также можете вызвать непосредственно:
>>> fowl.set_name('Daffy')
inside the setter
>>> fowl.name
inside the getter
'Daffy'
Еще один способ определить свойства — это декораторы. В следующем примере мы определим два разных метода с именем name(), предшествовать которым будут разные декораторы:
• @property, который размещается перед геттером;
• @name.setter, который размещается перед сеттером.
В коде они выглядят так:
>>> class Duck():
…·····def __init__(self, input_name):
…·········self.hidden_name = input_name
…·····@property
…·····def name(self):
…·········print('inside the getter')
…·········return self.hidden_name
…·····@name.setter
…·····def name(self, input_name):
…·········print('inside the setter')
…·········self.hidden_name = input_name
Вы все еще можете получать доступ к атрибуту name, но в этом случае не существует видимых методов get_name() или set_name():
>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'
Если кто-то догадается, что мы называли наш атрибут hidden_name, он сможет считать и записать его непосредственно с помощью конструкции fowl.hidden_name. В следующем разделе вы увидите особый способ именования закрытых атрибутов в Python.
В обоих предыдущих примерах мы использовали свойство name, чтобы обратиться к отдельному атрибуту (в нашем случае hidden_name), который хранится внутри объекта. Свойство может ссылаться и на вычисляемое значение. Определим класс Circle, который имеет атрибут radius и вычисляемое свойство diameter:
>>> class Circle():
…·····def __init__(self, radius):
…·········self.radius = radius
…·····@property
…·····def diameter(self):
…·········return 2 * self.radius
…
Мы создаем объект класса Circle, задав значение его атрибута radius:
>>> c = Circle(5)
>>> c.radius
5
Мы можем обратиться к свойству diameter точно так же, как к атрибуту вроде radius: