Вызывается неправильный метод объекта

#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 , но вы поняли идею).