การห่อไลบรารี C ใน Python: C, Cython หรือ ctypes?


284

ฉันต้องการเรียกไลบรารี C จากแอปพลิเคชัน Python ฉันไม่ต้องการห่อทั้ง API เฉพาะฟังก์ชันและประเภทข้อมูลที่เกี่ยวข้องกับกรณีของฉัน ตามที่เห็นฉันมีสามตัวเลือก:

  1. สร้างโมดูลส่วนขยายจริงใน C. อาจเป็นไปได้มากเกินไปและฉันยังต้องการหลีกเลี่ยงค่าใช้จ่ายในการเรียนรู้การเขียนส่วนขยาย
  2. ใช้Cythonเพื่อแสดงส่วนที่เกี่ยวข้องจาก C library ไปยัง Python
  3. ทำสิ่งทั้งหมดใน Python ctypesเพื่อใช้สื่อสารกับไลบรารีภายนอก

ฉันไม่แน่ใจว่า 2) หรือ 3) เป็นตัวเลือกที่ดีกว่าหรือไม่ ข้อได้เปรียบของ 3) คือctypesส่วนหนึ่งของไลบรารีมาตรฐานและโค้ดผลลัพธ์จะเป็น Python แท้ๆ - ถึงแม้ว่าฉันไม่แน่ใจว่าประโยชน์นั้นใหญ่เพียงใด

มีข้อดี / ข้อเสียมากกว่าทั้งสองทางเลือกหรือไม่? คุณแนะนำวิธีใด


แก้ไข:ขอบคุณสำหรับคำตอบทั้งหมดของคุณพวกเขาให้ทรัพยากรที่ดีสำหรับทุกคนที่ต้องการทำสิ่งที่คล้ายกัน แน่นอนว่าการตัดสินใจยังคงต้องทำในกรณีเดียว - ไม่มีใครตอบ "นี่คือสิ่งที่ถูกต้อง" สำหรับกรณีของฉันเองฉันอาจจะไปกับ ctypes แต่ฉันก็ตั้งตารอที่จะลอง Cython ในโครงการอื่น

เมื่อไม่มีคำตอบที่แท้จริงเพียงคำเดียวการยอมรับคำตอบก็ค่อนข้างเป็นการสุ่ม ฉันเลือกคำตอบของ FogleBird เพราะให้ข้อมูลเชิงลึกที่ดีเกี่ยวกับ ctypes และในปัจจุบันยังเป็นคำตอบที่ได้รับการโหวตสูงสุด อย่างไรก็ตามฉันขอแนะนำให้อ่านคำตอบทั้งหมดเพื่อให้ได้ภาพรวมที่ดี

ขอบคุณอีกครั้ง.


3
แอพพลิเคชั่นเฉพาะที่เกี่ยวข้อง (สิ่งที่ห้องสมุดทำ) อาจส่งผลต่อการเลือกแนวทาง เราได้ใช้ ctypes ค่อนข้างประสบความสำเร็จในการพูดคุยกับ DLLs ที่ผู้จำหน่ายจัดหาให้สำหรับชิ้นส่วนของ hardare (เช่น oscilloscopes) แต่ฉันไม่จำเป็นต้องเลือก ctypes ก่อนเพื่อพูดคุยกับไลบรารีการประมวลผลตัวเลขเนื่องจากค่าใช้จ่ายพิเศษเทียบกับ Cython หรือ SWIG
Peter Hansen

1
ตอนนี้คุณมีสิ่งที่คุณกำลังมองหา สี่คำตอบที่แตกต่างกัน นั่นหมายความว่าตอนนี้คุณมี 4 ตัวเลือกแทนที่จะเป็น 3
Luka Rahne

@ รัลนั่นคือสิ่งที่ฉันเจ้าด้วย :-) แต่อย่างจริงจังฉันไม่ได้คาดหวัง (หรือต้องการ) ตารางโปร / คอนหรือคำตอบเดียวที่บอกว่า "นี่คือสิ่งที่คุณต้องทำ" คำถามใด ๆ เกี่ยวกับการตัดสินใจตอบได้ดีที่สุดกับ "แฟน ๆ " ของแต่ละทางเลือกที่เป็นไปได้ที่ให้เหตุผลของพวกเขา จากนั้นการลงคะแนนของชุมชนจะมีส่วนร่วมเช่นเดียวกับงานของฉัน (ดูที่ข้อโต้แย้งนำไปใช้กับกรณีของฉันอ่านแหล่งข้อมูลที่ให้ ฯลฯ ) เรื่องสั้นสั้น: มีคำตอบที่ดีอยู่ที่นี่
balpha

แล้วคุณจะไปทางไหนด้วย? :)
FogleBird

1
เท่าที่ฉันรู้ (โปรดแก้ไขให้ฉันถ้าฉันผิด), Cython เป็นทางแยกของ Pyrex ที่มีการพัฒนามากขึ้นไปในนั้นทำให้ Pyrex ล้าสมัยไปมาก
balpha

