بيثون كود الأمثل مع أنواع

تم إعداد ترجمة للمقال خصيصًا لطلاب دورة مطوري Python .




ملحوظة : الكود الموجود في هذه المقالة مرخص بموجب GNU AGPLv3 .

لقد كتبت هذا الدليل لأنني لم أجد دليلاً يجمع بين جميع الأشياء المفيدة حول الأنواع. آمل أن تجعل هذه المقالة حياة شخص ما أسهل كثيرًا.

المحتوى:

  1. التحسينات الأساسية
  2. الأنماط
  3. تجميع بايثون
  4. الهياكل في بيثون
  5. اتصل برمزك في C
  6. تبا

التحسينات الأساسية


قبل إعادة كتابة شفرة مصدر Python في C ، فكر في طرق التحسين الأساسية في Python.

هياكل البيانات المضمنة


يتم كتابة هياكل البيانات المضمنة في Python ، مثل set و dict ، في C. تعمل بشكل أسرع بكثير من هياكل البيانات الخاصة بك المكتوبة على شكل فئات Python. يتم وصف هياكل البيانات الأخرى إلى جانب المجموعة القياسية والإملاء والقائمة والمجموعة في وثائق وحدة المجموعات .

سرد التعبيرات


بدلاً من إضافة عناصر إلى القائمة باستخدام الطريقة القياسية ، استخدم تعبيرات القائمة.

# Slow
      mapped = []
      for value in originallist:
          mapped.append(myfunc(value))
      
      # Faster
      mapped = [myfunc(value) in originallist]

الأنواع


تسمح لك وحدة ctypes بالتفاعل مع كود C من Python دون استخدام وحدة subprocessأو وحدة أخرى مماثلة لبدء عمليات أخرى من CLI.

هناك جزءان فقط: تجميع كود C للتحميل في الجودة shared objectوإعداد هياكل البيانات في كود

Python لتعيينها على الأنواع C. في هذه المقالة سأقوم بتوصيل رمز Python الخاص بي بـ lcs.c ، والذي يجد أطول مدة متضمنة في جزئين قوائم السلاسل. أريد أن يعمل ما يلي في Python:

list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
      list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
      
      common = lcs(list1, list2)
      
      print(common)
      # ['My', 'name', 'is', 'Stevens']

إحدى المشاكل هي أن هذه الدالة C المحددة هي توقيع دالة تأخذ قوائم السلاسل كنوع وسيطة وتعيد نوعًا ليس له طول ثابت. أحل هذه المشكلة ببنية تسلسلية تحتوي على مؤشرات وطول.

تجميع كود C في Python


أولاً ، يتم تجميع شفرة مصدر C ( lcs.c ) lcs.soللتحميل في Python.

gcc -c -Wall -Werror -fpic -O3 lcs.c -o lcs.o
      gcc -shared -o lcs.so lcs.o

  • - سيعرض الجدار جميع التحذيرات ؛
  • - W Will التفاف جميع التحذيرات في الأخطاء.
  • - ستنشئ fpic تعليمات مستقلة عن الموقع ستحتاجها إذا كنت تريد استخدام هذه المكتبة في Python ؛
  • - O3 يزيد من التحسين ؛

والآن سنبدأ في كتابة رمز Python باستخدام ملف الكائن المشترك الناتج .

الهياكل في بيثون


فيما يلي هيكلان للبيانات يتم استخدامها في رمز C الخاص بي.

struct Sequence
      {
          char **items;
          int length;
      };
      
      struct Cell
      {
          int index;
          int length;
          struct Cell *prev;
      };

وهنا ترجمة هذه الهياكل إلى بيثون.

import ctypes
      class SEQUENCE(ctypes.Structure):
          _fields_ = [('items', ctypes.POINTER(ctypes.c_char_p)),
                      ('length', ctypes.c_int)]
      
      class CELL(ctypes.Structure):
          pass
      
      CELL._fields_ = [('index', ctypes.c_int), ('length', ctypes.c_int),
                       ('prev', ctypes.POINTER(CELL))]

