Если вы хотите использовать разветвление, я не вижу пути использования глобального.Но я также не вижу причины, по которой вам пришлось бы чувствовать себя плохо из-за использования глобального в этом случае, вы не управляете глобальным списком с помощью многопоточности или около того.
С этим можно справитьсяхотя уродство в вашем примере.Вы хотите передать classifier.classify
напрямую, но объект Classifier
содержит объекты, которые невозможно протравить.
import os
import csv
import uuid
from threading import Lock
from multiprocessing import Pool
from weakref import WeakValueDictionary
class Classifier:
def __init__(self, spec):
self.lock = Lock() # unpickleable
self.spec = spec
def classify(self, row):
return f'classified by pid: {os.getpid()} with spec: {self.spec}', row
Я предлагаю подкласс Classifier
и определить __getstate__
и __setstate__
для включения травления.Так как вы в любом случае используете разветвление, все, что нужно, это информация о том, как получить ссылку на разветвленный глобальный экземпляр.Затем мы просто обновим __dict__
протравленного объекта на __dict__
разветвленного экземпляра (который не подвергся уменьшению травления), и ваш экземпляр снова будет завершен.
Для этого безДополнительный шаблонный экземпляр Classifier
должен создать для себя имя и зарегистрировать его как глобальную переменную.Эта первая ссылка будет слабой ссылкой, поэтому экземпляр можно будет собирать, когда пользователь этого ожидает.Вторая ссылка создается пользователем, когда он назначает classifier = Classifier(classifier_spec)
.Этот не обязательно должен быть глобальным.
Сгенерированное имя в примере ниже генерируется с помощью модуля uuid
standard-lib.Uuid преобразуется в строку и редактируется в допустимый идентификатор (это не должно быть, но это удобно для отладки в интерактивном режиме).
class SubClassifier(Classifier):
def __init__(self, spec):
super().__init__(spec)
self.uuid = self._generate_uuid_string()
self.pid = os.getpid()
self._register_global()
def __getstate__(self):
"""Define pickled content."""
return {'uuid': self.uuid}
def __setstate__(self, state):
"""Set state in child process."""
self.__dict__ = state
self.__dict__.update(self._get_instance().__dict__)
def _get_instance(self):
"""Get reference to instance."""
return globals()[self.uuid][self.uuid]
@staticmethod
def _generate_uuid_string():
"""Generate id as valid identifier."""
# return 'uuid_' + '123' # for testing
return 'uuid_' + str(uuid.uuid4()).replace('-', '_')
def _register_global(self):
"""Register global reference to instance."""
weakd = WeakValueDictionary({self.uuid: self})
globals().update({self.uuid: weakd})
def __del__(self):
"""Clean up globals when deleted in parent."""
if os.getpid() == self.pid:
globals().pop(self.uuid)
Самое приятное здесь то, что шаблонполностью ушелВам не нужно связываться вручную с объявлением и удалением глобальных переменных, поскольку экземпляр управляет всем сам в фоновом режиме:
def classify(classifier_spec, data_file, n_workers):
classifier = SubClassifier(classifier_spec)
# assert globals()['uuid_123']['uuid_123'] # for testing
with open(data_file, "rt") as fh, Pool(n_workers) as pool:
rd = csv.DictReader(fh)
yield from pool.imap_unordered(classifier.classify, rd)
if __name__ == '__main__':
PATHFILE = 'data.csv'
N_WORKERS = 4
g = classify(classifier_spec='spec1', data_file=PATHFILE, n_workers=N_WORKERS)
for record in g:
print(record)
# assert 'uuid_123' not in globals() # no reference left