Existe uma maneira fácil de selecionar uma função Python (ou serializar seu código)?

100

Estou tentando transferir uma função através de uma conexão de rede (usando asyncore). Existe uma maneira fácil de serializar uma função Python (uma que, neste caso, pelo menos, não terá efeitos colaterais) para transferência como esta?

Idealmente, gostaria de ter um par de funções semelhantes a estas:

def transmit(func):
    obj = pickle.dumps(func)
    [send obj across the network]

def receive():
    [receive obj from the network]
    func = pickle.loads(s)
    func()
Michael Fairley
fonte

Respostas:

120

Você poderia serializar o bytecode da função e, em seguida, reconstruí-lo no chamador. O módulo marshal pode ser usado para serializar objetos de código, que podem ser remontados em uma função. ie:

import marshal
def foo(x): return x*x
code_string = marshal.dumps(foo.func_code)

Então, no processo remoto (após transferir code_string):

import marshal, types

code = marshal.loads(code_string)
func = types.FunctionType(code, globals(), "some_func_name")

func(10)  # gives 100

Algumas advertências:

  • O formato do marshal (qualquer bytecode python para esse assunto) pode não ser compatível entre as versões principais do python.

  • Só funcionará para implementação de cpython.

  • Se a função faz referência a globais (incluindo módulos importados, outras funções, etc.) que você precisa selecionar, será necessário serializá-los também ou recriá-los no lado remoto. Meu exemplo apenas fornece o namespace global do processo remoto.

  • Você provavelmente precisará fazer um pouco mais para oferecer suporte a casos mais complexos, como encerramentos ou funções de gerador.

Brian
fonte
1
No Python 2.5, o "novo" módulo está obsoleto. 'new.function' deve ser substituído por 'types.FunctionType', após um "importar tipos", eu acredito.
Eric O Lebigot
2
Obrigado. Isso é exatamente o que eu estava procurando. Com base em alguns testes superficiais, funciona como está para geradores.
Michael Fairley
2
Se você ler os primeiros parágrafos do módulo marechal, verá que ele sugere fortemente o uso de picles? O mesmo vale para a página de picles. docs.python.org/2/library/marshal.html
dgorissen
1
Estou tentando aplicar o marshalmódulo para serializar um dicionário de dicionários inicializado como defaultdict(lambda : defaultdict(int)). Mas retorna o erro ValueError: unmarshallable object. Observe que estou usando o python2.7. Qualquer ideia? Obrigado
user17375
2
No Python 3.5.3, foo.func_codeaumenta AttributeError. Existe outra maneira de obter o código da função?
AlQuemist
41

Confira Dill , que estende a biblioteca pickle do Python para oferecer suporte a uma grande variedade de tipos, incluindo funções:

>>> import dill as pickle
>>> def f(x): return x + 1
...
>>> g = pickle.dumps(f)
>>> f(1)
2
>>> pickle.loads(g)(1)
2

Ele também oferece suporte a referências a objetos no encerramento da função:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3
Josh Rosen
fonte
2
O dill também faz um bom trabalho ao obter o código-fonte das funções e lambdas e salvá-los no disco, se você preferir isso em vez de selecionar objetos.
Mike McKerns
14

O Pyro pode fazer isso por você .

RichieHindle
fonte
Eu precisaria ficar com a biblioteca padrão para este projeto específico.
Michael Fairley
21
Mas isso não significa que você não pode olhar para o código do Pyro para ver como é feito :)
Aaron Digulla
4
@ AaronDigulla- true, mas vale a pena mencionar que antes de ler uma única linha do código publicado por outra pessoa, você deve sempre verificar a licença do software. Ler o código de outra pessoa e reutilizar as ideias sem citar a fonte ou aderir às restrições de licença / cópia pode ser considerado plágio e / ou violação de direitos autorais em muitos casos.
mdscruggs de
12

A maneira mais simples é provavelmente inspect.getsource(object)(consulte o módulo inspect ) que retorna uma String com o código-fonte de uma função ou método.