بعض الملاحظات:

  • جميع الهياكل هي الطبقات التي ترث من ctypes.Structure.
  • الحقل الوحيد _fields_هو قائمة بالصفوف. كل صف هو ( <variable-name>، <ctypes.TYPE>).
  • هناك ctypesأنواع متشابهة في c_char (char) و c_char_p (* char) .
  • هناك ctypesأيضًا واحد POINTER()يُنشئ مؤشر نوع من كل نوع يتم تمريره إليه.
  • إذا كان لديك تعريف عودي مثل في CELL، يجب عليك تمرير الإعلان الأولي ، ثم إضافة الحقول من _fields_أجل الحصول على رابط لنفسك لاحقًا.
  • نظرًا لأنني لم أستخدم CELLPython في الكود الخاص بي ، لم أكن بحاجة إلى كتابة هذه البنية ، ولكن لها خاصية مثيرة للاهتمام في المجال العودي.

اتصل برمزك في C


بالإضافة إلى ذلك ، كنت بحاجة إلى بعض التعليمات البرمجية لتحويل أنواع Python إلى بنى جديدة في C. يمكنك الآن استخدام وظيفة C الجديدة لتسريع كود Python.

def list_to_SEQUENCE(strlist: List[str]) -> SEQUENCE:
          bytelist = [bytes(s, 'utf-8') for s in strlist]
          arr = (ctypes.c_char_p * len(bytelist))()
          arr[:] = bytelist
          return SEQUENCE(arr, len(bytelist))
      
      
      def lcs(s1: List[str], s2: List[str]) -> List[str]:
          seq1 = list_to_SEQUENCE(s1)
          seq2 = list_to_SEQUENCE(s2)
      
          # struct Sequence *lcs(struct Sequence *s1, struct Sequence *s2)
          common = lcsmodule.lcs(ctypes.byref(seq1), ctypes.byref(seq2))[0]
      
          ret = []
      
          for i in range(common.length):
              ret.append(common.items[i].decode('utf-8'))
          lcsmodule.freeSequence(common)
      
          return ret
      
      lcsmodule = ctypes.cdll.LoadLibrary('lcsmodule/lcs.so')
      lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE)
      
      list1 = ['My', 'name', 'is', 'Sam', 'Stevens', '!']
      list2 = ['My', 'name', 'is', 'Alex', 'Stevens', '.']
      
      common = lcs(list1, list2)
      
      print(common)
      # ['My', 'name', 'is', 'Stevens']

بعض الملاحظات:

  • **char (قائمة السلاسل) تتطابق مباشرة مع قائمة وحدات البايت في Python.
  • هناك lcs.cوظيفة lcs()باستخدام Sequence * lcs (بنية التسلسل * s1 ، تسلسل الهيكل * s2) . لإعداد نوع الإرجاع ، أستخدم lcsmodule.lcs.restype = ctypes.POINTER(SEQUENCE).
  • لإجراء مكالمة مع الإشارة إلى هيكل التسلسل ، أستخدم ctypes.byref()واحدًا يعيد "مؤشر ضوئي" إلى كائنك (أسرع من ctypes.POINTER()).
  • common.items- هذه قائمة بالبايت ، يمكن فك تشفيرها للحصول retعلى شكل قائمة str.
  • lcsmodule.freeSequence (عام) يحرر الذاكرة المرتبطة بالعام . هذا مهم لأن جامع القمامة (AFAIK) لن يقوم بجمعه تلقائيًا.

رمز Python المحسن هو رمز كتبته في C ولفه في Python.

شيء أكثر: PyPy


انتباه: أنا نفسي لم أستخدم PyPy أبدًا.
أحد أبسط التحسينات هو تشغيل برامجك في وقت تشغيل PyPy ، الذي يحتوي على مترجم JIT (في الوقت المناسب) يعمل على تسريع عمل الحلقات ، وتجميعها في رمز الجهاز من أجل التنفيذ المتكرر.

إذا كان لديك تعليقات أو تريد مناقشة شيء ما ، فاكتب إلي (samuel.robert.stevens@gmail.com).

هذا كل شئ. نراكم في الدورة !

All Articles