Bir python işlevini seçmenin (veya kodunu başka şekilde seri hale getirmenin) kolay bir yolu var mı?


100

Bir ağ bağlantısı üzerinden bir işlevi aktarmaya çalışıyorum (asyncore kullanarak). Böyle bir aktarım için bir python işlevini (en azından bu durumda yan etkisi olmayacak) serileştirmenin kolay bir yolu var mı?

İdeal olarak bunlara benzer bir çift işleve sahip olmak isterim:

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

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

Yanıtlar:


120

İşlev bayt kodunu serileştirebilir ve ardından arayan üzerinde yeniden yapılandırabilirsiniz. Şerifi modülü daha sonra bir fonksiyonu halinde yeniden edilebilir tefrika kod nesneler için de kullanılabilir. yani:

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

Ardından uzak işlemde (kod dizesini aktardıktan sonra):

import marshal, types

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

func(10)  # gives 100

Birkaç uyarı:

  • marshal'ın biçimi (bu konudaki herhangi bir python bayt kodu) büyük python sürümleri arasında uyumlu olmayabilir.

  • Yalnızca cpython uygulaması için çalışır.

  • İşlev, almanız gereken globallere (içe aktarılan modüller, diğer işlevler vb. Dahil) başvuruyorsa, bunları da serileştirmeniz veya uzak tarafta yeniden oluşturmanız gerekir. Örneğim ona sadece uzak sürecin genel ad alanını veriyor.

  • Kapanışlar veya jeneratör işlevleri gibi daha karmaşık durumları desteklemek için muhtemelen biraz daha fazlasını yapmanız gerekecek.


1
Python 2.5'te "yeni" modül kullanımdan kaldırılmıştır. Sanırım bir "içe aktarma türleri" nden sonra "new.function" "types.FunctionType" ile değiştirilmelidir.
Eric O Lebigot

2
Teşekkürler. Bu tam olarak aradığım şeydi. Bazı üstünkörü testlere dayanarak, jeneratörlerde olduğu gibi çalışır.
Michael Fairley

2
Mareşal modülündeki ilk birkaç paragrafı okursanız, bunun yerine turşu kullanmayı şiddetle önerdiğini görürsünüz? Turşu sayfası için de aynı. docs.python.org/2/library/marshal.html
dgorissen

1
marshalOlarak başlatılan bir sözlükler sözlüğünü serileştirmek için modülü uygulamaya çalışıyorum defaultdict(lambda : defaultdict(int)). Ancak hatayı döndürür ValueError: unmarshallable object. Not python2.7 kullanıyorum. Herhangi bir fikir? Teşekkürler
user17375

2
Python 3.5.3'te foo.func_codeyükseltir AttributeError. İşlev kodunu almanın başka bir yolu var mı?
AlQuemist

41

Python'un turşu kitaplığını, işlevler de dahil olmak üzere çok çeşitli türleri desteklemek için genişleten Dill'e göz atın :

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

Ayrıca, işlevin kapanışındaki nesnelere yapılan başvuruları da destekler:

>>> def plusTwo(x): return f(f(x))
...
>>> pickle.loads(pickle.dumps(plusTwo))(1)
3

2
dill ayrıca kaynak kodunu fonksiyonlardan ve lambdalardan almak ve bunları diske kaydetmek için oldukça iyi bir iş çıkarır, eğer bunu nesne dekapajına tercih ederseniz.
Mike McKerns

14

Bu özel proje için standart kitaplığa bağlı kalmam gerekiyor.
Michael Fairley

21
Ancak bu, nasıl yapıldığını görmek için Pyro'nun koduna bakamayacağınız anlamına gelmez :)
Aaron Digulla

4
@ AaronDigulla- doğru, ancak başka birinin yayınlanan kodunun tek bir satırını okumadan önce her zaman yazılımın lisansını kontrol etmeniz gerektiğini belirtmek gerekir. Başkasının kodunu okumak ve fikirleri kaynağa atıfta bulunmaksızın veya lisans / kopyalama kısıtlamalarına uymadan yeniden kullanmak, birçok durumda intihal ve / veya telif hakkı ihlali olarak kabul edilebilir.
mdscruggs

12

En basit yol muhtemelen inspect.getsource(object)( inceleme modülüne bakın ), bir işlev veya yöntem için kaynak kodu ile bir String döndürür.


