Multiprocessamento Python PicklingError: Não é possível selecionar <type 'function'>


243

Lamento não poder reproduzir o erro com um exemplo mais simples, e meu código é muito complicado para postar. Se eu executar o programa no shell IPython em vez do Python comum, as coisas funcionam bem.

Procurei algumas notas anteriores sobre esse problema. Todos eles foram causados ​​pelo uso de pool para chamar a função definida dentro de uma função de classe. Mas esse não é o meu caso.

Exception in thread Thread-3:
Traceback (most recent call last):
  File "/usr/lib64/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib64/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib64/python2.7/multiprocessing/pool.py", line 313, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Eu apreciaria qualquer ajuda.

Atualização : A função que eu seleciono é definida no nível superior do módulo. Embora chame uma função que contenha uma função aninhada. ou seja, f()chama as g()chamadas h()que têm uma função aninhada i(), e eu estou chamando pool.apply_async(f). f(), g(), h()São todos definidos no nível superior. Eu tentei exemplo mais simples com esse padrão e funciona embora.


3
A resposta de nível superior / aceita é boa, mas pode significar que você precisa reestruturar seu código, o que pode ser doloroso. Eu recomendaria para quem tem esse problema também ler as respostas adicionais utilizando dille pathos. No entanto, eu não tive sorte com qualquer uma das soluções quando se trabalha com vtkobjects :( Alguém conseguiu executar código python em processamento paralelo vtkPolyData?
Chris

Respostas:


305

Aqui está uma lista do que pode ser conservado em conserva . Em particular, as funções só podem ser selecionadas se definidas no nível superior de um módulo.

Este trecho de código:

import multiprocessing as mp

class Foo():
    @staticmethod
    def work(self):
        pass

if __name__ == '__main__':   
    pool = mp.Pool()
    foo = Foo()
    pool.apply_async(foo.work)
    pool.close()
    pool.join()

gera um erro quase idêntico ao que você postou:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 552, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 505, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/usr/lib/python2.7/multiprocessing/pool.py", line 315, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

O problema é que pooltodos os métodos usam a mp.SimpleQueuepara passar tarefas para os processos de trabalho. Tudo o que passa pelo mp.SimpleQueuedeve ser selecionável e foo.worknão pode ser selecionado, pois não está definido no nível superior do módulo.

Pode ser corrigido definindo uma função no nível superior, que chama foo.work():

def work(foo):
    foo.work()

pool.apply_async(work,args=(foo,))

Observe que fooé selecionável, uma vez que Fooé definido no nível superior e foo.__dict__é selecionável.


2
Obrigado pela sua resposta. Eu atualizei minha pergunta. Eu não acho que essa é a causa, no entanto
Vendetta

7
Para obter um PicklingError, algo deve ser colocado na Fila que não é selecionável. Pode ser a função ou seus argumentos. Para descobrir mais sobre o problema, sugiro fazer uma cópia do seu programa e começar a reduzi-lo, tornando-o cada vez mais simples, sempre que o programa é executado novamente para verificar se o problema persiste. Quando se tornar realmente simples, você terá descoberto o problema sozinho ou terá algo que poderá postar aqui.
unutbu

3
Além disso: se você definir uma função no nível superior de um módulo, mas ela estiver decorada, a referência será a saída do decorador e você receberá esse erro de qualquer maneira.
precisa saber é o seguinte

5
Só estou atrasado em cinco anos, mas acabei de encontrar isso. Acontece que o "nível superior" deve ser considerado mais literalmente do que o habitual: parece-me que a definição da função deve preceder a inicialização do pool (ou seja, a pool = Pool()linha aqui ). Eu não esperava isso, e essa pode ser a razão pela qual o problema do OP persistiu.
Andras Deak

4
Em particular, as funções só podem ser selecionadas se definidas no nível superior de um módulo. Parece que o resultado da aplicação functool.partiala uma função de nível superior também pode ser ativado, mesmo que seja definido dentro de outra função.
user1071847

96

Eu usaria pathos.multiprocesssing, em vez de multiprocessing. pathos.multiprocessingé um garfo multiprocessingque usa dill. dillpode serializar quase tudo em python, para que você possa enviar muito mais em paralelo. O pathosfork também tem a capacidade de trabalhar diretamente com várias funções de argumento, conforme necessário para métodos de classe.

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> p = Pool(4)
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> p.map(t.plus, x, y)
[4, 6, 8, 10]
>>> 
>>> class Foo(object):
...   @staticmethod
...   def work(self, x):
...     return x+1
... 
>>> f = Foo()
>>> p.apipe(f.work, f, 100)
<processing.pool.ApplyResult object at 0x10504f8d0>
>>> res = _
>>> res.get()
101

Obtenha pathos(e se quiser dill) aqui: https://github.com/uqfoundation


5
trabalhou um tratamento. Para qualquer outra pessoa, instalei as duas bibliotecas através de: sudo pip install git+https://github.com/uqfoundation/dill.git@masteresudo pip install git+https://github.com/uqfoundation/pathos.git@master
Alexander McFarlane

5
@AlexanderMcFarlane Eu não instalaria pacotes python com sudo(de fontes externas como o github especialmente). Em vez disso, eu recomendaria executar:pip install --user git+...
Chris

Usar apenas pip install pathosnão funciona tristemente e dá a seguinte mensagem:Could not find a version that satisfies the requirement pp==1.5.7-pathos (from pathos)
xApple

11
pip install pathosagora funciona e pathosé compatível com python 3.
21716 Mike McKerns

3
@DanielGoldfarb: multiprocessé um fork de multiprocessingonde dillfoi substituído pickleem vários lugares no código ... mas essencialmente é isso. pathosfornece algumas camadas de API adicionais multiprocesse também possui back-end adicionais. Mas, essa é a essência disso.
Mike McKerns

29

Como outros já disseram, multiprocessingsó é possível transferir objetos Python para processos de trabalho que podem ser decapados. Se você não conseguir reorganizar seu código conforme descrito por unutbu, poderá usar os recursos dillestendidos de decapagem / remoção de pickp para transferir dados (especialmente dados de código), como mostramos abaixo.

Esta solução requer apenas a instalação dille nenhuma outra biblioteca como pathos:

import os
from multiprocessing import Pool

import dill


def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    return fun(*args)


def apply_async(pool, fun, args):
    payload = dill.dumps((fun, args))
    return pool.apply_async(run_dill_encoded, (payload,))


if __name__ == "__main__":

    pool = Pool(processes=5)

    # asyn execution of lambda
    jobs = []
    for i in range(10):
        job = apply_async(pool, lambda a, b: (a, b, a * b), (i, i + 1))
        jobs.append(job)

    for job in jobs:
        print job.get()
    print

    # async execution of static method

    class O(object):

        @staticmethod
        def calc():
            return os.getpid()

    jobs = []
    for i in range(10):
        job = apply_async(pool, O.calc, ())
        jobs.append(job)

    for job in jobs:
        print job.get()

6
Eu sou o autor dille pathos... e, quando você estiver certo, não é tão melhor e mais limpo e mais flexível usar também pathoscomo na minha resposta? Ou talvez eu esteja um pouco tendenciosa ...
Mike McKerns

4
Eu não estava ciente do status de pathosno momento da redação e queria apresentar uma solução muito próxima da resposta. Agora que vi sua solução, concordo que este é o caminho a percorrer.
Rocksportrocker

Eu li sua solução e fiquei tipo, Doh… I didn't even think of doing it like that. então isso foi legal.
Mike McKerns

4
Obrigado por postar, eu usei esta abordagem para Dilling / undilling argumentos que não puderam ser decapados: stackoverflow.com/questions/27883574/...
jazzblue

@rocksportrocker. Estou lendo este exemplo e não consigo entender por que há um forloop explícito . Eu normalmente veria a rotina paralela pegar uma lista e retornar uma lista sem loop.
user1700890

20

Descobri que também posso gerar exatamente essa saída de erro em um pedaço de código que funciona perfeitamente, tentando usar o criador de perfil nele.

Observe que isso foi no Windows (onde o garfo é um pouco menos elegante).

Eu estava correndo:

python -m profile -o output.pstats <script> 

E descobriu que a remoção da criação de perfil removeu o erro e a colocação do perfil o restaurou. Estava me deixando maluco também porque sabia que o código costumava funcionar. Eu estava verificando se algo havia atualizado pool.py ... então tive uma sensação de que estava afundando e eliminado a criação de perfil e foi isso.

Postando aqui para os arquivos, caso alguém o encontre.


3
WOW, obrigado por mencionar! Isso me deixou louco pela última hora, mais ou menos; Eu tentei de tudo, até um exemplo muito simples - nada parecia funcionar. Mas eu também tive o profiler executando através do meu batchfile :(
tim

1
Oh, não posso agradecer o suficiente. Isso parece tão bobo, pois é tão inesperado. Eu acho que deveria ser mencionado na documentação. Tudo o que eu tinha era uma instrução pdb de importação e uma função simples de nível superior com apenas um passnão era 'pickle'able.
0xc0de

10

Quando esse problema surge, multiprocessinguma solução simples é alternar de Poolpara ThreadPool. Isso pode ser feito sem nenhuma alteração de código além da importação

from multiprocessing.pool import ThreadPool as Pool

Isso funciona porque o ThreadPool compartilha a memória com o thread principal, em vez de criar um novo processo - isso significa que a decapagem não é necessária.

A desvantagem desse método é que o python não é a melhor linguagem para lidar com threads - ele usa algo chamado Global Interpreter Lock para manter o thread seguro, o que pode retardar alguns casos de uso aqui. No entanto, se você estiver interagindo principalmente com outros sistemas (executando comandos HTTP, conversando com um banco de dados, gravando em sistemas de arquivos), seu código provavelmente não está vinculado à CPU e não será afetado. De fato, descobri ao escrever benchmarks HTTP / HTTPS que o modelo encadeado usado aqui tem menos sobrecarga e atrasos, pois a sobrecarga da criação de novos processos é muito maior que a sobrecarga da criação de novos encadeamentos.

Portanto, se você estiver processando uma tonelada de coisas no espaço do usuário python, talvez esse não seja o melhor método.


2
Mas então você está usando apenas uma CPU (pelo menos nas versões regulares do Python que usam o GIL ), o que meio que anula o objetivo.
Endre Both

Isso realmente depende de qual é o objetivo. O bloqueio global de intérpretes significa que apenas uma instância por vez pode executar código python, mas para ações que bloqueiam fortemente (acesso ao sistema de arquivos, download de arquivos grandes ou múltiplos, execução de código externo), o GIL acaba sendo um problema. Em alguns casos, a sobrecarga da abertura de novos processos (em vez de threads) supera a sobrecarga do GIL.
Tedivm

Isso é verdade, obrigado. Ainda assim, você pode querer incluir uma ressalva na resposta. Atualmente, quando o aumento do poder de processamento ocorre principalmente na forma de núcleos de CPU mais do que poderosos, a mudança da execução multicore para núcleo único é um efeito colateral bastante significativo.
Endre Both

Bom ponto - atualizei a resposta com mais detalhes. Quero ressaltar que a mudança para o multiprocessamento encadeado não faz com que o python funcione apenas em um único núcleo.
tedivm

4

Esta solução requer apenas a instalação do endro e nenhuma outra biblioteca como pathos

def apply_packed_function_for_map((dumped_function, item, args, kwargs),):
    """
    Unpack dumped function as target function and call it with arguments.

    :param (dumped_function, item, args, kwargs):
        a tuple of dumped function and its arguments
    :return:
        result of target function
    """
    target_function = dill.loads(dumped_function)
    res = target_function(item, *args, **kwargs)
    return res


def pack_function_for_map(target_function, items, *args, **kwargs):
    """
    Pack function and arguments to object that can be sent from one
    multiprocessing.Process to another. The main problem is:
        «multiprocessing.Pool.map*» or «apply*»
        cannot use class methods or closures.
    It solves this problem with «dill».
    It works with target function as argument, dumps it («with dill»)
    and returns dumped function with arguments of target function.
    For more performance we dump only target function itself
    and don't dump its arguments.
    How to use (pseudo-code):

        ~>>> import multiprocessing
        ~>>> images = [...]
        ~>>> pool = multiprocessing.Pool(100500)
        ~>>> features = pool.map(
        ~...     *pack_function_for_map(
        ~...         super(Extractor, self).extract_features,
        ~...         images,
        ~...         type='png'
        ~...         **options,
        ~...     )
        ~... )
        ~>>>

    :param target_function:
        function, that you want to execute like  target_function(item, *args, **kwargs).
    :param items:
        list of items for map
    :param args:
        positional arguments for target_function(item, *args, **kwargs)
    :param kwargs:
        named arguments for target_function(item, *args, **kwargs)
    :return: tuple(function_wrapper, dumped_items)
        It returs a tuple with
            * function wrapper, that unpack and call target function;
            * list of packed target function and its' arguments.
    """
    dumped_function = dill.dumps(target_function)
    dumped_items = [(dumped_function, item, args, kwargs) for item in items]
    return apply_packed_function_for_map, dumped_items

Também funciona para matrizes numpy.


2
Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Este erro também ocorrerá se você tiver alguma função embutida dentro do objeto de modelo que foi passado para o trabalho assíncrono.

Portanto, verifique se os objetos de modelo passados ​​não possuem funções embutidas. (No nosso caso, estávamos usando a FieldTracker()função django-model-utils dentro do modelo para rastrear um determinado campo). Aqui está o link para a questão relevante do GitHub.


0

Com base na solução @rocksportrocker, faria sentido dill ao enviar e RECVing os resultados.

import dill
import itertools
def run_dill_encoded(payload):
    fun, args = dill.loads(payload)
    res = fun(*args)
    res = dill.dumps(res)
    return res

def dill_map_async(pool, fun, args_list,
                   as_tuple=True,
                   **kw):
    if as_tuple:
        args_list = ((x,) for x in args_list)

    it = itertools.izip(
        itertools.cycle([fun]),
        args_list)
    it = itertools.imap(dill.dumps, it)
    return pool.map_async(run_dill_encoded, it, **kw)

if __name__ == '__main__':
    import multiprocessing as mp
    import sys,os
    p = mp.Pool(4)
    res = dill_map_async(p, lambda x:[sys.stdout.write('%s\n'%os.getpid()),x][-1],
                  [lambda x:x+1]*10,)
    res = res.get(timeout=100)
    res = map(dill.loads,res)
    print(res)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.