Как определить класс в Python?

Я умею писать скрипты на Python и теперь хочу изучить объектно-ориентированный Python. Как мне создать свой первый класс Python?

Создание класса Python означает написание кода, который позволяет объединить данные и поведение, которые лучше всего описывают конкретную концепцию или сущность. Данные в классе представлены атрибутами, а поведение класса задается методами.

Давайте создадим классную часть более крупного приложения, предоставляющего прогнозы погоды. Мы определим класс, представляющий город, включая его атрибуты и методы.

В рамках приложения прогнозов погоды мы создадим класс, представляющий город.

В этом конкретном контексте город может иметь разные атрибуты:

  • температура
  • тип погоды
  • вероятность осадков (в процентах)
  • влажность (в процентах)
  • скорость ветра (км/ч)

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

Мы начнем с атрибутов класса, первым шагом будет определение конструктора:

class City:
 
    def __init__(self, temperature, weather_type, precipitation_chance, humidity, wind_speed):
        self.temperature = temperature
        self.weather_type = weather_type
        self.precipitation_chance = precipitation_chance
        self.humidity = humidity
        self.wind_speed = wind_speed

Имя метода конструктора класса в Python — __init__. Первый параметр, принимаемый этим методом, — self, который представляет экземпляр этого класса, также известный как объект.

Давайте проясним эту концепцию, прежде чем мы продолжим…

Класс — это шаблон или схема, позволяющая создавать объекты (или экземпляры классов). Например, мы можем использовать класс City для создания двух объектов: одного для прогнозов по Лондону и одного для прогнозов по Парижу.

Каждый объект может иметь разные значения своих атрибутов и храниться в разных областях памяти.

Параметр self является первым параметром в методах класса и ссылается на экземпляр класса.

Давайте посмотрим, как мы можем создать объект типа City:

london = City(21, 'Sunny', 0.1, 0.63, 10)
print(london)
print(london.__dict__)

Первая строка создает объект под названием London. Когда мы создаем объект, метод __init__ класса City вызывается автоматически.

Как видите, при построении объекта мы передаем пять аргументов.

Но это не соответствует количеству параметров, принимаемых методом __init__, равному шести. Это связано с тем, что параметр self не нужно указывать при создании экземпляра класса, он автоматически ссылается на него.

Результат первого оператора печати:

<__main__.City object at 0x109755a10>

Он показывает ссылку на объект типа City и его расположение в памяти.

Результат второго оператора печати:

{'temperature': 21, 'weather_type': 'Sunny', 'precipitation_chance': 0.1, 'humidity': 0.63, 'wind_speed': 10}

Метод __dict__ печатает пространство имен объекта (как вы можете видеть, это словарь Python). В этом случае мы можем увидеть значение атрибутов, которые мы установили, с помощью конструктора класса City.

Позже в этом уроке мы рассмотрим пространства имен подробнее. Просмотр значений в пространстве имен объекта также может быть очень полезен при изучении наследования.

Добавление метода в наш класс Python

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

Вот метод:

def print_summary(self):
      print('Weather forecasts for London - {}\nTemperature: {}°\nChance of precipitation: {}%\nHumidity: {}%\nWind speed: {} km/h\n'
  .format(self.weather_type, self.temperature, int(self.precipitation_chance*100), int(self.humidity*100), self.wind_speed))

Как видите, единственный параметр этого метода — self. Как мы объясняли ранее, self используется для передачи экземпляра нашего класса методу.

Мы печатаем атрибуты объекта в читаемом формате.

После создания объекта города мы можем вызвать этот метод с помощью точечной записи:

london = City(21, 'Sunny', 0.1, 0.63, 10)
london.print_summary()

Результат:

Weather forecasts for London - Sunny
Temperature: 21°
Chance of precipitation: 10%
Humidity: 63%
Wind speed: 10 km/h

Вы можете увидеть, как этот метод помогает нам создать стандартный способ отображения прогнозов погоды для данного города. Если бы этого метода не было, разные разработчики, использующие наш класс City, отображали бы эту информацию по-разному.

Понимание атрибутов экземпляра

Пять атрибутов, установленных внутри метода __init__ нашего класса, называются атрибутами экземпляра (или переменными экземпляра), поскольку они принадлежат определенному экземпляру нашего класса.

Давайте создадим еще один объект на основе класса City, на этот раз для Рима.

Обратите внимание, что у нашего класса нет атрибута названия города, поэтому давайте добавим его в конструктор.

Наш класс становится:

