Классы в Python: поля экземпляра и переменные уровня класса
Класс и его параметры
Параметры класса в Python разделяются на два уровня. Уровень класса содержит атрибуты, общие для всех экземпляров. Уровень экземпляра содержит атрибуты, которые создаются в методе __init__. Аргументы, переданные после имени класса, попадают в __init__ и становятся индивидуальными параметрами объекта.
Основной способ определить индивидуальные параметры объекта, это объявить метод __init__ с аргументами и сохранить их через self.
class Car:
def __init__(self, brand, price):
self.brand = brand
self.price = price
car = Car('Skoda', 1200000)
print(car.brand, car.price)
параметры класса python (параметры класса в python)
Skoda 1200000
При вызове Car('Skoda', 1200000) значения brand и price сразу связываются с объектом car. Обращение car.brand и car.price даёт переданные данные.
Ошибка избыточности. Если параметр одинаков для всех экземпляров, его не следует класть в __init__. Такое значение объявляется как переменная класса.
Как задать параметр, общий для всех экземпляров класса?
Переменную, объявленную в теле класса, называют атрибутом класса. Она доступна через сам класс и через объекты.
class Car:
wheels = 4
car1 = Car()
car2 = Car()
print(Car.wheels, car1.wheels, car2.wheels)
4 4 4
Запись Car.wheels = 6 изменит значение для всех экземпляров одновременно.
Как сделать параметр необязательным?
В сигнатуре __init__ присваивается значение по умолчанию, и тогда аргумент можно не передавать.
class Car:
def __init__(self, brand, price=None):
self.brand = brand
self.price = price
car = Car('Lada')
print(car.brand, car.price)
Lada None
Значение None удобно как маркер отсутствия данных. Позже этому атрибуту присваивается реальное значение.
Как принять переменное число параметров при создании объекта?
Параметр *args собирает позиционные аргументы в кортеж, а **kwargs собирает именованные аргументы в словарь.
class Notebook:
def __init__(self, **options):
self.options = options
note = Notebook(cpu='Intel', ram=16)
print(note.options)
{'cpu': 'Intel', 'ram': 16}
Такой способ подходит, когда у разных объектов набор параметров не фиксирован.
Как создать вычисляемый параметр без сохранения значения?
Декоратор property превращает метод в свойство, доступное для чтения.
class Square:
def __init__(self, side):
self.side = side
@property
def area(self):
return self.side ** 2
s = Square(5)
print(s.area)
25
Опасный изменяемый аргумент по умолчанию. Список или словарь в качестве значения по умолчанию создаётся один раз. Все будущие объекты получат один и тот же объект.
class Order:
def __init__(self, goods=[]):
self.goods = goods
order1 = Order()
order1.goods.append('apple')
order2 = Order()
print(order2.goods)
['apple']
Решение состоит в том, чтобы использовать в параметре значение None и внутри метода создавать новый список.
class Order:
def __init__(self, goods=None):
if goods is None:
goods = []
self.goods = goods
order1 = Order()
order1.goods.append('apple')
order2 = Order()
print(order2.goods)
[]
Примеры работы с параметрами класса
В этом разделе показаны дополнительные варианты использования параметров класса и параметров экземпляра.
Атрибут класса, который изменяется через класс или через объект
class Server:
port = 8080
srv1 = Server()
srv2 = Server()
Server.port = 9090
print(srv1.port, srv2.port)
srv1.port = 7070
print(srv1.port, Server.port, srv2.port)
9090 9090 7070 9090 9090
Пока атрибут не переопределён в объекте, объект получает значение из класса. Присваивание srv1.port создаёт новый атрибут только у srv1.
Подсчёт экземпляров через атрибут класса
class Item:
total = 0
def __init__(self):
self.__class__.total += 1
first = Item()
second = Item()
print(Item.total, first.total, second.total)
2 2 2
Ссылка self.__class__ ведёт на класс Item, поэтому запись total += 1 меняет значение во всём классе.
Ключевые аргументы в методе __init__
class AppConfig:
def __init__(self, *, host, port=80):
self.host = host
self.port = port
cfg = AppConfig(host='localhost')
print(cfg.host, cfg.port)
localhost 80
Звёздочка в сигнатуре запрещает передавать host и port позиционно, порядок параметров не влияет на вызов.
Переменное число позиционных и именованных параметров
class Event:
def __init__(self, name, *tags, **meta):
self.name = name
self.tags = tags
self.meta = meta
event = Event('release', 'v1', 'stable', date='2025-01-01')
print(event.name)
print(event.tags)
print(event.meta)
release
('v1', 'stable')
{'date': '2025-01-01'}
Параметр *tags собирает лишние позиционные значения, параметр **meta собирает именованные значения.
Вычисляемый параметр экземпляра на основе других параметров
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
return self.width * self.height
rect = Rectangle(3, 4)
print(rect.area)
12
Свойство area не хранится как отдельная переменная. Его значение пересчитывается каждый раз при обращении.
Проверка значения в setter
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, amount):
if amount < 0:
raise ValueError('Баланс не может быть отрицательным')
self._balance = amount
account = BankAccount('Ivan', 1000)
print(account.balance)
account.balance = 1500
print(account.balance)
1000 1500
Через _balance внутри класса хранится исходное значение, а доступ к нему извне проходит через свойство balance.
Автоматическая генерация __init__ с dataclass
from dataclasses import dataclass
@dataclass
class Book:
title: str
author: str
pages: int = 0
book = Book('Мастер и Маргарита', 'Булгаков', 480)
print(book)
Book(title='Мастер и Маргарита', author='Булгаков', pages=480)
Декоратор dataclass сам создаёт метод __init__, а также строковое представление объекта.
Фиксированный набор параметров со __slots__
class Point3D:
__slots__ = ('x', 'y', 'z')
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
p = Point3D(1, 2, 3)
try:
p.q = 5
except AttributeError as exc:
print(exc)
'Point3D' object has no attribute 'q'
Поле __slots__ ограничивает список возможных параметров экземпляра, за счёт этого уменьшается память объекта.