คำตอบ:


115

ctypes เป็นทางออกที่ดีที่สุดของคุณในการทำให้เสร็จอย่างรวดเร็วและยินดีที่ได้ร่วมงานกับคุณขณะที่คุณเขียน Python!

ฉันเพิ่งห่อไดรเวอร์FTDIสำหรับการสื่อสารกับชิป USB โดยใช้ ctypes และมันยอดเยี่ยมมาก ฉันทำงานเสร็จแล้วและทำงานน้อยกว่าหนึ่งวัน (ฉันใช้เฉพาะฟังก์ชั่นที่เราต้องการประมาณ 15 ฟังก์ชั่น)

ก่อนหน้านี้เราใช้โมดูลPyUSBของบุคคลที่สามเพื่อจุดประสงค์เดียวกัน PyUSB เป็นโมดูลส่วนขยาย C / Python จริง แต่ PyUSB ไม่ปล่อย GIL เมื่อทำการบล็อกอ่าน / เขียนซึ่งทำให้เกิดปัญหากับเรา ดังนั้นฉันจึงเขียนโมดูลของเราเองโดยใช้ ctypes ซึ่งจะปล่อย GIL เมื่อเรียกใช้ฟังก์ชันดั้งเดิม

สิ่งหนึ่งที่ควรทราบคือ ctypes จะไม่ทราบ#defineค่าคงที่และสิ่งต่าง ๆ ในห้องสมุดที่คุณใช้งานอยู่มีเพียงฟังก์ชั่นเท่านั้นดังนั้นคุณจะต้องกำหนดค่าคงที่เหล่านั้นอีกครั้งในรหัสของคุณเอง

นี่คือตัวอย่างของวิธีการมองรหัส (จำนวนมากหลุดออกมาเพียงแค่พยายามแสดงให้คุณเห็นส่วนสำคัญของมัน):

from ctypes import *

d2xx = WinDLL('ftd2xx')

OK = 0
INVALID_HANDLE = 1
DEVICE_NOT_FOUND = 2
DEVICE_NOT_OPENED = 3

...

def openEx(serial):
    serial = create_string_buffer(serial)
    handle = c_int()
    if d2xx.FT_OpenEx(serial, OPEN_BY_SERIAL_NUMBER, byref(handle)) == OK:
        return Handle(handle.value)
    raise D2XXException

class Handle(object):
    def __init__(self, handle):
        self.handle = handle
    ...
    def read(self, bytes):
        buffer = create_string_buffer(bytes)
        count = c_int()
        if d2xx.FT_Read(self.handle, buffer, bytes, byref(count)) == OK:
            return buffer.raw[:count.value]
        raise D2XXException
    def write(self, data):
        buffer = create_string_buffer(data)
        count = c_int()
        bytes = len(data)
        if d2xx.FT_Write(self.handle, buffer, bytes, byref(count)) == OK:
            return count.value
        raise D2XXException

มีคนทำการเปรียบเทียบบางอย่างกับตัวเลือกต่างๆ

ฉันอาจจะลังเลมากกว่านี้ถ้าฉันต้องห่อไลบรารี C ++ ที่มีคลาส / เทมเพลต / ฯลฯ จำนวนมาก แต่ ctypes ทำงานได้ดีกับ structs และสามารถเรียกกลับเข้าไปใน Python ได้


5
เข้าร่วมการสรรเสริญสำหรับ ctypes แต่อย่าสังเกตเห็นปัญหาหนึ่ง (ที่ไม่มีเอกสาร): ctypes ไม่สนับสนุนการฟอร์ก หากคุณแยกจากกระบวนการโดยใช้ ctypes และทั้งกระบวนการหลักและกระบวนการย่อยยังคงใช้ ctypes คุณจะสะดุดกับข้อผิดพลาดที่น่ารังเกียจซึ่งเกี่ยวข้องกับ ctypes โดยใช้หน่วยความจำที่ใช้ร่วมกัน
Oren Shemesh

1
@OrenShemesh มีการอ่านเพิ่มเติมเกี่ยวกับปัญหานี้คุณสามารถชี้ให้ฉันได้อย่างไร ฉันคิดว่าฉันอาจปลอดภัยกับโครงการที่ฉันกำลังทำงานอยู่เนื่องจากฉันเชื่อว่ามีเพียงกระบวนการหลักที่ใช้ctypes(สำหรับpyinotify) แต่ฉันต้องการเข้าใจปัญหาให้ละเอียดยิ่งขึ้น
zigg

ข้อความนี้ช่วยฉันได้มากOne thing to note is that ctypes won't know about #define constants and stuff in the library you're using, only the functions, so you'll have to redefine those constants in your own code.ดังนั้นฉันต้องกำหนดค่าคงที่ซึ่งมีในwinioctl.h....
swdev

