Найдите и объясните ошибку в Python-функции, предназначенной для подсчёта среднего: def mean(values=[]): return sum(values)/len(values) — опишите тесты, которые покажут проблему, и перепишите функцию правильно, обсудив побочные эффекты и контракт функции

30 Сен в 17:27
10 +1
0
Ответы
1

Код
def meanvalues=[]values=[]values=[]:
return sumvaluesvaluesvalues/lenvaluesvaluesvalues содержит как минимум две проблемные вещи.

1) Пустой аргумент приводит к делению на ноль
Если вызвать mean или mean[][][], то lenvaluesvaluesvalues == 0 и вы получите ZeroDivisionError. Это плохая и неинформативная ошибка для клиента функции — лучше явно проверять и выбрасывать ValueError с объяснением.

2) Использование изменяемого значения по умолчанию values=[]values=[]values=[] Хотя текущая реализация сама по себе не модифицирует список, использование как значения по умолчанию опасно: одно и то же объект-список будет разделяться между вызовами функции и может привести к неожиданному поведению, если кто-то его модифицирует. Пример классической ошибки иллюстрацияпроблемыmutabledefaultиллюстрация проблемы mutable defaultиллюстрацияпроблемыmutabledefault:

def append_and_returnx,lst=[]x, lst=[]x,lst=[]:
lst.appendxxx return lst

append_and_return111 -> 111 append_and_return222 -> 1,21, 21,2 # тот же список, накопление состояния между вызовами

Даже если в mean вы сейчас ничего не меняете, такой дефолт делает API хрупким и вводит неожиданность.

Тесты, показывающие проблему
встилеpytestв стиле pytestвстилеpytest

1) Деление на ноль при пустом входе
def test_mean_empty_list_raises_zero_division:
with pytest.raisesZeroDivisionErrorZeroDivisionErrorZeroDivisionError:
mean[][][]

def test_mean_no_args_raises_zero_division:
with pytest.raisesZeroDivisionErrorZeroDivisionErrorZeroDivisionError:
mean

2) Иллюстрация опасности mutable default пояснительныйпримерпояснительный примерпояснительныйпример def test_mutable_default_is_shared:

если реализация бы модифицировала default, это бы проявилось# (пример демонстрационный, для функции mean это не выполняется,
# но показывает риск такого подхода)
pass

Правильная реализация и контракт
Рекомендации по контракту функции:

Принимает итерируемый объект чисел list,tuple,generatorит.п.list, tuple, generator и т.п.list,tuple,generatorит.п..Требует хотя бы одного числа — при пустом входе должна возникать осмысленная ошибка ValueErrorилиспециальноеисключениеValueError или специальное исключениеValueErrorилиспециальноеисключение.Не должна модифицировать переданный объект.Не использовать изменяемые значения по умолчанию.

Два варианта реализации.

A) Простой, понятный конвертируетитераторвсписок,удобендлятестовконвертирует итератор в список, удобен для тестовконвертируетитераторвсписок,удобендлятестов:
def meanvaluesvaluesvalues:
values = listvaluesvaluesvalues # поддержка любых итерируемых
if not values:
raise ValueError("mean() requires at least one value")
return sumvaluesvaluesvalues / lenvaluesvaluesvalues

Плюсы: простая логика; минусы: требует хранения всех элементов в памяти копируетгенераторыкопирует генераторыкопируетгенераторы.

B) Использование single-pass, без копирования всех элементов работаетспотоком/генераторомработает с потоком/генераторомработаетспотоком/генератором:
def meanvaluesvaluesvalues:
it = itervaluesvaluesvalues try:
first = nextititit except StopIteration:
raise ValueError("mean() requires at least one value")
total = first
count = 1
for x in it:
total += x
count += 1
return total / count

Плюсы: не копирует весь вход в память; работает и для ленивых итераторов. Минусы: немного сложнее.

Замечания

Подчеркну: не следует писать values= в сигнатуре. Лучше явно требовать аргумент: def meanvaluesvaluesvalues: ... — это ясно и безопасно.Если хочется синтаксиса mean1,2,31,2,31,2,3, можно сделать def mean∗values*valuesvalues: и в этом случае нужно проверить if not values: raise ValueError.Встроенный модуль statistics предоставляет statistics.meandatadatadata и делает грамотную проверку пустого входа — можно использовать его.

Итого: основной баг — использование mutable default и отсутствие проверки на пустоту делениенанольделение на нольделениенаноль. Исправление: удалить значение по умолчанию, явно проверять пустой ввод и документировать контракт итерабельность,отсутствиемодификации,поведениенапустомвводеитерабельность, отсутствие модификации, поведение на пустом вводеитерабельность,отсутствиемодификации,поведениенапустомвводе.

30 Сен в 18:44
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир