Классы -- основа объектно-ориентированного программирования в Python. Класс определяет структуру данных и поведение объектов. В этой статье разберём создание классов, методы инициализации, статические и классовые методы.
Создание классов
# Basic class definition
class User:
"""Represent a user in the system."""
# Class attribute (shared by all instances)
platform = "IT Crib"
def __init__(self, name: str, age: int, email: str) -> None:
"""Initialize a new User instance."""
# Instance attributes (unique to each instance)
self.name = name
self.age = age
self.email = email
self.is_active = True
def greet(self) -> str:
"""Return a greeting message."""
return f"Привет! Я {self.name}, мне {self.age} лет."
def deactivate(self) -> None:
"""Deactivate the user account."""
self.is_active = False
print(f"Аккаунт {self.name} деактивирован")
# Creating instances
user1 = User("Иван", 25, "[email protected]")
user2 = User("Мария", 30, "[email protected]")
print(user1.greet()) # Привет! Я Иван, мне 25 лет.
print(user2.name) # Мария
print(user1.platform) # IT Crib
print(User.platform) # IT Crib (access via class)
# Instance attributes are independent
user1.deactivate()
print(user1.is_active) # False
print(user2.is_active) # True
self -- ссылка на экземпляр
class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def distance_to(self, other: "Point") -> float:
"""Calculate distance to another point."""
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
def translate(self, dx: float, dy: float) -> "Point":
"""Return a new translated point."""
return Point(self.x + dx, self.y + dy)
p1 = Point(0, 0)
p2 = Point(3, 4)
print(p1.distance_to(p2)) # 5.0
# self is just a convention - it's the first argument
# These are equivalent:
p1.distance_to(p2)
Point.distance_to(p1, p2) # explicit class call
init и инициализация
class BankAccount:
"""Bank account with balance and transaction history."""
def __init__(self, owner: str, balance: float = 0.0) -> None:
self.owner = owner
self._balance = balance # convention: _ means "protected"
self._transactions: list[tuple[str, float]] = []
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
self._balance += amount
self._transactions.append(("deposit", amount))
def withdraw(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Сумма должна быть положительной")
if amount > self._balance:
raise ValueError("Недостаточно средств")
self._balance -= amount
self._transactions.append(("withdraw", amount))
def get_balance(self) -> float:
return self._balance
# Usage
account = BankAccount("Иван", 1000.0)
account.deposit(500)
account.withdraw(200)
print(account.get_balance()) # 1300.0
# Validation in __init__
class Temperature:
def __init__(self, celsius: float) -> None:
if celsius < -273.15:
raise ValueError("Температура не может быть ниже абсолютного нуля")
self.celsius = celsius
@property
def fahrenheit(self) -> float:
return self.celsius * 9/5 + 32
Атрибуты класса vs экземпляра
class Dog:
# Class attributes - shared by ALL instances
species = "Canis familiaris"
count = 0 # track number of instances
def __init__(self, name: str, breed: str) -> None:
# Instance attributes - unique to each instance
self.name = name
self.breed = breed
Dog.count += 1 # modify class attribute via class name
# Class attribute is shared
d1 = Dog("Бобик", "Овчарка")
d2 = Dog("Шарик", "Лабрадор")
print(Dog.count) # 2
print(d1.species) # Canis familiaris
print(d2.species) # Canis familiaris
# TRAP: mutable class attributes
class BadExample:
items = [] # shared between ALL instances!
def add(self, item):
self.items.append(item)
a = BadExample()
b = BadExample()
a.add("hello")
print(b.items) # ['hello'] - BUG! Both share the same list
# CORRECT: initialize mutable attributes in __init__
class GoodExample:
def __init__(self):
self.items = [] # each instance gets its own list
@staticmethod и @classmethod
@staticmethod
class MathUtils:
"""Collection of math utility functions."""
@staticmethod
def is_prime(n: int) -> bool:
"""Check if a number is prime."""
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
@staticmethod
def gcd(a: int, b: int) -> int:
"""Calculate Greatest Common Divisor."""
while b:
a, b = b, a % b
return a
@staticmethod
def factorial(n: int) -> int:
"""Calculate factorial iteratively."""
result = 1
for i in range(2, n + 1):
result *= i
return result
# Called via class (no instance needed)
print(MathUtils.is_prime(17)) # True
print(MathUtils.gcd(48, 18)) # 6
print(MathUtils.factorial(10)) # 3628800
# Can also call via instance (but class is preferred)
m = MathUtils()
print(m.is_prime(7)) # True
@classmethod
import json
from datetime import datetime
class User:
"""User with alternative constructors."""
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
self.created_at = datetime.now()
@classmethod
def from_dict(cls, data: dict) -> "User":
"""Create User from a dictionary."""
return cls(data["name"], data["age"])
@classmethod
def from_json(cls, json_str: str) -> "User":
"""Create User from a JSON string."""
data = json.loads(json_str)
return cls(data["name"], data["age"])
@classmethod
def from_birth_year(cls, name: str, birth_year: int) -> "User":
"""Create User from birth year."""
age = datetime.now().year - birth_year
return cls(name, age)
# Alternative constructors
user1 = User("Иван", 25)
user2 = User.from_dict({"name": "Мария", "age": 30})
user3 = User.from_json('{"name": "Пётр", "age": 28}')
user4 = User.from_birth_year("Анна", 2001)
print(user2.name) # Мария
print(user3.name) # Пётр
print(user4.age) # 25 (in 2026)
# classmethod and inheritance
class Admin(User):
def __init__(self, name: str, age: int) -> None:
super().__init__(name, age)
self.role = "admin"
# cls refers to Admin, not User!
admin = Admin.from_dict({"name": "Суперадмин", "age": 35})
print(type(admin)) # <class 'Admin'>
print(admin.role) # admin
str и repr
class Product:
"""Product with string representations."""
def __init__(self, name: str, price: float, quantity: int = 0) -> None:
self.name = name
self.price = price
self.quantity = quantity
def __repr__(self) -> str:
"""Unambiguous representation for developers (debugging)."""
return f"Product({self.name!r}, {self.price!r}, quantity={self.quantity!r})"
def __str__(self) -> str:
"""Readable representation for users."""
return f"{self.name}: {self.price:,.2f} руб. (в наличии: {self.quantity})"
p = Product("Python Book", 1500.0, 10)
# __str__ is used by print() and str()
print(p) # Python Book: 1,500.00 руб. (в наличии: 10)
print(str(p)) # same
# __repr__ is used in REPL, containers, and debugging
print(repr(p)) # Product('Python Book', 1500.0, quantity=10)
print([p]) # [Product('Python Book', 1500.0, quantity=10)]
print(f"{p!r}") # Product('Python Book', 1500.0, quantity=10)
# If only __repr__ is defined, it's used for both
class Simple:
def __init__(self, value):
self.value = value
def __repr__(self):
return f"Simple({self.value!r})"
s = Simple(42)
print(s) # Simple(42) (__repr__ used as fallback for __str__)
print(repr(s)) # Simple(42)
Правила для repr и str
class Coordinate:
def __init__(self, lat: float, lon: float) -> None:
self.lat = lat
self.lon = lon
def __repr__(self) -> str:
# Goal: eval(repr(obj)) should recreate the object
return f"Coordinate({self.lat}, {self.lon})"
def __str__(self) -> str:
# Goal: human-friendly output
lat_dir = "N" if self.lat >= 0 else "S"
lon_dir = "E" if self.lon >= 0 else "W"
return f"{abs(self.lat):.4f}°{lat_dir}, {abs(self.lon):.4f}°{lon_dir}"
moscow = Coordinate(55.7558, 37.6173)
print(str(moscow)) # 55.7558°N, 37.6173°E
print(repr(moscow)) # Coordinate(55.7558, 37.6173)
# You can recreate the object from repr (ideal)
moscow_copy = eval(repr(moscow))
print(moscow_copy) # 55.7558°N, 37.6173°E
Соглашения об именовании
class MyClass:
def __init__(self) -> None:
self.public = "доступно всем" # public attribute
self._protected = "конвенция: не трогай" # protected (convention)
self.__private = "name mangling" # private (name mangling)
obj = MyClass()
print(obj.public) # 'доступно всем'
print(obj._protected) # 'конвенция: не трогай' (still accessible!)
# print(obj.__private) # AttributeError!
print(obj._MyClass__private) # 'name mangling' (name mangling)
# Name mangling is NOT security - it's to avoid name clashes in inheritance
class Parent:
def __init__(self):
self.__value = "parent"
class Child(Parent):
def __init__(self):
super().__init__()
self.__value = "child" # doesn't override Parent's __value
c = Child()
print(c._Parent__value) # 'parent'
print(c._Child__value) # 'child'
Практический пример: TaskManager
from datetime import datetime
from enum import Enum
class Priority(Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
CRITICAL = 4
class Task:
"""Represent a task in a project."""
_next_id = 1 # class-level counter
def __init__(self, title: str, priority: Priority = Priority.MEDIUM) -> None:
self.id = Task._next_id
Task._next_id += 1
self.title = title
self.priority = priority
self.created_at = datetime.now()
self.completed = False
@classmethod
def urgent(cls, title: str) -> "Task":
"""Create an urgent task."""
return cls(title, Priority.CRITICAL)
def complete(self) -> None:
self.completed = True
def __repr__(self) -> str:
status = "done" if self.completed else "pending"
return f"Task(#{self.id}, {self.title!r}, {self.priority.name}, {status})"
def __str__(self) -> str:
icon = "x" if self.completed else " "
return f"[{icon}] #{self.id} {self.title} ({self.priority.name})"
# Usage
tasks = [
Task("Написать тесты"),
Task("Исправить баг", Priority.HIGH),
Task.urgent("Падение продакшена"),
]
tasks[0].complete()
for task in sorted(tasks, key=lambda t: t.priority.value, reverse=True):
print(task)
# [ ] #3 Падение продакшена (CRITICAL)
# [ ] #2 Исправить баг (HIGH)
# [x] #1 Написать тесты (MEDIUM)
Итоги
classопределяет шаблон для создания объектов__init__-- инициализатор (не конструктор),self-- ссылка на экземпляр- Атрибуты класса разделяются, атрибуты экземпляра уникальны
@staticmethod-- обычная функция в пространстве класса@classmethod-- получаетcls, используется для альтернативных конструкторов__repr__-- для разработчиков,__str__-- для пользователей_protected-- конвенция,__private-- name mangling- Всегда определяйте как минимум
__repr__для отладки