• en
  • Language: ru
  • Documentation version: latest

18. Занятия

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

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

Большинство начинающих и даже некоторые опытные программисты Python не понимают различия между переменными экземпляра и переменными класса. Непонимание заставляет их неправильно использовать эти разные типы переменных. Давайте разберемся в них.

Основное различие заключается в следующем:

  • Переменные экземпляра предназначены для данных, которые уникальны для каждого объекта

  • Переменные класса предназначены для данных, разделяемых между различными экземплярами класса

Давайте рассмотрим пример:

class Cal(object):
    # pi is a class variable
    pi = 3.142

    def __init__(self, radius):
        # self.radius is an instance variable
        self.radius = radius

    def area(self):
        return self.pi * (self.radius ** 2)

a = Cal(32)
a.area()
# Output: 3217.408
a.pi
# Output: 3.142
a.pi = 43
a.pi
# Output: 43

b = Cal(44)
b.area()
# Output: 6082.912
b.pi
# Output: 3.142
b.pi = 50
b.pi
# Output: 50

При использовании неизменяемых переменных класса возникает не так много проблем. Это основная причина, по которой новички не пытаются узнать больше об этой теме, потому что все работает! Если вы также считаете, что переменные экземпляра и класса не могут вызвать никаких проблем при неправильном использовании, то посмотрите следующий пример.

class SuperClass(object):
    superpowers = []

    def __init__(self, name):
        self.name = name

    def add_superpower(self, power):
        self.superpowers.append(power)

foo = SuperClass('foo')
bar = SuperClass('bar')
foo.name
# Output: 'foo'

bar.name
# Output: 'bar'

foo.add_superpower('fly')
bar.superpowers
# Output: ['fly']

foo.superpowers
# Output: ['fly']

В этом и заключается прелесть неправильного использования переменных класса mutable. Чтобы обезопасить свой код от такого рода неожиданных атак, убедитесь, что вы не используете переменные класса mutable. Вы можете использовать их только в том случае, если знаете, что делаете.

18.2. 2. Новые классы стиля

Классы нового стиля были введены в Python 2.1, но многие люди не знают о них даже сейчас! Это так, потому что Python также поддерживает классы старого стиля просто для поддержания обратной совместимости. Я много говорил о новых и старых классах, но не рассказал вам о разнице. Основная разница заключается в следующем:

  • Старые базовые классы не наследуются ни от чего

  • Базовые классы нового стиля наследуются от object

Очень простой пример:

class OldClass():
    def __init__(self):
        print('I am an old class')

class NewClass(object):
    def __init__(self):
        print('I am a jazzy new class')

old = OldClass()
# Output: I am an old class

new = NewClass()
# Output: I am a jazzy new class

Это наследование от object позволяет новым классам стилей использовать некоторую магию. Основным преимуществом является то, что вы можете использовать некоторые полезные оптимизации, такие как __slots__. Вы можете использовать super(), дескрипторы и тому подобное. Итог? Всегда старайтесь использовать классы нового стиля.

Примечание: В Python 3 есть только классы нового стиля. Не имеет значения, будете ли вы создавать подклассы из object или нет. Однако рекомендуется все же создавать подклассы из object.

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

Классы Python славятся своими магическими методами, которые обычно называют dunder (двойное подчеркивание). Я собираюсь обсудить несколько из них.

  • __init__

Это инициализатор класса. Каждый раз, когда создается экземпляр класса, вызывается его метод __init__. Например:

class GetTest(object):
    def __init__(self):
        print('Greetings!!')
    def another_method(self):
        print('I am another method which is not'
              ' automatically called')

a = GetTest()
# Output: Greetings!!

a.another_method()
# Output: I am another method which is not automatically
# called

Видно, что __init__ вызывается сразу после создания экземпляра. Вы также можете передавать аргументы классу во время его инициализации. Например:

class GetTest(object):
    def __init__(self, name):
        print('Greetings!! {0}'.format(name))
    def another_method(self):
        print('I am another method which is not'
              ' automatically called')

a = GetTest('yasoob')
# Output: Greetings!! yasoob

# Try creating an instance without the name arguments
b = GetTest()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() takes exactly 2 arguments (1 given)

Я уверен, что теперь вы понимаете метод __init__.

  • __getitem__

Реализация getitem в классе позволяет его экземплярам использовать оператор [] (индексатор). Вот пример:

class GetTest(object):
    def __init__(self):
        self.info = {
            'name':'Yasoob',
            'country':'Pakistan',
            'number':12345812
        }

    def __getitem__(self,i):
        return self.info[i]

foo = GetTest()

foo['name']
# Output: 'Yasoob'

foo['number']
# Output: 12345812

Без метода __getitem__ мы бы получили эту ошибку:

>>> foo['name']

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'GetTest' object has no attribute '__getitem__'