Telegram Group & Telegram Channel
🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru



tg-me.com/Python_Community_ru/2597
Create:
Last Update:

🐍 Задача с подвохом: Декораторы и изменяемые объекты

Условие:

Что выведет следующий код и почему?

def memoize(fn):
cache = {}
def wrapper(arg):
if arg in cache:
print("Из кэша")
return cache[arg]
else:
result = fn(arg)
cache[arg] = result
return result
return wrapper

@memoize
def add_to_list(val, lst=[]):
lst.append(val)
return lst

res1 = add_to_list(1)
res2 = add_to_list(2)
res3 = add_to_list(1)

print(res1)
print(res2)
print(res3)

Вопрос:
Что именно выведется? В чём здесь двойная ловушка?

🔍 Анализ:

Сначала кажется, что:

1. add_to_list(1) вернёт [1].
2. add_to_list(2) вернёт [2].
3. add_to_list(1) либо вызовет функцию снова, либо вернёт результат из кэша.

Но есть два подвоха:

Подвох №1: изменяемый аргумент по умолчанию

Аргумент lst=[] создаётся один раз при определении функции. Все вызовы без передачи списка будут использовать один и тот же список.

Подвох №2: кэширование по ключу

Декоратор memoize сохраняет результат в кэше по ключу arg. Но функция возвращает список, который изменяется при каждом вызове. Даже если результат берётся из кэша, вы получите ссылку на тот же список, который менялся между вызовами!

🧮 Что реально произойдёт:

- `res1 = add_to_list(1)` → функция вызвана, список становится `[1]`.
- `res2 = add_to_list(2)` → функция вызвана снова с другим аргументом, список теперь `[1, 2]`.
- `res3 = add_to_list(1)` → аргумент `1` есть в кэше, сработает ветка `print("Из кэша")`, и вернётся ссылка на тот же изменённый список.

🔢 Итог:

```
[1, 2]
[1, 2]
Из кэша
[1, 2]
```

Все переменные указывают на один и тот же изменённый список.

💥 Почему это важно:

1️⃣ Изменяемые аргументы по умолчанию сохраняются между вызовами функции.
2️⃣ Кэширование изменяемых объектов может привести к неожиданным результатам: возвращается не неизменяемый результат, а ссылка на объект, который может изменяться позже.

🛡️ Как исправить:

1️⃣ Использовать `lst=None` и создавать новый список внутри функции:
```python
def add_to_list(val, lst=None):
if lst is None:
lst = []
lst.append(val)
return lst
```

2️⃣ Если кэшировать изменяемые объекты, лучше возвращать их копии:
```python
import copy
cache[arg] = copy.deepcopy(result)
```

Итог:

Декораторы вместе с изменяемыми аргументами — это ловушка даже для опытных программистов. Особенно, если изменяемые объекты кэшируются и потом меняются за кулисами.

@Python_Community_ru

BY Python Community


Warning: Undefined variable $i in /var/www/tg-me/post.php on line 283

Share with your friend now:
tg-me.com/Python_Community_ru/2597

View MORE
Open in Telegram


Python Community Telegram | DID YOU KNOW?

Date: |

Telegram auto-delete message, expiring invites, and more

elegram is updating its messaging app with options for auto-deleting messages, expiring invite links, and new unlimited groups, the company shared in a blog post. Much like Signal, Telegram received a burst of new users in the confusion over WhatsApp’s privacy policy and now the company is adopting features that were already part of its competitors’ apps, features which offer more security and privacy. Auto-deleting messages were already possible in Telegram’s encrypted Secret Chats, but this new update for iOS and Android adds the option to make messages disappear in any kind of chat. Auto-delete can be enabled inside of chats, and set to delete either 24 hours or seven days after messages are sent. Auto-delete won’t remove every message though; if a message was sent before the feature was turned on, it’ll stick around. Telegram’s competitors have had similar features: WhatsApp introduced a feature in 2020 and Signal has had disappearing messages since at least 2016.

The global forecast for the Asian markets is murky following recent volatility, with crude oil prices providing support in what has been an otherwise tough month. The European markets were down and the U.S. bourses were mixed and flat and the Asian markets figure to split the difference.The TSE finished modestly lower on Friday following losses from the financial shares and property stocks.For the day, the index sank 15.09 points or 0.49 percent to finish at 3,061.35 after trading between 3,057.84 and 3,089.78. Volume was 1.39 billion shares worth 1.30 billion Singapore dollars. There were 285 decliners and 184 gainers.

Python Community from sa


Telegram Python Community
FROM USA