class City:
 
    def __init__(self, city_name, temperature, weather_type, precipitation_chance, humidity, wind_speed):
        self.city_name = city_name
        self.temperature = temperature
        self.weather_type = weather_type
        self.precipitation_chance = precipitation_chance
        self.humidity = humidity
        self.wind_speed = wind_speed

    def print_summary(self):
        print('Weather forecasts for {} - {}\nTemperature: {}°\nChance of precipitation: {}%\nHumidity: {}%\nWind speed: {} km/h\n'
  .format(self.city_name, self.weather_type, self.temperature, int(self.precipitation_chance*100), int(self.humidity*100), self.wind_speed))

Я добавил в конструктор новый параметр под названием city_name, а затем установил значение атрибута экземпляра city_name.

Кроме того, я обновил метод print_summary, чтобы использовать название города из нашего нового атрибута экземпляра.

Учитывая, что количество параметров, необходимых методу __init__, изменилось, нам также придется изменить способ создания нашего объекта, нам также нужно передать строку с названием города в качестве первого аргумента.

london = City('London', 21, 'Sunny', 0.1, 0.63, 10)
london.print_summary()

rome = City('Rome', 32, 'Sunny', 0.05, 0.67, 5)
rome.print_summary() 

Вот результат, который мы получаем от метода класса print_summary:

Weather forecasts for London - Sunny
Temperature: 21°
Chance of precipitation: 10%
Humidity: 63%
Wind speed: 10 km/h

Weather forecasts for Rome - Sunny
Temperature: 32°
Chance of precipitation: 5%
Humidity: 67%
Wind speed: 5 km/h

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

print(london.temperature)
print(rome.temperature)
21
32

Как вы можете видеть, значение атрибута экземпляра температуры меняется между двумя нашими экземплярами класса City.

Мы можем сделать следующее, чтобы обновить этот атрибут экземпляра для объекта «Лондон»:

london.temperature = 23
london.print_summary()

Вывод теперь:

Weather forecasts for London - Sunny
Temperature: 23°
Chance of precipitation: 10%
Humidity: 63%
Wind speed: 10 km/h

Теперь давайте посмотрим на другой тип атрибута, который мы можем иметь в классе.

Объявление атрибутов класса в Python

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

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

Вот пример: я хочу добавить атрибут класса в наш класс City. Наш атрибут класса представляет собой список, содержащий допустимые типы погоды. Цель состоит в том, чтобы иметь возможность проверять Weather_type, передаваемый конструктору, когда мы создаем новый объект.

Чтобы определить атрибут класса, мы делаем следующее (все остальное в нашем классе пока остается неизменным):

class City:
 
    valid_weather_types = ['Sunny', 'Cloudy', 'Rainy']

Экземпляры класса имеют доступ к атрибуту класса, и то же самое относится и к самому классу.

Вот что я имею в виду:

print(london.valid_weather_types)
print(rome.valid_weather_types)
print(City.valid_weather_types)

['Sunny', 'Cloudy', 'Rainy']
['Sunny', 'Cloudy', 'Rainy']
['Sunny', 'Cloudy', 'Rainy']

Мы хотим убедиться, что наша программа выдает исключение, если через конструктор __init__ передается неверный тип погоды. Наш конструктор становится:

def __init__(self, city_name, temperature, weather_type, precipitation_chance, humidity, wind_speed):
    if weather_type not in City.valid_weather_types:
        raise ValueError('Invalid weather type provided.')
    self.city_name = city_name
    self.temperature = temperature
    self.weather_type = weather_type
    self.precipitation_chance = precipitation_chance
    self.humidity = humidity
    self.wind_speed = wind_speed

Как вы можете видеть, мы вызываем исключение ValueError, если пользователь передает недопустимый тип погоды при создании нового объекта.

Давайте создадим объект с неправильным типом погоды и посмотрим, что произойдет:

athens = City('Athens', 34, 'Partly cloudy', 0.02, 0.81, 4)

Вот ошибка, которую мы получаем:

Traceback (most recent call last):
    File "city.py", line 25, in <module>
    athens = City('Athens', 34, 'Partly cloudy', 0.02, 0.81, 4)
    File "city.py", line 7, in __init__
    raise ValueError('Invalid weather type provided.')
ValueError: Invalid weather type provided.

Как и ожидалось, мы возвращаем ValueError из-за недопустимого типа погоды.

Однако это всего лишь пример того, как конструктор может проверять атрибуты экземпляра. Это не мешает нам устанавливать неправильное значение для Weather_type вне конструктора.

Это то, что я имею в виду:

athens = City('Athens', 34, 'Cloudy', 0.02, 0.81, 4)
athens.weather_type = 'Partly cloudy'
print(athens.weather_type)

Partly cloudy

Мы создали объект Афины, используя действительный тип Weather_type, а затем установили для него значение недопустимого типа Weather_Type вне конструктора, не получив никаких ошибок.

В другом уроке я покажу вам, как можно улучшить проверку. На данный момент цель — начать знакомство с классами Python.

Пространства имен классов и экземпляров Python

Чтобы понять, как работают атрибуты класса и экземпляра, важно представить концепцию пространства имен, применяемую к классу Python.

Пространство имен — это способ создания сопоставления между именами и объектами в Python , реализованный с использованием структуры данных словаря.

Мы рассмотрим пространства имен для класса City и экземпляра класса london, чтобы увидеть разницу между ними.

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

Вот пространство имен для класса City:

print(City.__dict__)

{'__module__': '__main__', 'valid_weather_types': ['Sunny', 'Cloudy', 'Rainy'], '__init__': <function City.__init__ at 0x105b01710>, 'print_summary': <function City.print_summary at 0x105b0eb90>, '__dict__': <attribute '__dict__' of 'City' objects>, '__weakref__': <attribute '__weakref__' of 'City' objects>, '__doc__': None}

А вот пространство имен для экземпляра класса london:

print(london.__dict__)

{'city_name': 'London', 'temperature': 21, 'weather_type': 'Sunny', 'precipitation_chance': 0.1, 'humidity': 0.63, 'wind_speed': 10}

Итак, мы видим, что атрибут класса valid_weather_types находится в пространстве имен класса, а не в пространстве имен экземпляра. Пространство имен экземпляра содержит только атрибуты, установленные в конструкторе класса.

Итак, как я могу сделать следующее, если пространство имен экземпляра не содержит атрибут valid_weather_types?

print(london.valid_weather_types)

['Sunny', 'Cloudy', 'Rainy']

Вот логика, которой следует Python:

  • Найдите атрибут в пространстве имен экземпляра.
  • Если атрибут отсутствует в пространстве имен экземпляра, просмотрите пространство имен класса (именно там Python находит атрибут класса valid_weather_types).

Теперь предположим, что мы делаем следующее:

london.valid_weather_types = ['Sunny', 'Cloudy', 'Partly cloudy', 'Rainy']

Изменяем ли мы атрибут класса, который мы определили ранее?

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

print(City.__dict__)
{'__module__': '__main__', 'valid_weather_types': ['Sunny', 'Cloudy', 'Rainy'], '__init__': <function City.__init__ at 0x10c773710>, 'print_summary': <function City.print_summary at 0x10c780b90>, '__dict__': <attribute '__dict__' of 'City' objects>, '__weakref__': <attribute '__weakref__' of 'City' objects>, '__doc__': None}

print(london.__dict__)
{'city_name': 'London', 'temperature': 21, 'weather_type': 'Sunny', 'precipitation_chance': 0.1, 'humidity': 0.63, 'wind_speed': 10, 'valid_weather_types': ['Sunny', 'Cloudy', 'Partly cloudy', 'Rainy']}

Судя по приведенным выше выводам, значение valid_weather_types в пространстве имен класса не изменилось. В то же время мы видим, что атрибут valid_weather_types был добавлен в пространство имен экземпляра с новым значением, которое мы предоставили.

Итак, наше задание добавило новый атрибут экземпляра к экземпляру Лондона.

Этот новый атрибут не виден другим экземплярам, ​​которые продолжают ссылаться на атрибут, установленный на уровне класса.

Давайте подтвердим, что это относится к экземпляру rome:

print(rome.__dict__)
{'city_name': 'Rome', 'temperature': 32, 'weather_type': 'Sunny', 'precipitation_chance': 0.05, 'humidity': 0.67, 'wind_speed': 5}

print(rome.valid_weather_types)
['Sunny', 'Cloudy', 'Rainy']

Как и ожидалось, атрибут valid_weather_types отсутствует в пространстве имен экземпляра rome и разрешается через пространство имен класса.

Как использовать атрибуты класса

Существуют разные способы использования атрибутов класса. Вы можете применить этот пример ко многим типам приложений.

Мы хотим отслеживать количество городов, для которых мы предоставляем прогнозы погоды. Для хранения этой информации мы можем использовать атрибут класса, который мы увеличиваем каждый раз, когда создаем новый экземпляр класса.

