I have a python file which contains a abstract class and several sub-classes. The code looks like the following
# test.py
import threading
from abc import ABC, abstractmethod
class Job(object):
def __init__(self, job_name):
self.job_name = job_name
def start(self):
self.run()
self.clean_up()
@abstractmethod
def run(self):
pass
@abstractmethod
def clean_up(self):
pass
class JobA(Job):
def __init__(self):
super().__init__('job_a')
print('JobA constructed')
def run(self):
print('%s: run for %s' % (self.__str__(), self.job_name))
def clean_up(self):
print('%s: clean up for %s' % (self.__str__(), self.job_name))
class JobB(Job):
def __init__(self):
super().__init__('job_b')
print('JobB constructed')
def run(self):
print('%s: run for %s' % (self.__str__(), self.job_name))
def clean_up(self):
print('%s: clean up for %s' % (self.__str__(), self.job_name))
if __name__ == '__main__':
jobs = [JobA(), JobB()]
threads = [threading.Thread(name=job.__str__(), target=lambda: job.start()) for job in jobs]
print(threads)
for t in threads:
print('starting thread %s' % t.name)
t.start()
The output of this code is
JobA constructed
JobB constructed
[<Thread(<__main__.JobA object at 0x1008a8f70>, initial)>, <Thread(<__main__.JobB object at 0x1008a8d90>, initial)>]
starting thread <__main__.JobA object at 0x1008a8f70>
<__main__.JobB object at 0x1008a8d90>: run for job_b
<__main__.JobB object at 0x1008a8d90>: clean up for job_b
starting thread <__main__.JobB object at 0x1008a8d90>
<__main__.JobB object at 0x1008a8d90>: run for job_b
<__main__.JobB object at 0x1008a8d90>: clean up for job_b
Although two threads are constructed for objects of both JobA and JobB, start() is called on object of JobB only.
I expect that start() should be called on different objects in the array. Are there any mistakes in this code?
Thanks.
My python version is Python 3.9.7 (default, Oct 12 2021, 22:38:23) [Clang 13.0.0 (clang-1300.0.29.3)] on darwin
CodePudding user response:
The lambda
definition refers to the loop variable job
of the list comprehension (i.e. it defines a closure). When the lambda is called, it will resolve that name job
which now refers to the last element of the comprehension (i.e. Job B).
Instead you could use a default value, in order to bind each object of the comprehension correctly to the respective lambda
function:
threads = [threading.Thread(name=job.__str__(), target=lambda j=job: j.start()) for job in jobs]
Consider the following example code for a better visualization:
>>> funcs = [lambda: print(x) for x in range(3)]
>>> for f in funcs:
... f()
...
2
2
2
>>> funcs = [lambda y=x: print(y) for x in range(3)]
>>> for f in funcs:
... f()
...
0
1
2