Классы
Классы - это ядро Python. Они дают широкие возможности, но их легко неправильно использовать. В этой главе я расскажу про некоторые трюки в работе с классами и сделаю несколько предостережений. Давайте начнем!

Переменные экземпляра и класса

Большинство начинающих, а иногда и опытных Python разработчиков не понимают до конца различия между переменными экземпляра и переменными класса. Это приводит к некорректному использованию этих различных типов переменных. Давайте разберемся.
Основное различие:
    Переменные экземпляров предназначены для данных уникальных для каждого
    объекта
    Переменные класса - для общих для всех экземпляров класса данных
Посмотрим на пример:
1
class Cal(object):
2
# pi - переменная класса
3
pi = 3.142
4
5
def __init__(self, radius):
6
# self.radius - переменная экземпляра
7
self.radius = radius
8
9
def area(self):
10
return self.pi * (self.radius ** 2)
11
12
a = Cal(32)
13
a.area()
14
# Вывод: 3217.408
15
a.pi
16
# Вывод: 3.142
17
a.pi = 43
18
a.pi
19
# Вывод: 43
20
21
b = Cal(44)
22
b.area()
23
# Вывод: 6082.912
24
b.pi
25
# Вывод: 3.142
26
b.pi = 50
27
b.pi
28
# Вывод: 50
Copied!
С использованием изменяемых переменных класса редко бывают проблемы. Поэтому разработчики обычно не стараются изучить тему подробнее - все и так работает! Если вы тоже уверены в надежности использования таких переменных, то следующий пример для вас:
1
class SuperClass(object):
2
superpowers = []
3
4
def __init__(self, name):
5
self.name = name
6
7
def add_superpower(self, power):
8
self.superpowers.append(power)
9
10
foo = SuperClass('foo')
11
bar = SuperClass('bar')
12
foo.name
13
# Вывод: 'foo'
14
15
bar.name
16
# Вывод: 'bar'
17
18
foo.add_superpower('fly')
19
bar.superpowers
20
# Вывод: ['fly']
21
22
foo.superpowers
23
# Вывод: ['fly']
Copied!
Красота неправильного использования изменяемых переменных класса. Для предотвращения подобных ошибок - не храните изменяемые структуры данных в переменных класса или понимайте зачем вам это нужно.

Классы нового стиля

Классы нового стиля были представлены в Python 2.1, но не так много разработчиков знает о них даже сейчас! Отчасти это связано с сохранением поддержки классов старого стиля для обратной совместимости. Давайте рассмотрим разницу между двумя стилями:
    Классы старого стиля ничему не наследуют
    Классы нового стиля наследуют object
Базовый пример:
1
class OldClass():
2
def __init__(self):
3
print('Я старый класс')
4
5
class NewClass(object):
6
def __init__(self):
7
print('Я новый модный класс')
8
9
old = OldClass()
10
# Вывод: Я старый класс
11
12
new = NewClass()
13
# Вывод: Я новый модный класс
Copied!
Наследование от object дает новым классам доступ к определенной магии. Например, вы можете использовать оптимизацию __slots__, вызов super() и дескрипторы. Резюме? Всегда используйте классы нового стиля.
Примечание: в Python 3 все классы нового стиля. Не важно наследует ли ваш класс object или нет. Тем не менее, хорошей идеей будет явно прописывать наследование.

Магические методы

Классы в Python известны за свои магические методы, их отличительная черта - двойное нижнее подчеркивание с двух сторон от имени. Давайте рассмотрим несколько из них.

__init__

Это конструктор класса. Конструктор вызывается каждый раз при создании экземпляра класса. Например:
1
class GetTest(object):
2
def __init__(self):
3
print('Приветствую!')
4
5
def another_method(self):
6
print('Я другой метод, который не вызывается автоматически')
7
8
a = GetTest()
9
# Вывод: Приветствую!
10
11
a.another_method()
12
# Вывод: Я другой метод, который не вызывается автоматически
Copied!
Как вы видите __init__ вызывается при создании экземпляра класса. Вы также можете передавать аргументы конструктору для инициализации экземпляра:
1
class GetTest(object):
2
def __init__(self, name):
3
print('Приветствую! {0}'.format(name))
4
5
def another_method(self):
6
print('Я другой метод, который не вызывается автоматически')
7
8
a = GetTest('Yasoob')
9
# Output: Приветствую! Yasoob
10
11
# Попробуем создать экземпляр без аргумента "name"
12
b = GetTest()
13
Traceback (most recent call last):
14
File "<stdin>", line 1, in <module>
15
TypeError: __init__() takes exactly 2 arguments (1 given)
Copied!
Надеюсь в общих чертах логика работы __init__ понятна.

__getitem__

Реализация метода __getitem__ в классе позволяет использовать на его экземплярах [] оператор. Пример:
1
class GetTest(object):
2
def __init__(self):
3
self.info = {
4
'name':'Yasoob',
5
'country':'Pakistan',
6
'number':12345812
7
}
8
9
def __getitem__(self,i):
10
return self.info[i]
11
12
foo = GetTest()
13
14
foo['name']
15
# Вывод: 'Yasoob'
16
17
foo['number']
18
# Вывод: 12345812
Copied!
Без реализации __getitem__ вы бы получили следующую ошибку:
1
>>> foo['name']
2
3
Traceback (most recent call last):
4
File "<stdin>", line 1, in <module>
5
TypeError: 'GetTest' object has no attribute '__getitem__'
Copied!
Last modified 3yr ago