Bu iyi görünüyor, ancak işlev adının kodda açıkça tanımlanmış olması biraz sorunlu. Kodun ilk satırını çıkarabilirim, ancak bu 'def \ / n func ():' gibi bir şey yaparak kırılabilir. İşlevin adını işlevin kendisiyle seçebilirdim, ancak adın çakışmayacağına dair hiçbir garantim yok ya da işlevi bir sarmalayıcıya koymak zorunda kalacağım, ki bu hala en temiz çözüm değil ama yapması gerekebilir.
Michael Fairley

1
İnceleme modülünün aslında sadece işlevin nerede tanımlandığını sorduğunu ve ardından bu satırları kaynak kod dosyasından okuduğunu unutmayın - pek karmaşık değildir.
çok fazla php

1
İşlevin adını, .__ ad__ özelliğini kullanarak bulabilirsiniz. ^ Def \ s * {ad} \ s * üzerinde bir normal ifade değişikliği yapabilirsiniz (ve ona istediğiniz adı verin. Kusursuz değildir, ancak çoğu şey için çalışacaktır.
çok fazla php

6

Her şey, işlevi çalışma zamanında oluşturup oluşturmamanıza bağlıdır:

Bunu yaparsanız inspect.getsource(object), nesnenin kaynağını .pydosyadan aldığı için dinamik olarak oluşturulmuş işlevler için çalışmaz , bu nedenle yalnızca yürütmeden önce tanımlanan işlevler kaynak olarak alınabilir.

Ve eğer işlevleriniz yine de dosyalara yerleştirilmişse, neden alıcıya bunlara erişim izni vermeyin ve yalnızca modül ve işlev adlarını aktarın.

Dinamik olarak oluşturulmuş işlevler için düşünebildiğim tek çözüm, işlevi iletimden önce bir dizge olarak oluşturmak, kaynağı iletmek ve sonra eval()alıcı tarafında yapmaktır.

Düzenleme: marshalÇözüm aynı zamanda oldukça akıllı görünüyor, yerleşik başka bir şeyi serileştirebileceğinizi bilmiyordum



2
code_string = '' '
def foo (x):
    dönüş x * 2
def bar (x):
    dönüş x ** 2
'' '

obj = pickle.dumps (kod_dizesi)

Şimdi

exec (pickle.loads (obj))

foo (1)
> 2
çubuğu (3)
> 9

2

Bunu yapabilirsiniz:

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

Şimdi, modül adına referans yerine transmit(fn_generator())gerçek tanımı gönderecek fn(x,y).

Ağ üzerinden sınıflar göndermek için aynı numarayı kullanabilirsiniz.


1

Bu modül için kullanılan temel işlevler sorgunuzu kapsar, ayrıca kablo üzerinden en iyi sıkıştırmayı elde edersiniz; eğitici kaynak koduna bakın:

y_serial.py module :: SQLite ile Python nesneleri depo

"Serileştirme + kalıcılık :: birkaç kod satırında, Python nesnelerini SQLite içinde sıkıştırın ve açıklama ekleyin; daha sonra bunları herhangi bir SQL olmadan anahtar sözcüklerle kronolojik olarak alın. Şemasız verileri depolamak için bir veritabanı için en kullanışlı" standart "modül."

http://yserial.sourceforge.net


1

Cloudpickle muhtemelen aradığınız şeydir. Cloudpickle şu şekilde açıklanmaktadır:

cloudpickle, Python kodunun muhtemelen verilere yakın olan uzak ana bilgisayarlarda çalıştırılmak üzere ağ üzerinden gönderildiği küme bilişim için özellikle yararlıdır.

Kullanım örneği:

def add_one(n):
  return n + 1

pickled_function = cloudpickle.dumps(add_one)
pickle.loads(pickled_function)(42)

0

Burada, seçilebilir hale getirmek için işlevleri sarmak için kullanabileceğiniz bir yardımcı sınıf var. Daha önce bahsedilen uyarılar marshalgeçerli olacaktır, ancak mümkün olduğu kadar turşu kullanmak için çaba gösterilmektedir. Serileştirmede küreselleri veya kapanışları korumak için hiçbir çaba gösterilmez.

    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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.