ประสิทธิภาพเป็นอย่างไร ctypesช้ากว่า c-extension มากเนื่องจากคอขวดคืออินเทอร์เฟซจาก Python ถึง C
TomSawyer

154

คำเตือน: ความคิดเห็นของนักพัฒนาหลักของ Cython

ฉันมักจะแนะนำ Cython มากกว่า ctypes เหตุผลก็คือมันมีเส้นทางการอัพเกรดที่นุ่มนวลขึ้นมาก ถ้าคุณใช้ ctypes หลาย ๆ อย่างจะง่ายในตอนแรกและมันก็เจ๋งมากที่จะเขียนโค้ด FFI ของคุณใน Python ธรรมดาโดยไม่ต้องคอมไพล์สร้างการพึ่งพาและทั้งหมดนั้น อย่างไรก็ตามในบางจุดคุณเกือบจะพบว่าคุณต้องโทรเข้าสู่ไลบรารี C ของคุณอย่างมากไม่ว่าจะเป็นแบบวนซ้ำหรือแบบต่อเนื่องหลายสายและคุณต้องการเพิ่มความเร็วให้สูงขึ้น นั่นคือจุดที่คุณจะสังเกตเห็นว่าคุณไม่สามารถทำได้ด้วย ctypes หรือเมื่อคุณต้องการฟังก์ชั่นการโทรกลับและคุณพบว่ารหัสการติดต่อกลับของ Python ของคุณเป็นคอขวดคุณต้องการเพิ่มความเร็วและ / หรือย้ายลงใน C เช่นกัน อีกครั้งคุณไม่สามารถทำได้ด้วย ctypes

ด้วย Cython, OTOH คุณมีอิสระอย่างสมบูรณ์ในการสร้างรหัสตัดและเรียกที่บางหรือหนาตามที่คุณต้องการ คุณสามารถเริ่มต้นด้วยการโทรอย่างง่าย ๆ ในรหัส C ของคุณจากรหัส Python ปกติและ Cython จะแปลพวกเขาเป็นการโทรแบบ C ดั้งเดิมโดยไม่มีค่าใช้จ่ายการโทรเพิ่มเติมและด้วยค่าใช้จ่ายการแปลงที่ต่ำมากสำหรับพารามิเตอร์ Python เมื่อคุณสังเกตเห็นว่าคุณต้องการประสิทธิภาพที่มากขึ้นในบางจุดที่คุณทำการโทรที่มีราคาแพงมากเกินไปในไลบรารี C ของคุณคุณสามารถเริ่มต้นใส่คำอธิบายรหัสไพ ธ อนที่ล้อมรอบด้วยประเภทคงที่และให้ Cython เพิ่มประสิทธิภาพลงใน C สำหรับคุณ หรือคุณสามารถเริ่มเขียนใหม่บางส่วนของรหัส C ของคุณใน Cython เพื่อหลีกเลี่ยงการโทรและเพื่อให้ชำนาญและกระชับลูปของคุณเป็นอัลกอริทึม และถ้าคุณต้องการการติดต่อกลับที่รวดเร็ว เพียงแค่เขียนฟังก์ชั่นที่มีลายเซ็นที่เหมาะสมและส่งไปยัง C callback registry โดยตรง อีกครั้งไม่มีค่าใช้จ่ายและให้ประสิทธิภาพการโทร C แบบธรรมดา และในกรณีที่มีโอกาสน้อยกว่าที่คุณไม่สามารถรับรหัสของคุณได้เร็วพอใน Cython คุณยังสามารถพิจารณาเขียนส่วนที่สำคัญอย่างแท้จริงใน C (หรือ C ++ หรือ Fortran) และเรียกมันจากรหัส Cython ของคุณตามธรรมชาติและโดยกำเนิด แต่แล้วนี่กลายเป็นทางเลือกสุดท้ายแทนที่จะเป็นทางเลือกเดียว

ctypes ยินดีที่จะทำสิ่งที่ง่ายและให้ทำงานอย่างรวดเร็ว อย่างไรก็ตามทันทีที่สิ่งต่าง ๆ เริ่มเติบโตคุณจะมาถึงจุดที่คุณสังเกตเห็นว่าคุณควรใช้ Cython ตั้งแต่ต้น


4
+1 นั้นเป็นคะแนนที่ดีขอบคุณมาก! แม้ว่าฉันจะสงสัยว่าการย้ายส่วนคอขวดไปยัง Cython นั้นเป็นค่าใช้จ่ายส่วนใหญ่หรือไม่ แต่ฉันเห็นด้วยถ้าคุณคาดว่าจะมีปัญหาเรื่องประสิทธิภาพคุณอาจใช้ Cython ตั้งแต่ต้น
balpha

