exec против типа при динамическом создании класса в Python - PullRequest
2 голосов
/ 05 ноября 2019

У меня есть трубопровод Луиджи. У нас много и много внешних файлов, которые меняются на регулярной основе, и мы хотим иметь возможность строить конвейер из метаданных.

Я создаю классы динамически, и нашел два способа сделать это:

Использование exec:

exec("""
class {system}(DeliverySystem):
    pass
""".format(system='ClassUsingExec'))

Использование типа:

name = 'ClassUsingType'
globals()[name] = type(name, (DeliverySystem,),{})

Оба эти варианта отлично работают в однопоточных средах, но когда я запускаю luigi с множеством рабочихребенок обрабатывает версию exec в порядке, но версия типа выдает ошибки, как описано в этом посте и этом посте (см. их для более полных трассировок стека):

PicklingError: Can't pickle <class 'abc.ClassUsingType'>: attribute lookup abc.ClassUsingType failed.

Единственная разница, которую я могу найти между ними, - это модуль:

print(ClassUsingExec.__dict__) #=>

mappingproxy({'__module__': '__main__',
              '__doc__': None,
              '__abstractmethods__': frozenset(),
              '_abc_impl': <_abc_data at 0x15b5063c120>,
              '_namespace_at_class_time': ''})


print(ClassUsingType.__dict__) #=>

mappingproxy({'__module__': 'abc',
              '__doc__': None,
              '__abstractmethods__': frozenset(),
              '_abc_impl': <_abc_data at 0x15b3f870450>,
              '_namespace_at_class_time': ''})

Кажется, что модуль отличается, и это может быть источником различий.

Использование Python3,6, Windows 10, Луиджи 2,8,9.

Вопросы:

Есть ли способ использовать type для создания класса, чтобы его модуль был модулем, в котором он определен, а не в abc?

Есть ли какая-то другая разница, которую мне не хватает между методами? Согласно этому посту не должно быть никакой разницы, но я не считаю, что это так.

1 Ответ

3 голосов
/ 05 ноября 2019

Проблема возникает из-за того, что:

  • В Windows дочерние процессы не имеют доступа к родительским переменным.
  • В качестве метаданных Luigi Tasks использует Register (и расширение ABC). class.
  • При динамическом создании класса, который имеет ABC (абстрактный базовый класс) в качестве мета-класса, модуль этого класса будет abc, а не именем модуля, в котором находится классбыл определен.

Когда работнику назначается задание, он отправляется в модуль для загрузки задания. Поскольку для модуля установлено значение abc, а не для модуля, в котором класс создается динамически, произойдет сбой.

Для того, чтобы он работал, все, что требуется, - это изменить создание класса, чтобы модифицировать модуль:

type(name, (DeliverySystem,),{})

становится

type(name, (DeliverySystem,),{'__module__':__name__})

Теперь, когда рабочий получает задание, он перейдет в нужный модуль и заново создаст класс, и все будет работать!

...