#python #inheritance #abstract-class #python-3.9
Вопрос:
У меня есть файл python, который содержит абстрактный класс и несколько подклассов. Код выглядит следующим образом
# 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()
Вывод этого кода является
JobA constructed JobB constructed [lt;Thread(lt;__main__.JobA object at 0x1008a8f70gt;, initial)gt;, lt;Thread(lt;__main__.JobB object at 0x1008a8d90gt;, initial)gt;] starting thread lt;__main__.JobA object at 0x1008a8f70gt; lt;__main__.JobB object at 0x1008a8d90gt;: run for job_b lt;__main__.JobB object at 0x1008a8d90gt;: clean up for job_b starting thread lt;__main__.JobB object at 0x1008a8d90gt; lt;__main__.JobB object at 0x1008a8d90gt;: run for job_b lt;__main__.JobB object at 0x1008a8d90gt;: clean up for job_b
Хотя для объектов JobA и JobB создаются два потока, функция start() вызывается только для объекта JobB.
Я ожидаю, что start() следует вызывать для разных объектов в массиве. Есть ли какие-либо ошибки в этом коде?
Спасибо.
Моя версия python-Python 3.9.7 (по умолчанию, октябрь 12 2021, 22:38:23) [Лязг 13.0.0 (лязг-1300.0.29.3)] на Дарвине
Ответ №1:
lambda
Определение относится к переменной цикла job
понимания списка (т. е. определяет замыкание). Когда вызывается лямбда, она разрешает то имя job
, которое теперь относится к последнему элементу понимания (т. Е. к заданию B).
Вместо этого вы можете использовать значение по умолчанию, чтобы правильно привязать каждый объект понимания к соответствующей lambda
функции:
threads = [threading.Thread(name=job.__str__(), target=lambda j=job: j.start()) for job in jobs]
Рассмотрим следующий пример кода для лучшей визуализации:
gt;gt;gt; funcs = [lambda: print(x) for x in range(3)] gt;gt;gt; for f in funcs: ... f() ... 2 2 2 gt;gt;gt; funcs = [lambda y=x: print(y) for x in range(3)] gt;gt;gt; for f in funcs: ... f() ... 0 1 2
Комментарии:
1. Большое спасибо. Решение работает идеально!
2. вы также можете просто указать
target=job.start
— лямбда избыточна, если она просто вызывает другую функцию3. @Phydeaux Конечно, я хотел предложить более общее решение, которое также применимо к другим случаям , например
[lambda: func(x) for x in data]
(которое также может быть решеноfunctools.partial
, но вы поняли идею).