สิ่งนี้ยังคงไว้สำหรับโปรแกรมเมอร์ที่มีประสบการณ์กับทั้ง C และ Python หรือไม่? ในกรณีนั้นเราอาจโต้แย้งว่า Python / ctypes เป็นตัวเลือกที่ดีกว่าเนื่องจาก vectorization ของ C loops (SIMD) บางครั้งตรงไปตรงมามากกว่า แต่นอกเหนือจากนั้นฉันไม่สามารถคิดถึงข้อเสียของ Cython ได้
Alex van Houten

ขอบคุณสำหรับคำตอบ! สิ่งหนึ่งที่ฉันมีปัญหาเกี่ยวกับ Cython ก็คือการสร้างกระบวนการที่ถูกต้อง (แต่นั่นก็เกี่ยวข้องกับฉันไม่เคยเขียนโมดูล Python มาก่อน) - ฉันควรจะรวบรวมมันมาก่อนหรือรวมไฟล์ต้นฉบับของ Cython ไว้ใน sdist และคำถามที่คล้ายกัน ฉันเขียนโพสต์บล็อกเกี่ยวกับเรื่องนี้ในกรณีที่ใครก็ตามมีปัญหา / ข้อสงสัยคล้ายกัน: martinsosic.com/development/2016/02/08/…
Martinsos

ขอบคุณสำหรับคำตอบ! ข้อเสียเปรียบอย่างหนึ่งเมื่อฉันใช้ Cython คือการใช้งานโอเวอร์โหลดไม่ได้ดำเนินการอย่างสมบูรณ์ (เช่น__radd__) สิ่งนี้น่ารำคาญเป็นพิเศษเมื่อคุณวางแผนให้ชั้นเรียนมีปฏิสัมพันธ์กับบิวด์อิน (เช่นintและfloat) นอกจากนี้วิธีการที่ใช้เวทย์มนตร์ใน cython นั้นเป็นรถบั๊กเล็กน้อยโดยทั่วไป
Monolith

100

Cython เป็นเครื่องมือที่ยอดเยี่ยมในตัวเองคุ้มค่าต่อการเรียนรู้และใกล้กับไวยากรณ์ของ Python หากคุณทำการคำนวณทางวิทยาศาสตร์กับ Numpy แล้ว Cython เป็นหนทางที่จะไปเพราะมันผสานรวมกับ Numpy สำหรับการดำเนินการเมทริกซ์ที่รวดเร็ว

Cython เป็นภาษาชุดของภาษางูใหญ่ คุณสามารถโยนไฟล์ Python ที่ถูกต้องใด ๆ ที่มันและมันจะคายออกโปรแกรม C ที่ถูกต้อง ในกรณีนี้ Cython จะจับคู่การเรียก Python กับ CPython API พื้นฐาน ซึ่งอาจส่งผลให้ความเร็วเพิ่มขึ้น 50% เนื่องจากรหัสของคุณจะไม่ถูกตีความอีกต่อไป

ในการรับการเพิ่มประสิทธิภาพบางอย่างคุณต้องเริ่มต้นแจ้งข้อเท็จจริงเพิ่มเติมเกี่ยวกับรหัสของ Cython เช่นการประกาศประเภท ถ้าคุณบอกพอมันสามารถต้มโค้ดให้บริสุทธิ์ C นั่นคือการวนรอบใน Python กลายเป็นการวนซ้ำใน C ที่นี่คุณจะเห็นการเพิ่มความเร็วสูง คุณสามารถลิงก์ไปยังโปรแกรม C ภายนอกได้ที่นี่

การใช้รหัส Cython นั้นง่ายอย่างไม่น่าเชื่อ ฉันคิดว่าคู่มือทำให้ฟังดูยาก คุณแค่ทำอย่างแท้จริง:

$ cython mymodule.pyx
$ gcc [some arguments here] mymodule.c -o mymodule.so

และจากนั้นคุณสามารถimport mymoduleในรหัส Python ของคุณและลืมไปเลยว่ามันรวบรวมไปยัง C

ไม่ว่าในกรณีใดเนื่องจาก Cython นั้นง่ายต่อการติดตั้งและเริ่มใช้งานฉันขอแนะนำให้ลองใช้ดูว่าเหมาะสมกับความต้องการของคุณหรือไม่ มันจะไม่เป็นการสิ้นเปลืองหากปรากฏว่าไม่ใช่เครื่องมือที่คุณกำลังมองหา


1
ไม่มีปัญหา. สิ่งที่ดีเกี่ยวกับ Cython คือคุณสามารถเรียนรู้เฉพาะสิ่งที่คุณต้องการ หากคุณต้องการการปรับปรุงเพียงเล็กน้อยสิ่งที่คุณต้องทำคือรวบรวมไฟล์ Python ของคุณแล้วเสร็จ
carl

