มีวิธีง่ายๆในการดองฟังก์ชัน python (หรือทำให้รหัสเป็นลำดับ) หรือไม่?


103

ฉันกำลังพยายามถ่ายโอนฟังก์ชันผ่านการเชื่อมต่อเครือข่าย (โดยใช้ asyncore) มีวิธีง่ายๆในการทำให้เป็นอนุกรมของฟังก์ชัน python (อย่างน้อยที่สุดในกรณีนี้จะไม่มีผลข้างเคียง) สำหรับการถ่ายโอนเช่นนี้หรือไม่?

ฉันอยากจะมีฟังก์ชั่นที่คล้ายกับสิ่งเหล่านี้:

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

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

สิ่งนี้จะเจ๋งกว่าคลาสอนุกรมและคลาส API ทั้งหมดของ REST
HashRocketSyntax

คำตอบ:


121

คุณสามารถอนุกรมฟังก์ชัน bytecode แล้วสร้างใหม่บนตัวเรียก จอมพลโมดูลสามารถนำมาใช้กับวัตถุรหัสการจัดเรียงซึ่งจากนั้นจะสามารถประกอบเข้าสู่ฟังก์ชั่น กล่าวคือ:

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

จากนั้นในกระบวนการระยะไกล (หลังจากโอน code_string):

import marshal, types

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

func(10)  # gives 100

คำเตือนบางประการ:

  • รูปแบบของ marshal (bytecode ของ python สำหรับเรื่องนั้น) อาจไม่สามารถใช้ร่วมกันได้ระหว่างเวอร์ชัน python หลัก ๆ

  • จะใช้ได้กับการใช้งาน cpython เท่านั้น

  • หากฟังก์ชั่นอ้างอิงถึง globals (รวมถึงโมดูลที่นำเข้าฟังก์ชั่นอื่น ๆ และอื่น ๆ ) ที่คุณต้องการรับคุณจะต้องต่ออนุกรมกันด้วยหรือสร้างขึ้นใหม่ทางด้านระยะไกล ตัวอย่างของฉันแค่ให้เนมสเปซส่วนกลางของกระบวนการระยะไกล

  • คุณอาจต้องดำเนินการอีกเล็กน้อยเพื่อรองรับกรณีที่ซับซ้อนมากขึ้นเช่นการปิดหรือฟังก์ชันเครื่องกำเนิดไฟฟ้า


1
ใน Python 2.5 โมดูล "ใหม่" เลิกใช้งานแล้ว "new.function" ควรถูกแทนที่ด้วย "types.FunctionType" หลังจาก "import types" ฉันเชื่อว่า
Eric O Lebigot

2
ขอบคุณ. นี่คือสิ่งที่ฉันกำลังมองหา จากการทดสอบคร่าวๆบางอย่างมันใช้งานได้กับเครื่องกำเนิดไฟฟ้า
Michael Fairley

2
หากคุณอ่านสองสามย่อหน้าแรกในโมดูลจอมพลคุณเห็นว่าแนะนำอย่างยิ่งให้ใช้ผักดองแทน? เหมือนกันสำหรับหน้าดอง docs.python.org/2/library/marshal.html
dgorissen

1
ฉันกำลังพยายามใช้marshalโมดูลเพื่อจัดลำดับพจนานุกรมของพจนานุกรมที่เริ่มต้นเป็นdefaultdict(lambda : defaultdict(int)). ValueError: unmarshallable objectแต่ก็จะส่งกลับข้อผิดพลาด หมายเหตุฉันเป็น usin python2.7 ความคิดใด ๆ ? ขอบคุณ
user17375

3
บน Python 3.5.3 ยกfoo.func_code AttributeErrorมีวิธีอื่นในการรับรหัสฟังก์ชันหรือไม่?
AlQuemist

41

ลองดูDillซึ่งขยายไลบรารีดองของ Python เพื่อรองรับประเภทต่างๆที่มากขึ้นรวมถึงฟังก์ชัน:

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

นอกจากนี้ยังสนับสนุนการอ้างอิงถึงวัตถุในการปิดฟังก์ชัน:

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

