← Назад к вопросам
Как возвращаются объекты из функции в Python?
1.0 Junior🔥 181 комментариев
#Python Core
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Возврат объектов из функций в Python
Возврат объектов в Python имеет свои особенности из-за того, что в Python всё является объектом и используется механизм ссылок. Важно понимать, как это работает на практике.
Основные концепции
1. Объекты и ссылки
В Python функция всегда возвращает ссылку на объект, а не копию:
class Person:
def __init__(self, name: str):
self.name = name
self.age = 0
def create_person(name: str) -> Person:
"""Возвращает ссылку на новый объект"""
person = Person(name)
return person
# Создаем два объекта через функцию
person1 = create_person('Alice')
person2 = create_person('Bob')
# Это разные объекты
assert person1 is not person2
assert id(person1) != id(person2)
# Если вернуть существующий объект
person3 = person1
assert person3 is person1 # Одна и та же ссылка
assert id(person3) == id(person1)
2. Изменяемые и неизменяемые объекты
Это влияет на поведение при возврате:
def modify_list(items: list) -> list:
"""Изменяемые объекты: изменения видны снаружи"""
items.append(4)
return items
# Исходный список изменяется
original = [1, 2, 3]
result = modify_list(original)
assert original == [1, 2, 3, 4] # Исходный список изменился!
assert result is original # Одна ссылка
def return_copy_list(items: list) -> list:
"""Правильно: возвращаем копию"""
items_copy = items.copy() # или items[:]
items_copy.append(4)
return items_copy
original = [1, 2, 3]
result = return_copy_list(original)
assert original == [1, 2, 3] # Исходный не изменился
assert result == [1, 2, 3, 4]
assert result is not original # Разные ссылки
def modify_number(num: int) -> int:
"""Неизменяемые объекты: создается новый"""
return num + 1
original = 5
result = modify_number(original)
assert original == 5 # Исходное не изменилось
assert result == 6
Способы возврата данных
1. Единственное значение
def get_user_age(user_id: int) -> int:
"""Простой возврат"""
return 25
age = get_user_age(1)
assert age == 25
2. Несколько значений (кортеж)
def divide_with_remainder(a: int, b: int) -> tuple[int, int]:
"""Возврат кортежа"""
quotient = a // b
remainder = a % b
return quotient, remainder # Автоматически создается кортеж
quot, rem = divide_with_remainder(17, 5)
assert quot == 3
assert rem == 2
# Или можно распаковать в переменные
result = divide_with_remainder(17, 5)
print(result[0]) # 3
print(result[1]) # 2
3. Именованный кортеж (NamedTuple)
from typing import NamedTuple
class DivisionResult(NamedTuple):
quotient: int
remainder: int
def divide_with_remainder(a: int, b: int) -> DivisionResult:
"""Возврат именованного кортежа"""
return DivisionResult(
quotient=a // b,
remainder=a % b,
)
result = divide_with_remainder(17, 5)
assert result.quotient == 3
assert result.remainder == 2
# Остается неизменяемым
# result.quotient = 10 # Ошибка: AttributeError
4. Dataclass
from dataclasses import dataclass
@dataclass
class User:
id: int
name: str
email: str
is_active: bool = True
def get_user(user_id: int) -> User:
"""Возврат объекта dataclass"""
# В реальности запросили бы из БД
return User(
id=user_id,
name='John Doe',
email='john@example.com',
)
user = get_user(1)
assert user.name == 'John Doe'
user.is_active = False # Можно изменять
5. Словарь (для динамических полей)
def fetch_user_data(user_id: int) -> dict:
"""Возврат словаря"""
return {
'id': user_id,
'name': 'John Doe',
'email': 'john@example.com',
'role': 'admin',
}
user_data = fetch_user_data(1)
assert user_data['name'] == 'John Doe'
# Типизация для лучшей читаемости
from typing import TypedDict
class UserDict(TypedDict):
id: int
name: str
email: str
role: str
def fetch_user_data_typed(user_id: int) -> UserDict:
return {
'id': user_id,
'name': 'John Doe',
'email': 'john@example.com',
'role': 'admin',
}
Продвинутые паттерны
1. Возврат None для отсутствия
def find_user_by_email(email: str) -> Optional[User]:
"""Может вернуть None"""
users_db = {
'alice@example.com': User(1, 'Alice'),
'bob@example.com': User(2, 'Bob'),
}
return users_db.get(email) # None если не найдено
user = find_user_by_email('unknown@example.com')
if user is None:
print('Пользователь не найден')
# Правильная типизация
from typing import Optional
def find_user(user_id: int) -> Optional[User]:
pass
# Или в Python 3.10+
from typing import Optional
def find_user(user_id: int) -> User | None:
pass
2. Возврат результата с ошибкой (Result pattern)
from dataclasses import dataclass
from typing import Generic, TypeVar, Union
T = TypeVar('T')
@dataclass
class Ok(Generic[T]):
value: T
@dataclass
class Error:
message: str
Result = Union[Ok[T], Error]
def create_user(name: str, email: str) -> Result[User]:
"""Возвращает либо User, либо Error"""
if not email or '@' not in email:
return Error('Неверный email')
user = User(id=1, name=name, email=email)
return Ok(user)
# Использование
result = create_user('John', 'john@example.com')
if isinstance(result, Ok):
print(f'Создан пользователь: {result.value.name}')
elif isinstance(result, Error):
print(f'Ошибка: {result.message}')
# Или через pattern matching в Python 3.10+
match result:
case Ok(value=user):
print(f'Успешно: {user.name}')
case Error(message=msg):
print(f'Ошибка: {msg}')
3. Возврат через контекстный менеджер
from contextlib import contextmanager
@contextmanager
def database_connection(db_url: str):
"""Возвращает управляемый ресурс"""
conn = Database(db_url)
conn.connect()
try:
yield conn # Возвращаем объект
finally:
conn.close() # Гарантированная очистка
# Использование
with database_connection('postgresql://localhost/mydb') as conn:
user = conn.query('SELECT * FROM users WHERE id = %s', (1,))
print(user)
# Соединение закроется автоматически
4. Возврат Callable
def create_multiplier(factor: int):
"""Возвращает функцию"""
def multiply(x: int) -> int:
return x * factor
return multiply
# Использование
multiply_by_3 = create_multiplier(3)
assert multiply_by_3(5) == 15
assert multiply_by_3(2) == 6
# Более типизировано
from typing import Callable
def create_multiplier(factor: int) -> Callable[[int], int]:
def multiply(x: int) -> int:
return x * factor
return multiply
5. Генератор (ленивый возврат)
def range_generator(start: int, end: int):
"""Возвращает значения по одному"""
for i in range(start, end):
yield i # Не возврат, а отправка значения
# Использование
for num in range_generator(1, 5):
print(num) # 1, 2, 3, 4
# С типизацией
from typing import Generator
def range_generator(start: int, end: int) -> Generator[int, None, None]:
for i in range(start, end):
yield i
Типовые ошибки
1. Забыли вернуть значение
def bad_function():
x = 5
# Нет return!
result = bad_function()
assert result is None # Всегда None если нет return
2. Изменение возвращаемого mutable объекта
def get_default_config() -> dict:
return {'debug': False, 'port': 8000}
config = get_default_config()
config['debug'] = True
# Если бы это был глобальный объект:
config2 = get_default_config()
assert config2['debug'] == False # Хорошо, разные объекты
# Но если функция кэширует результат:
_cached_config = None
def get_cached_config() -> dict:
global _cached_config
if _cached_config is None:
_cached_config = {'debug': False}
return _cached_config
config = get_cached_config()
config['debug'] = True # Меняем кэш!
config2 = get_cached_config()
assert config2['debug'] == True # Плохо!
Лучшие практики
- Всегда типизируй возвращаемое значение — помощь IDE и читаемость
- Возвращай новые объекты вместо изменения переданных параметров
- Используй None явно для отсутствия значения
- Используй dataclass/NamedTuple вместо словарей для структурированных данных
- Избегай глобального состояния при возврате изменяемых объектов
- Документируй если функция может вернуть None или исключение