18
"คุณสามารถโยนไฟล์ไพ ธ อนใด ๆ ที่ถูกต้องได้และมันจะคายโปรแกรม C ที่ถูกต้อง" <- ไม่มากมีข้อ จำกัด บางอย่าง: docs.cython.org/src/userguide/limitations.html อาจไม่ใช่ปัญหาสำหรับกรณีการใช้งานส่วนใหญ่ แต่ต้องการให้เสร็จสมบูรณ์
Randy Syring

7
ปัญหาเริ่มน้อยลงในแต่ละรุ่นจนถึงตอนนี้หน้ากล่าวว่า "ปัญหาส่วนใหญ่ได้รับการแก้ไขใน 0.15"
Henry Gomersall

3
ในการเพิ่มมีวิธีที่ง่ายกว่าในการนำเข้าโค้ด cython: เขียนโค้ด cython ของคุณเป็นmymod.pyxโมดูลจากนั้นทำimport pyximport; pyximport.install(); import mymodและการคอมไพล์เกิดขึ้นเบื้องหลัง
Kaushik Ghose

3
@kaushik แม้เรียบง่ายเป็นpypi.python.org/pypi/runcython runcython mymodule.pyxใช้เพียงแค่ และแตกต่างจาก pyximport คุณสามารถใช้มันสำหรับงานเชื่อมโยงที่ต้องการมากขึ้น ข้อแม้เท่านั้นคือฉันเป็นคนที่เขียน 20 bash สำหรับมันและอาจจะลำเอียง
RussellStewart

42

สำหรับการโทรห้องสมุด C จากการประยุกต์ใช้งูใหญ่ยังมีcffiซึ่งเป็นทางเลือกใหม่สำหรับctypes มันนำมาซึ่งรูปโฉมใหม่สำหรับ FFI:

  • มันจัดการกับปัญหาในวิธีที่น่าสนใจและสะอาด (ตรงข้ามกับctypes )
  • มันไม่จำเป็นต้องเขียนโค้ด Python ที่ไม่ใช่ (เช่นในSWIG, Cython , ... )

วิธีห่ออย่างแน่นอนตามที่ OP ต้องการ cython ฟังดูยอดเยี่ยมสำหรับการเขียนลูปที่ร้อนแรงด้วยตัวคุณเอง แต่สำหรับอินเทอร์เฟซ cffi ก็เป็นการอัพเกรดจาก ctypes
บินแกะ

21

ฉันจะโยนอีกอันออกไป: SWIG

ง่ายต่อการเรียนรู้ทำสิ่งต่าง ๆ ให้ถูกต้องและรองรับภาษาอื่น ๆ อีกมากมายดังนั้นเวลาที่ใช้ในการเรียนรู้จะมีประโยชน์มาก

หากคุณใช้ SWIG คุณกำลังสร้างโมดูลส่วนขยายหลามใหม่ แต่ด้วยการใช้งาน SWIG ซึ่งเป็นการยกระดับอย่างหนักสำหรับคุณ


18

โดยส่วนตัวแล้วฉันจะเขียนโมดูลส่วนขยายใน C อย่ากลัวว่าจะใช้ส่วนขยาย Python C เพราะมันไม่ยากเลยที่จะเขียน เอกสารมีความชัดเจนและเป็นประโยชน์ เมื่อฉันเขียนส่วนขยาย C ใน Python เป็นครั้งแรกฉันคิดว่าใช้เวลาประมาณหนึ่งชั่วโมงในการหาวิธีเขียนหนึ่งครั้ง - ไม่มากเลย


การห่อไลบรารี C คุณสามารถค้นหารหัสได้ที่นี่: github.com/mdippery/lehmer
mipadi

1
@forivall: รหัสไม่ได้มีประโยชน์จริงๆและมีเครื่องกำเนิดตัวเลขสุ่มที่ดีกว่า ฉันมีการสำรองข้อมูลบนคอมพิวเตอร์เท่านั้น
mipadi

2
ตกลง C-API ของ Python ไม่น่ากลัวเท่าที่ดู (สมมติว่าคุณรู้จัก C) อย่างไรก็ตามแตกต่างจากไพ ธ อนและคลังทรัพยากรและนักพัฒนาเมื่อเขียนส่วนขยายใน C โดยพื้นฐานแล้วคุณเป็นเจ้าของด้วยตัวคุณเอง อาจเป็นข้อเสียเปรียบเพียงอย่างเดียว (นอกเหนือจากที่เขียนโดยทั่วไปใน C)
Noob Saibot

1
@mipadi: ดี แต่มันต่างกันระหว่าง Python 2.x และ 3.x ดังนั้นจึงสะดวกกว่าที่จะใช้ Cython ในการเขียนส่วนขยายของคุณให้ Cython หารายละเอียดทั้งหมดแล้วรวบรวมรหัส C ที่สร้างขึ้นสำหรับ Python 2.x หรือ 3.x ตามความจำเป็น
0xC0000022L

2
@mipadi ดูเหมือนว่าลิงก์ github นั้นตายแล้วและไม่ปรากฏใน archive.org คุณมีข้อมูลสำรองหรือไม่
jrh