2
นอกจากนี้ผักชีลาวยังทำงานได้ดีในการรับซอร์สโค้ดจากฟังก์ชันและแลมบ์ดาและบันทึกลงในดิสก์หากคุณต้องการมากกว่าการดองวัตถุ
Mike McKerns

14

ฉันต้องใช้ไลบรารีมาตรฐานสำหรับโครงการนี้โดยเฉพาะ
Michael Fairley

21
แต่นั่นไม่ได้หมายความว่าคุณไม่สามารถดูรหัสของ Pyro เพื่อดูว่ามันเป็นอย่างไร :)
Aaron Digulla

4
@ AaronDigulla- จริง แต่ควรค่าแก่การกล่าวถึงว่าก่อนที่จะอ่านโค้ดที่เผยแพร่ของผู้อื่นคุณควรตรวจสอบใบอนุญาตซอฟต์แวร์ทุกครั้ง การอ่านรหัสของผู้อื่นและนำแนวคิดไปใช้ซ้ำโดยไม่อ้างแหล่งที่มาหรือปฏิบัติตามข้อ จำกัด ด้านใบอนุญาต / การคัดลอกอาจถือได้ว่าเป็นการลอกเลียนแบบและ / หรือละเมิดลิขสิทธิ์ในหลาย ๆ กรณี
mdscruggs

12

วิธีที่ง่ายที่สุดน่าจะเป็นinspect.getsource(object)(ดูโมดูลการตรวจสอบ ) ซึ่งส่งคืนสตริงที่มีซอร์สโค้ดสำหรับฟังก์ชันหรือวิธีการ


สิ่งนี้ดูดียกเว้นว่ามีการกำหนดชื่อฟังก์ชันไว้อย่างชัดเจนในโค้ดซึ่งเป็นปัญหาเล็กน้อย ฉันสามารถตัดบรรทัดแรกของโค้ดออกได้ แต่ก็สามารถแตกหักได้โดยทำบางสิ่งเช่น 'def \ / n func ():' ฉันสามารถเลือกชื่อของฟังก์ชันด้วยฟังก์ชันได้ แต่ฉันไม่สามารถรับประกันได้ว่าชื่อจะไม่ชนกันหรือฉันต้องใส่ฟังก์ชันลงในกระดาษห่อหุ้มซึ่งยังไม่ใช่วิธีที่สะอาดที่สุด แต่ มันอาจจะต้องทำ
Michael Fairley

1
โปรดทราบว่าโมดูลการตรวจสอบเป็นเพียงการถามฟังก์ชันที่กำหนดไว้จากนั้นอ่านบรรทัดเหล่านั้นจากไฟล์ซอร์สโค้ดซึ่งแทบจะไม่ซับซ้อน
php มากเกินไป

