← Назад к вопросам

Как возвращаются объекты из функции в 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  # Плохо!

Лучшие практики

  1. Всегда типизируй возвращаемое значение — помощь IDE и читаемость
  2. Возвращай новые объекты вместо изменения переданных параметров
  3. Используй None явно для отсутствия значения
  4. Используй dataclass/NamedTuple вместо словарей для структурированных данных
  5. Избегай глобального состояния при возврате изменяемых объектов
  6. Документируй если функция может вернуть None или исключение