11

ctypesนั้นยอดเยี่ยมเมื่อคุณมี blob ไลบรารีที่คอมไพล์แล้วเพื่อจัดการ (เช่นไลบรารี OS) ค่าโสหุ้ยการโทรนั้นรุนแรง แต่ถ้าคุณจะโทรเข้าไปในห้องสมุดมากและคุณจะต้องเขียนรหัส C ต่อไป (หรืออย่างน้อยก็คอมไพล์มัน) ฉันจะบอกว่าไปCython มันใช้งานไม่ได้มากกว่านี้อีกแล้วและมันจะเร็วขึ้นและไพเราะมากขึ้นในการใช้ไฟล์ pyd ที่เกิดขึ้น

ผมเองมักจะใช้ Cython สำหรับ speedups อย่างรวดเร็วของรหัสหลาม (ลูปและการเปรียบเทียบจำนวนเต็มสองพื้นที่ที่ Cython โดยเฉพาะอย่างยิ่งส่อง) และเมื่อมีโค้ดบางส่วนร่วมมากขึ้น / ห่อของห้องสมุดอื่น ๆ ที่เกี่ยวข้องฉันจะหันไปBoost.Python Boost.Python สามารถตั้งค่าได้อย่างพิถีพิถัน แต่เมื่อคุณได้ทำงานแล้วมันจะทำให้การห่อรหัส C / C ++ ตรงไปตรงมา

cython นั้นยอดเยี่ยมในการห่อnumpy (ซึ่งฉันได้เรียนรู้จากการดำเนินคดี SciPy 2009 ) แต่ฉันไม่ได้ใช้ numpy ดังนั้นฉันจึงไม่สามารถแสดงความคิดเห็นได้


11

หากคุณมีไลบรารีที่มี API ที่กำหนดอยู่แล้วฉันคิดว่าctypesเป็นตัวเลือกที่ดีที่สุดเนื่องจากคุณต้องทำการกำหนดค่าเริ่มต้นเพียงเล็กน้อยแล้วจึงเรียกไลบรารีในแบบที่คุณคุ้นเคย

ฉันคิดว่า Cython หรือสร้างโมดูลส่วนขยายใน C (ซึ่งไม่ยากมาก) มีประโยชน์มากขึ้นเมื่อคุณต้องการโค้ดใหม่เช่นการเรียกไลบรารี่นั้นและทำภารกิจที่ซับซ้อนใช้เวลานานแล้วส่งผ่านผลลัพธ์ไปยัง Python

อีกวิธีหนึ่งสำหรับโปรแกรมอย่างง่ายคือทำกระบวนการที่แตกต่างโดยตรง (รวบรวมจากภายนอก) ส่งผลผลลัพธ์ไปยังเอาต์พุตมาตรฐานและเรียกมันด้วยโมดูลย่อย บางครั้งมันเป็นวิธีที่ง่ายที่สุด

ตัวอย่างเช่นถ้าคุณสร้างโปรแกรมคอนโซล C ที่ทำงานได้มากกว่าหรือน้อยกว่านั้น

$miCcode 10
Result: 12345678

คุณสามารถโทรหาได้จาก Python

>>> import subprocess
>>> p = subprocess.Popen(['miCcode', '10'], shell=True, stdout=subprocess.PIPE)
>>> std_out, std_err = p.communicate()
>>> print std_out
Result: 12345678

ด้วยการจัดรูปแบบสตริงเล็กน้อยคุณสามารถรับผลลัพธ์ในแบบที่คุณต้องการ คุณสามารถจับเอาท์พุทข้อผิดพลาดมาตรฐานได้ดังนั้นมันจึงค่อนข้างยืดหยุ่น


แม้ว่าคำตอบนี้จะไม่ถูกต้อง แต่ผู้ใช้ควรระมัดระวังหากผู้อื่นเรียกใช้รหัสเปิดเนื่องจากการเรียก subprocess ด้วยshell=Trueอาจทำให้เกิดการใช้ประโยชน์บางอย่างเมื่อผู้ใช้รับเชลล์ ไม่เป็นไรเมื่อนักพัฒนาเป็นผู้ใช้คนเดียว แต่ในโลกนี้มีลูกเล่นที่น่ารำคาญมากมายรอให้อะไรแบบนี้
Ben

7

มีปัญหาหนึ่งที่ทำให้ฉันใช้ ctypes ไม่ใช่ cython และไม่ได้กล่าวถึงในคำตอบอื่น ๆ