1
คุณสามารถค้นหาชื่อของฟังก์ชันได้โดยใช้แอตทริบิวต์. __ name__ คุณสามารถแทนที่ regex บน ^ def \ s * {name} \ s * (และตั้งชื่ออะไรก็ได้ที่คุณต้องการมันไม่สามารถเข้าใจผิดได้ แต่จะใช้ได้กับทุกสิ่ง
php มากเกินไป

6

ทุกอย่างขึ้นอยู่กับว่าคุณสร้างฟังก์ชันที่รันไทม์หรือไม่:

ถ้าคุณทำ - inspect.getsource(object)จะไม่ทำงานสำหรับฟังก์ชันที่สร้างขึ้นแบบไดนามิกเนื่องจากได้รับซอร์สของอ็อบเจ็กต์จาก.pyไฟล์ดังนั้นเฉพาะฟังก์ชันที่กำหนดไว้ก่อนการเรียกใช้งานเท่านั้นที่สามารถเรียกใช้เป็นซอร์สได้

และหากฟังก์ชั่นของคุณถูกวางไว้ในไฟล์แล้วทำไมไม่ให้ผู้รับสามารถเข้าถึงได้และส่งเฉพาะชื่อโมดูลและฟังก์ชันเท่านั้น

ทางออกเดียวสำหรับฟังก์ชันที่สร้างขึ้นแบบไดนามิกที่ฉันคิดได้คือการสร้างฟังก์ชันเป็นสตริงก่อนการส่งส่งแหล่งที่มาและจากนั้น eval()ไปที่ด้านผู้รับ

แก้ไข: marshalโซลูชันนั้นดูฉลาดมากเช่นกันไม่รู้ว่าคุณสามารถต่ออนุกรมสิ่งอื่น ๆ ที่มีอยู่ในตัวได้


5

cloudแพคเกจ (PIP ติดตั้งระบบคลาวด์) สามารถดองรหัสที่กำหนดรวมทั้งการอ้างอิง ดูhttps://stackoverflow.com/a/16891169/1264797



2

คุณสามารถทำได้:

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

ตอนนี้transmit(fn_generator())จะส่งคำจำกัดความที่แท้จริงfn(x,y)แทนการอ้างอิงถึงชื่อโมดูล

คุณสามารถใช้เคล็ดลับเดียวกันในการส่งชั้นเรียนข้ามเครือข่าย


1

ฟังก์ชันพื้นฐานที่ใช้สำหรับโมดูลนี้ครอบคลุมข้อความค้นหาของคุณและคุณจะได้รับการบีบอัดที่ดีที่สุดผ่านสายไฟ ดูซอร์สโค้ดคำแนะนำ:

โมดูล y_serial.py :: ออบเจ็กต์ Python ของคลังสินค้าพร้อม SQLite

"Serialization + persistance :: ในโค้ดไม่กี่บรรทัดบีบอัดและใส่คำอธิบายประกอบออบเจ็กต์ Python ลงใน SQLite จากนั้นดึงข้อมูลตามลำดับเวลาด้วยคีย์เวิร์ดโดยไม่มี SQL โมดูล" มาตรฐาน "ที่มีประโยชน์ที่สุดสำหรับฐานข้อมูลเพื่อจัดเก็บข้อมูลแบบไม่ใช้สคีมา"

http://yserial.sourceforge.net


1

Cloudpickleน่าจะเป็นสิ่งที่คุณกำลังมองหา Cloudpickle อธิบายไว้ดังนี้:

cloudpickle มีประโยชน์อย่างยิ่งสำหรับการประมวลผลแบบคลัสเตอร์ซึ่งโค้ด Python ถูกส่งผ่านเครือข่ายเพื่อดำเนินการบนโฮสต์ระยะไกลซึ่งอาจอยู่ใกล้กับข้อมูล

ตัวอย่างการใช้งาน:

def add_one(n):
  return n + 1

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

0

นี่คือคลาสตัวช่วยที่คุณสามารถใช้เพื่อตัดฟังก์ชั่นต่างๆเพื่อให้สามารถเลือกได้ คำเตือนที่กล่าวถึงแล้วmarshalจะมีผลบังคับใช้ แต่จะพยายามใช้ของดองทุกครั้งที่ทำได้ ไม่มีความพยายามใด ๆ ในการเก็บรักษา globals หรือการปิดในการทำให้เป็นอนุกรม

    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)

0

ใน Python สมัยใหม่คุณสามารถใช้ฟังก์ชันดองและตัวแปรมากมาย พิจารณาสิ่งนี้

import pickle, time
def foobar(a,b):
    print("%r %r"%(a,b))

คุณสามารถดองได้

p = pickle.dumps(foobar)
q = pickle.loads(p)
q(2,3)

คุณสามารถปิดดอง

import functools
foobar_closed = functools.partial(foobar,'locked')
p = pickle.dumps(foobar_closed)
q = pickle.loads(p)
q(2)

แม้ว่าการปิดจะใช้ตัวแปรภายใน

def closer():
    z = time.time()
    return functools.partial(foobar,z)
p = pickle.dumps(closer())
q = pickle.loads(p)
q(2)

แต่ถ้าคุณปิดโดยใช้ฟังก์ชันภายในจะล้มเหลว

def builder():
    z = 'internal'
    def mypartial(b):
        return foobar(z,b)
    return mypartial
p = pickle.dumps(builder())
q = pickle.loads(p)
q(2)

มีข้อผิดพลาด

pickle.PicklingError: ไม่สามารถดอง <function mypartial ที่ 0x7f3b6c885a50>: ไม่พบเป็น __ main __. mypartial

ทดสอบกับ Python 2.7 และ 3.6

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.