Давайте определим атрибут класса под названием city_number. Мы будем увеличивать этот атрибут каждый раз, когда создаем новый экземпляр. Это означает, что нам также придется изменить реализацию конструктора __init__.

Наш класс становится:

class City:
  
    cities_number = 0
    valid_weather_types = ['Sunny', 'Cloudy', 'Rainy']

    def __init__(self, city_name, temperature, weather_type, precipitation_chance, humidity, wind_speed):
        if weather_type not in City.valid_weather_types:
            raise ValueError('Invalid weather type provided.')
        self.city_name = city_name
        self.temperature = temperature
        self.weather_type = weather_type
        self.precipitation_chance = precipitation_chance
        self.humidity = humidity
        self.wind_speed = wind_speed
        City.cities_number += 1

[the print_summary method doesn't change]

Теперь посмотрим, что происходит со значением атрибута класса city_number после создания двух экземпляров:

london = City('London', 21, 'Sunny', 0.1, 0.63, 10)
print(City.cities_number)
1

rome = City('Rome', 32, 'Sunny', 0.05, 0.67, 5)
print(City.cities_number)
2

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

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

Определение константы с использованием атрибута класса

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

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

Формула для преобразования температуры по Цельсию (Tc) в градусы Фаренгейта (Tf) следующая:

Tf = Tc * 9/5+32

В нашем классе давайте определим 9/5 (что равно 1,8) как атрибут класса, называемый temp_conversion_factor.

Затем мы будем использовать этот атрибут класса для печати температуры по Фаренгейту с помощью метода print_summary. Ниже вы можете увидеть обновленный класс:

class City:
    cities_number = 0
    valid_weather_types = ['Sunny', 'Cloudy', 'Rainy']
    temperature_conversion_factor = 1.8

    def __init__(self, city_name, temperature, weather_type, precipitation_chance, humidity, wind_speed):
        if weather_type not in City.valid_weather_types:
            raise ValueError('Invalid weather type provided.')
        self.city_name = city_name
        self.temperature = temperature
        self.weather_type = weather_type
        self.precipitation_chance = precipitation_chance
        self.humidity = humidity
        self.wind_speed = wind_speed
        City.cities_number += 1

    def print_summary(self):
        print('Weather forecasts for {} - {}\nTemperature: {}°C / {}°F\nChance of precipitation: {}%\nHumidity: {}%\nWind speed: {} km/h\n'
            .format(self.city_name, self.weather_type, self.temperature, int(self.temperature*City.temperature_conversion_factor+32), int(self.precipitation_chance*100), int(self.humidity*100), self.wind_speed))

Давайте посмотрим на часть кода, которая преобразует температуру из Цельсия в Фаренгейт:

int(self.temperature*City.temperature_conversion_factor+32)

Следует подчеркнуть, что в этой формуле я использую атрибут класса temp_conversion_factor.

Учитывая, что это атрибут класса, для ссылки на него я использую имя класса (Город), за которым следует точка и имя атрибута.

Если я вызову метод print_summary для объектов Лондона и Рима, я получу следующий результат:

Weather forecasts for London - Sunny
Temperature: 21°C/69°F
Chance of precipitation: 10%
Humidity: 63%
Wind speed: 10 km/h

Weather forecasts for Rome - Sunny
Temperature: 32°C/89°F
Chance of precipitation: 5%
Humidity: 67%
Wind speed: 5 km/h

И это все для этого урока!

Вот видеообзор, который поможет вам сохранить больше информации о создании класса в Python:

Заключение

В заключение, в этом уроке мы поработали над определением класса Python. Мы начали с метода класса __init__, который работает как конструктор и позволяет создавать новые экземпляры (или объекты) классов.

Внутри конструктора мы установили атрибуты экземпляра на основе значений, переданных пользователем при создании объекта типа City.

Затем мы определили метод, который печатает сводку прогнозов погоды для конкретного города. Очень полезно для стандартизации способа показа данных нашим пользователям.

Следующим шагом стало понимание разницы между атрибутами экземпляра и атрибутами класса. Мы также увидели роль пространств имен в том, как атрибуты класса и экземпляра идентифицируются в программе Python.

Наконец, мы увидели, как использовать атрибуты класса для:

  • отслеживать показатели на уровне класса (например, количество городов, для которых мы предоставляем прогнозы погоды).
  • определите константу, которую вы можете использовать в методах нашего класса.

Теперь у вас достаточно знаний, чтобы начать создавать свои собственные классы!