การใช้ ctypes ผลลัพธ์ไม่ได้ขึ้นอยู่กับคอมไพเลอร์ที่คุณใช้เลย คุณสามารถเขียนห้องสมุดโดยใช้ภาษาใดก็ได้ที่อาจถูกรวบรวมไว้ในไลบรารี่ที่แชร์กัน มันไม่สำคัญว่าระบบใดภาษาใดและคอมไพเลอร์ใด อย่างไรก็ตาม Cython ถูก จำกัด โดยโครงสร้างพื้นฐาน ตัวอย่างเช่นหากคุณต้องการใช้ intel compiler บน windows มันมีความยุ่งยากมากขึ้นในการทำให้ cython ทำงานได้: คุณควร "อธิบาย" คอมไพเลอร์กับ cython, คอมไพล์คอมไพเลอร์ที่แน่นอนนี้และอื่น ๆ


4

หากคุณกำหนดเป้าหมายเป็น Windows และเลือกที่จะรวมไลบรารี C ++ ที่เป็นกรรมสิทธิ์บางส่วนคุณอาจค้นพบว่าmsvcrt***.dll(Visual C ++ Runtime รุ่นต่าง ๆ) นั้นเข้ากันไม่ได้ในเวลาสั้น ๆ

ซึ่งหมายความว่าคุณอาจจะไม่สามารถที่จะใช้Cythonตั้งแต่ปีส่งผลให้wrapper.pydมีการเชื่อมโยงกับmsvcr90.dll (Python 2.7)หรือmsvcr100.dll (Python 3.x) หากไลบรารี่ที่คุณกำลังเชื่อมโยงกับเวอร์ชันรันไทม์ที่แตกต่างกันแสดงว่าคุณโชคไม่ดี

จากนั้นในการทำให้สิ่งต่าง ๆ ใช้งานได้คุณจะต้องสร้าง C wrappers สำหรับไลบรารี C ++ ให้ลิงก์ที่ wrapper dll เทียบกับmsvcrt***.dllไลบรารี C ++ รุ่นเดียวกัน จากนั้นใช้ctypesเพื่อโหลด dll wrapper ที่รีดด้วยมือแบบไดนามิกขณะรันไทม์

ดังนั้นจึงมีรายละเอียดเล็ก ๆ จำนวนมากซึ่งอธิบายไว้ในรายละเอียดมากในบทความต่อไปนี้:

"Beautiful Native Libraries (ใน Python) ": http://lucumr.pocoo.org/2013/8/18/beautiful-native-lไลบรารี/


บทความนั้นไม่มีส่วนเกี่ยวข้องกับปัญหาที่คุณพบกับคอมไพเลอร์ของ Microsoft การทำให้ส่วนขยาย Cython ทำงานบน Windows ได้ไม่ยากนัก ฉันสามารถใช้ MinGW สำหรับทุกสิ่งได้มาก การแจกจ่าย Python ที่ดีช่วยได้
IanH

2
+1 สำหรับการพูดถึงปัญหาที่เป็นไปได้บน windows (ซึ่งฉันกำลังมีอยู่เช่นกัน ... ) @IanH มันเกี่ยวกับ windows โดยทั่วไปน้อยลง แต่มันก็เป็นเรื่องยุ่งถ้าคุณติดอยู่กับ lib อื่น ๆ ที่ไม่ตรงกับการกระจายของ python
เซบาสเตียน


2

ฉันรู้ว่านี่เป็นคำถามเก่า แต่สิ่งนี้เกิดขึ้นบน google เมื่อคุณค้นหาสิ่งต่าง ๆctypes vs cythonและคำตอบส่วนใหญ่ที่นี่เขียนโดยผู้ที่มีความเชี่ยวชาญอยู่แล้วcythonหรือcอาจไม่สะท้อนเวลาจริงที่คุณต้องลงทุนเพื่อเรียนรู้สิ่งเหล่านั้น เพื่อใช้โซลูชันของคุณ ฉันเป็นผู้เริ่มต้นที่สมบูรณ์ในทั้งสองอย่าง ผมไม่เคยสัมผัสมาก่อนและมีประสบการณ์น้อยมากในcythonc/c++

ในช่วงสองวันที่ผ่านมาฉันกำลังมองหาวิธีที่จะมอบหมายส่วนประสิทธิภาพที่หนักของรหัสของฉันให้อยู่ในระดับที่ต่ำกว่างูหลาม ฉันใช้รหัสของฉันทั้งในctypesและCythonซึ่งโดยทั่วไปประกอบด้วยสองฟังก์ชั่นง่าย ๆ

ฉันมีรายการสตริงจำนวนมากที่ต้องดำเนินการ แจ้งให้ทราบล่วงหน้าlistและ stringทั้งสองประเภทไม่สอดคล้องกันอย่างสมบูรณ์กับประเภทในcเพราะสตริงหลามเป็นยูนิโค้ดเริ่มต้นและcสตริงไม่ได้ รายการในไพ ธ อนไม่ใช่เพียงอาร์เรย์ของ c

