Как перебирать прокси-сервер dict в Python?

Я использую в Python multiprocessing.Manager для совместного доступа к набору данных, который будет генерироваться одним процессом, а другие будут просматривать. Тем не менее, я сталкиваюсь с проблемой, что прокси-сервер dict возвращается manager.dict() не поддерживает iteritems().

Я мог бы повторить items(), но это означает построение нового кортежа всех элементов в dict, что является большим числом. Есть ли способ сделать это без создания промежуточного списка / кортежа, таким образом, используя только постоянное количество дополнительных память?

Примечание: это нормально, если решение требует, чтобы процесс формирования паузы для повторения.

3 ответов


вы можете повторить keys() чтобы уменьшить объем памяти. Вам придется остерегаться удаления ключей.

в противном случае, вот пример с двумя разными способами, которые позволят вам перебирать элементы в dict. The iteritems() метод в этом примере работает только из процесса, который создает объект manager и дочерний процесс, который создает объект manager. Это потому, что объект manager необходим для создания новых прокси, а другие процессы не имеют доступ к нему. The iteritems2() метод работает от других процессов, так как он не зависит от создания нового прокси-сервера в этих процессах.

import multiprocessing as mp
import multiprocessing.managers

class mydict(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.iters = {}

    def iteritems(self):
        print "iteritems", mp.current_process()
        return dict.iteritems(self)

    def _iteritems_start(self):
        print "_iteritems_start", mp.current_process()
        i = dict.iteritems(self)
        self.iters[id(i)] = i
        return id(i)

    def _iteritems_next(self, iter_id):
        try:
            return self.iters[iter_id].next()
        except StopIteration:
            del self.iters[iter_id]
            return None

class mydict_proxy(mp.managers.DictProxy):
    def iteritems(self):
        print "iteritems proxy", mp.current_process()
        return self._callmethod("iteritems")

    def iteritems2(self):
        print "iteritems2 proxy", mp.current_process()
        iter_id = self._callmethod("_iteritems_start")
        def generator():
            while True:
                a = self._callmethod("_iteritems_next", 
                             (iter_id,))
                if a == None:
                    return
                yield a
        return generator()

    _method_to_typeid_ = { "iteritems": "Iterator" }
    _exposed_ = mp.managers.DictProxy._exposed_
    _exposed_ += ("iteritems", "_iteritems_start", "_iteritems_next")

class mymanager(mp.managers.BaseManager):
    pass
mymanager.register("mydict", mydict, mydict_proxy)
mymanager.register("Iterator", proxytype = mp.managers.IteratorProxy,
           create_method = False)

def other(d):
    for k, v in d.iteritems2():
        d[k] = v.lower()
    for k, v in d.iteritems():
        d[k] = ord(v)

def main():
    manager = mymanager()
    manager.start()
    d = manager.mydict(list(enumerate("ABCDEFGHIJKLMNOP")))
    for (k, v) in d.iteritems():
        print k, v
    proc = mp.Process(target = other, args = (d,))
    proc.start()
    proc.join()
    for (k, v) in d.iteritems():
        print k, v

if __name__ == "__main__":
    main()

обратите внимание, что хотя этот код может быть более эффективным, памяти, вероятно, будет гораздо медленнее.


можно использовать syncmanager для того, чтобы класс для регистрации ваших собственных типов. Затем вы можете реализовать методы этого типа, например, для получения только ограниченного числа элементов из дикт.

вот пример, чтобы вы начали:

import multiprocessing
from multiprocessing import managers


class TakerDict(dict):
    """Like a dict, but allows taking a limited number of items."""

    def take(self, items=1):
        """Take the first `items` items."""
        return [item for _, item in zip(range(items), self.items())]


# NOTE: add other dict methods to the tuple if you need them.
TakerProxy = managers.MakeProxyType('TakerProxy', ('take',))

managers.SyncManager.register('taker', TakerDict, TakerProxy)


if __name__ == '__main__':
    manager = multiprocessing.Manager()
    taker = manager.taker()
    # in other processes, use e.g. taker.take(5)

таким образом, чтобы ограничить использование памяти, вам придется повторно вызвать процесс диспетчера, чтобы получить следующую партию элементов.

для этого, однако, ваш dict должен будет поддерживать индексирование (так что вы можете возобновить специфическое смещение). Поскольку у вас нет доступа к базовому порядку элементов в dict, вам, вероятно, лучше использовать список (например,manager.list()). Затем в ваших подпроцессах попросите len() списка и индекс срезом, чтобы получить пакет соответствующего размера - вам не нужно регистрировать какой-либо тип прокси для этого.


iteritems() на список дикт. Вы можете использовать цикл for. Или вы могли бы сказать sorted() который вернет ключи в отсортированном списке, а затем повторите этот список и сделайте dict[key]. Надеюсь, это поможет. Если есть лучший способ. Поделись со мной. Я умираю от любопытства.