Aaron Digulla
fonte
Parece bom, exceto que o nome da função é explicitamente definido no código, o que é um pouco problemático. Eu poderia retirar a primeira linha do código, mas isso é quebrável fazendo algo como 'def \ / n func ():'. Eu poderia escolher o nome da função com a função em si, mas não teria garantias de que o nome não iria colidir, ou teria que colocar a função em um invólucro, o que ainda não é a solução mais limpa, mas pode ter que servir.
Michael Fairley
1
Observe que o módulo inspect está, na verdade, apenas perguntando à função onde ela foi definida e, em seguida, lendo essas linhas do arquivo de código-fonte - dificilmente sofisticado.
muito php de
1
Você pode descobrir o nome da função usando seu atributo .__ name__. Você poderia substituir o regex em ^ def \ s * {name} \ s * (e dar a ele o nome que quiser. Não é infalível, mas funcionará para a maioria das coisas.
muito php
6

Tudo depende se você gera a função em tempo de execução ou não:

Se você fizer isso - inspect.getsource(object)não funcionará para funções geradas dinamicamente, uma vez que obtém a fonte do objeto do .pyarquivo, portanto, apenas as funções definidas antes da execução podem ser recuperadas como fonte.

E se suas funções são colocadas em arquivos de qualquer maneira, por que não dar ao receptor acesso a eles e apenas passar nomes de módulos e funções.

A única solução para funções criadas dinamicamente que posso pensar é construir a função como uma string antes da transmissão, transmitir a fonte e, em seguida eval(), no lado do receptor.

Editar: a marshalsolução também parece muito inteligente, não sabia que você pode serializar algo que não seja integrado

Kurczak
fonte
2
code_string = '' '
def foo (x):
    retornar x * 2
def bar (x):
    retornar x ** 2
'' '

obj = pickle.dumps (code_string)

Agora

exec (pickle.loads (obj))

foo (1)
> 2
bar (3)
> 9
Yanni Papadakis
fonte
2

Você consegue fazer isso:

def fn_generator():
    def fn(x, y):
        return x + y
    return fn

Agora, transmit(fn_generator())enviará a definição real de em fn(x,y)vez de uma referência ao nome do módulo.

Você pode usar o mesmo truque para enviar classes pela rede.

Fardin
fonte
1

As funções básicas usadas para este módulo abrangem sua consulta, além de obter a melhor compactação durante a transmissão; veja o código fonte instrutivo:

y_serial.py module :: warehouse objetos Python com SQLite

"Serialização + persistência :: em algumas linhas de código, comprima e anote objetos Python em SQLite; depois recupere-os cronologicamente por palavras-chave sem qualquer SQL. Módulo" padrão "mais útil para um banco de dados para armazenar dados sem esquema."

http://yserial.sourceforge.net


fonte
1

Cloudpickle é provavelmente o que você está procurando. Cloudpickle é descrito da seguinte maneira:

cloudpickle é especialmente útil para computação em cluster, onde o código Python é enviado pela rede para ser executado em hosts remotos, possivelmente perto dos dados.

Exemplo de uso:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)
Simon C
fonte
0

Aqui está uma classe auxiliar que você pode usar para agrupar funções a fim de torná-las selecionáveis. As advertências já mencionadas para marshalserão aplicadas, mas um esforço é feito para usar picles sempre que possível. Nenhum esforço é feito para preservar globais ou encerramentos na serialização.

    class PicklableFunction:
        def __init__(self, fun):
            self._fun = fun

        def __call__(self, *args, **kwargs):
            return self._fun(*args, **kwargs)

        def __getstate__(self):
            try:
                return pickle.dumps(self._fun)
            except Exception:
                return marshal.dumps((self._fun.__code__, self._fun.__name__))

        def __setstate__(self, state):
            try:
                self._fun = pickle.loads(state)
            except Exception:
                code, name = marshal.loads(state)
                self._fun = types.FunctionType(code, {}, name)
memeplex
fonte