นี่คือคำตัดสินของฉัน cythonใช้ มันรวมเข้ากับ python ได้อย่างคล่องแคล่วและใช้งานง่ายขึ้นโดยทั่วไป เมื่ออะไรผิดพลาดctypesเพียงแค่พ่นคุณ segfault อย่างน้อยจะให้คุณรวบรวมคำเตือนกับกองติดตามเมื่อใดก็ตามที่เป็นไปได้และคุณสามารถกลับวัตถุหลามที่ถูกต้องได้อย่างง่ายดายด้วยcythoncython

นี่คือบัญชีรายละเอียดเกี่ยวกับระยะเวลาที่ฉันต้องลงทุนในทั้งสองเพื่อใช้งานฟังก์ชั่นเดียวกัน ฉันได้เขียนโปรแกรม C / C ++ น้อยมากโดยวิธี:

  • ctypes:

    • เกี่ยวกับ 2h ในการค้นคว้าวิธีแปลงรายการของสตริง Unicode เป็นชนิดที่เข้ากันได้กับ ac
    • ประมาณหนึ่งชั่วโมงเกี่ยวกับวิธีส่งคืนสตริงอย่างถูกต้องจากฟังก์ชัน ac ที่นี่ฉันให้วิธีการแก้ปัญหาของฉันเองดังนั้นเมื่อฉันได้เขียนฟังก์ชั่น
    • ประมาณครึ่งชั่วโมงเพื่อเขียนโค้ดใน c, คอมไพล์มันไปยังไลบรารี่แบบไดนามิก
    • 10 นาทีในการเขียนรหัสทดสอบในไพ ธ อนเพื่อตรวจสอบว่าcรหัสใช้งานได้หรือไม่
    • ประมาณหนึ่งชั่วโมงในการทำแบบทดสอบและจัดเรียงcรหัสใหม่
    • จากนั้นฉันเสียบcรหัสลงในฐานรหัสจริงและเห็นว่าctypesเล่นได้ไม่ดีกับmultiprocessingโมดูลเนื่องจากตัวจัดการไม่สามารถเลือกได้ตามค่าเริ่มต้น
    • ประมาณ 20 นาทีฉันจัดเรียงโค้ดของฉันใหม่เพื่อไม่ใช้multiprocessingโมดูลและลองใหม่
    • ฟังก์ชั่นที่สองในcรหัสของฉันสร้าง segfaults ในฐานรหัสของฉันแม้ว่ามันจะผ่านการทดสอบรหัสของฉัน นี่อาจเป็นความผิดของฉันที่ตรวจสอบไม่ดีกับเคสแบบขอบฉันกำลังมองหาวิธีแก้ปัญหาอย่างรวดเร็ว
    • ประมาณ 40 นาทีฉันพยายามระบุสาเหตุที่เป็นไปได้ของ segfaults เหล่านี้
    • ฉันแบ่งฟังก์ชั่นของฉันออกเป็นสองห้องสมุดแล้วลองอีกครั้ง ยังคงมี segfaults สำหรับฟังก์ชั่นที่สองของฉัน
    • ฉันตัดสินใจที่จะปล่อยฟังก์ชั่นที่สองและใช้เฉพาะฟังก์ชั่นแรกของcรหัสและในการวนซ้ำที่สองหรือสามของ python loop ที่ใช้มันฉันมีการUnicodeErrorไม่ถอดรหัสไบต์ที่ตำแหน่งบางตำแหน่งแม้ว่าฉันจะเข้ารหัสและถอดรหัสทุกอย่าง explicitely

เมื่อมาถึงจุดนี้ฉันตัดสินใจที่จะค้นหาทางเลือกและตัดสินใจที่จะดูcython:

  • Cython
    • 10 นาทีของการอ่านโลก Cython สวัสดี
    • 15 นาทีของการตรวจสอบดังนั้นเกี่ยวกับวิธีการใช้ Cython กับแทนsetuptoolsdistutils
    • 10 นาทีในการอ่านcython typesและ python types ฉันเรียนรู้ว่าฉันสามารถใช้ชนิดหลามส่วนใหญ่ในตัวสำหรับการพิมพ์แบบคงที่ได้
    • 15 นาทีของการประกาศรหัสไพ ธ อนของฉันอีกครั้งพร้อมกับประเภทของไพ ธ อน
    • 10 นาทีในการแก้ไขของฉันsetup.pyเพื่อใช้โมดูลที่คอมไพล์ใน codebase ของฉัน
    • เสียบโมดูลโดยตรงกับmultiprocessingเวอร์ชันของ codebase มันได้ผล.

สำหรับบันทึกฉันแน่นอนไม่ได้วัดเวลาที่แน่นอนของการลงทุนของฉัน อาจเป็นกรณีที่การรับรู้เวลาของฉันเล็กน้อยที่จะเอาใจใส่เนื่องจากความพยายามทางจิตที่จำเป็นในขณะที่ฉันกำลังจัดการกับ ctypes แต่มันควรถ่ายทอดความรู้สึกของการจัดการกับcythonและctypes

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