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

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

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

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

Ответ 1

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

В противном случае, здесь приведен пример с двумя разными способами, которые позволят вам перебирать элементы в dict. Метод iteritems() в этом примере работает только с процессом, который создает объект-менеджер и дочерний процесс, создаваемый объектом-менеджером. Это потому, что объект-менеджер необходим для создания новых прокси-серверов, а другие процессы не имеют к нему доступа. Метод 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()

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

Ответ 2

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

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

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() списка и укажите срез для получения пакета соответствующего размера - вам не нужно регистрировать какой-либо тип прокси для этого.

Ответ 3

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