0

I've this simple python code:

TOPICS = ['dacha/1', 'dacha/2']

def publish(topic, payload):
  print(f"Publish -> Topic: {topic}, Payload: {payload}")  

publishers = []
for topic in TOPICS:
  print(topic)
  pub = lambda payload: publish(topic, payload)
  publishers.append(pub)

pub1, pub2 = publishers

pub1("HI!")
pub2("HI!")

and my output:

dacha/1
dacha/2

Publish -> Topic: dacha/2, Payload: HI!
Publish -> Topic: dacha/2, Payload: HI!

Why are topics the same, when i'm passing different of them while creating? Maybe I'm using lambda uncorrect, or just they are not supported by this way? Or it`s just a python bug, my python is: 3.9.3

The same result:

pub1, pub2 = [
  (lambda payload: publish(topic, payload))
  for topic in TOPICS
]
Atilova
  • 21
  • 1
  • 5
  • Yeah, that's not a bug, don't worry. Can't help as to finding more info, try searching SO a bit more, there are many questions surrounding this topic. – Captain Trojan Jun 05 '21 at 20:31
  • The problem is that when calling pub(), it calls it with the last value of 'topic' and not the value that it had when the pub was created. lambda functions do not stock variables in memory – S-c-r-a-t-c-h-y Jun 05 '21 at 20:35
  • Yeah, thanks a lot, maybe do you know another way to do this? – Atilova Jun 05 '21 at 20:38

2 Answers2

1

Okay, let's try to figure out what exactly the issue is, try to delete local variable topic after the loop,

def publish(topic, payload):
  print(f"Publish -> Topic: {topic}, Payload: {payload}")  

publishers = []
for topic in TOPICS:
  print(topic)
  pub = lambda payload: publish(topic, payload)
  publishers.append(pub)
del topic     ########## <--------------------------------- This one
pub1, pub2 = publishers
pub1("HI!")
pub2("HI!")

If you do this, Python will throw error later when trying to evaluate pub1:

Traceback (most recent call last):
  File "<input>", line 14, in <module>
  File "<input>", line 9, in <lambda>
NameError: name 'topic' is not defined

So, it means that Python is looking for the local variable reference for topic, and that is why your output has only the second topic always, because by the time loop exits, topic is always going to have the last item in the list.

Further Debugging:

Try chaning your publish function, and return the id of the variable topic in the formatted string:

def publish(topic, payload):
  print(f"Publish -> Topic: {id(topic)}, Payload: {payload}") 

The output will be as follows:

dacha/1
dacha/2
Publish -> Topic: 2574286013360, Payload: HI!
Publish -> Topic: 2574286013360, Payload: HI!

As you can see, both the topic has same id, it's undoubtedly True that both refer to the same value.

So, how to resolve?

You can, instead of using local variable, use the variable within the scope of lambda function. You can change your lambda function to:

lambda payload, topic=topic: publish(topic, payload)

What it will do is, it will keep another variable named topic with in the scope of lambda itself, that being said, it will store it's own copy of the value for topic, and the result remains intact despite the action that changes the value of local variable topic later.

OUTPUT:

dacha/1
dacha/2
Publish -> Topic: dacha/1, Payload: HI!
Publish -> Topic: dacha/2, Payload: HI!
ThePyGuy
  • 17,779
  • 5
  • 18
  • 45
0

Hurray, I've found out a working solution, just change this line: pub = lambda payload: publish(topic, payload) to this pub = (lambda payload, topic=topic: publish(topic, payload)) Result:

TOPICS = ['dacha/1', 'dacha/2']

def publish(topic, payload):
  print(f"Publish -> Topic: {topic}, Payload: {payload}")  

publishers = []
for topic in TOPICS:
  print(topic)
  pub = (lambda payload, topic=topic: publish(topic, payload))
  publishers.append(pub)

pub1, pub2 = publishers

pub1("HI!")
pub2("HI!")

Output:

dacha/1
dacha/2
Publish -> Topic: dacha/1, Payload: HI!
Publish -> Topic: dacha/2, Payload: HI!

This helped me: Python lambda's binding to local values

Atilova
  • 21
  • 1
  • 5