المؤشرات في بيثون: ما هي الفائدة؟


إذا كنت قد عملت مع لغات منخفضة المستوى مثل C أو C ++ ، فربما تكون قد سمعت عن المؤشرات. أنها تسمح لك لزيادة فعالية أجزاء مختلفة من التعليمات البرمجية بشكل كبير. لكن يمكنهم أيضًا الخلط بين المبتدئين - وحتى المطورين ذوي الخبرة - ويؤدي إلى أخطاء إدارة الذاكرة. هل هناك مؤشرات في بايثون ، هل يمكنني محاكاة تلك بطريقة أو بأخرى؟

تستخدم المؤشرات على نطاق واسع في C و C ++. في الواقع ، هذه هي المتغيرات التي تحتوي على عناوين الذاكرة التي توجد بها المتغيرات الأخرى. لتنظيف المؤشرات ، اقرأ هذا الاستعراض .

بفضل هذه المقالة ، ستفهم بشكل أفضل نموذج الكائن في Python وتكتشف لماذا لا توجد مؤشرات بالفعل في هذه اللغة. إذا احتجت إلى محاكاة سلوك المؤشرات ، فسوف تتعلم كيف تحاكيها دون الكابوس المصاحب لإدارة الذاكرة.

مع هذا المقال ، أنت:

  • تعلم لماذا لا تحتوي Python على مؤشرات.
  • تعلم الفرق بين المتغيرات C والأسماء في بيثون.
  • تعلم لمحاكاة المؤشرات في بيثون.
  • استخدم ctypes لتجربة مؤشرات حقيقية.

ملاحظة : هنا ، يتم تطبيق مصطلح "Python" على تطبيق Python في C ، والذي يعرف باسم CPython. جميع مناقشات جهاز اللغة صالحة لـ CPython 3.7 ، ولكنها قد لا تتوافق مع التكرارات اللاحقة.

لماذا لا توجد مؤشرات في بايثون؟


لا اعرف يمكن أن توجد مؤشرات في بيثون أصلا؟ ربما ، ولكن يبدو أن المؤشرات تتناقض مع مفهوم Zen of Python ، لأنها تثير تغييرات ضمنية بدلاً من التغييرات الصريحة. غالبًا ما تكون المؤشرات معقدة جدًا ، خاصة بالنسبة للمبتدئين. علاوة على ذلك ، يدفعونك إلى اتخاذ قرارات غير ناجحة أو القيام بشيء خطير حقًا ، مثل القراءة من منطقة الذاكرة ، حيث لا ينبغي أن تقرأها.

يحاول Python الاستغناء عن تفاصيل التنفيذ من المستخدم ، مثل عنوان الذاكرة. غالبًا ما يكون التركيز في هذه اللغة على سهولة الاستخدام وليس على السرعة. لذلك المؤشرات في بيثون لا معنى لها. ولكن لا تقلق ، فاللغة توفر لك افتراضيًا بعض فوائد استخدام المؤشرات.

لفهم المؤشرات في Python ، دعنا ننتقل لفترة وجيزة إلى ميزات تطبيق اللغة. على وجه الخصوص ، تحتاج إلى فهم:

  1. ما هي كائنات قابلة للتغيير وغير قابلة للتغيير.
  2. كيف يتم ترتيب المتغيرات / الأسماء في بايثون.

التمسك بعناوين الذاكرة الخاصة بك ، دعنا نذهب!

كائنات في بيثون


كل شيء في بيثون هو كائن. على سبيل المثال ، افتح REPL isinstance() كيف isinstance() :

 >>> isinstance(1, object) True >>> isinstance(list(), object) True >>> isinstance(True, object) True >>> def foo(): ... pass ... >>> isinstance(foo, object) True 

يوضح هذا الرمز أن كل شيء في بيثون هو في الواقع كائن. يحتوي كل كائن على ثلاثة أنواع على الأقل من البيانات:

  • مرجع العداد.
  • اكتب.
  • قيمة.

يتم استخدام عداد مرجع لإدارة الذاكرة. تتم كتابة تفاصيل حول هذه الإدارة في إدارة الذاكرة في بيثون . يستخدم النوع على مستوى CPython لتوفير سلامة النوع أثناء وقت التشغيل. والقيمة هي القيمة الفعلية المرتبطة بالكائن.

لكن ليست كل الأشياء متشابهة. هناك فرق مهم واحد: الكائنات قابلة للتغيير وغير قابلة للتغيير. سيساعدك فهم هذا التمييز بين أنواع الكائنات على فهم الطبقة الأولى من البصل بشكل أفضل والذي يسمى "مؤشرات في بيثون".

كائنات قابلة للتغيير وغير قابلة للتغيير


يوجد نوعان من الكائنات في Python:

  1. كائنات ثابتة (لا يمكن تغييرها) ؛
  2. كائنات قابلة للتعديل (عرضة للتغيير).

إن إدراك هذا الاختلاف هو أول مفتاح للسفر عبر عالم المؤشرات في بيثون. فيما يلي وصف لقابلية بعض الأنواع الشائعة:

نوع
غير قابلة للتغيير؟
الباحث
نعم
عوامة
نعم
منطقي
نعم
مجمع
نعم
الصفوف (tuple)
نعم
frozenset
نعم
شارع
نعم
قائمة
لا
مجموعة
لا
ديكت
لا

كما ترون ، فإن العديد من الأنواع البدائية الشائعة الاستخدام غير قابلة للتغيير. يمكنك التحقق من ذلك عن طريق كتابة بعض رموز بايثون. ستحتاج إلى أداتين من المكتبة القياسية:

  1. id() بإرجاع عنوان ذاكرة الكائن ؛
  2. is إرجاع True إذا وفقط إذا كان لكائنين نفس عنوان الذاكرة.

يمكنك تشغيل هذا الرمز في بيئة REPL:

 >>> x = 5 >>> id(x) 94529957049376 

نحن هنا تعيين المتغير x إلى 5 . إذا حاولت تغيير القيمة باستخدام الإضافة ، فستحصل على كائن جديد:

 >>> x += 1 >>> x 6 >>> id(x) 94529957049408 

على الرغم من أن هذا الرمز قد يغير ببساطة قيمة x ، إلا أنه في الواقع تحصل على كائن جديد كإجابة.

نوع str غير قابل للتغيير أيضًا:

 >>> s = "real_python" >>> id(s) 140637819584048 >>> s += "_rocks" >>> s 'real_python_rocks' >>> id(s) 140637819609424 

وفي هذه الحالة ، تحصل s بعد العملية += على عنوان ذاكرة مختلف .

المكافأة : += المشغل يترجم إلى استدعاءات الطرق المختلفة.

بالنسبة لبعض الكائنات ، مثل القائمة ، += يتحول إلى __iadd__() (إلحاق محلي). سوف يغير نفسه ويعيد نفس المعرف. ومع ذلك ، لا تملك str و int هذه الطرق ، ونتيجة لذلك ، سيتم استدعاء __iadd__() بدلاً من __iadd__() .

راجع وثائق نموذج بيانات Python لمزيد من التفاصيل .

عندما نحاول تغيير قيمة سلسلة s نحصل على خطأ:

 >>> s[0] = "R" 

تتبع الخلف (يتم عرض آخر المكالمات في آخر):

  File "<stdin>", line 1, in <mdule> TypeError: 'str' object does not support item assignment 

تعطل الرمز أعلاه وتقارير Python أن str لا تدعم هذا التغيير ، والذي يتوافق مع تعريف قابلية تثبيت النوع str .

قارن مع كائن قابل للتغيير ، على سبيل المثال ، بقائمة:

 >>> my_list = [1, 2, 3] >>> id(my_list) 140637819575368 >>> my_list.append(4) >>> my_list [1, 2, 3, 4] >>> id(my_list) 140637819575368 

يوضح هذا الرمز الفرق الرئيسي بين نوعي الكائنات. في البداية ، لدى my_list معرّف. حتى بعد إضافة 4 إلى القائمة ، لا يزال لدى my_list نفس المعرف. والسبب هو أن list النوع قابلة للتغيير.

فيما يلي عرض تقديمي لقائمة قابلية التحويل باستخدام الواجب:

 >>> my_list[0] = 0 >>> my_list [0, 2, 3, 4] >>> id(my_list) 140637819575368 

في هذا الرمز ، قمنا بتغيير my_list بتعيينه على 0 كعنصر أول. ومع ذلك ، احتفظت القائمة بنفس المعرف بعد هذه العملية. الخطوة التالية في طريقنا إلى تعلم بيثون هي استكشاف نظامها البيئي.

نحن نتعامل مع المتغيرات


تختلف المتغيرات في Python بشكل أساسي عن المتغيرات في C و C ++. أساسا ، هم فقط لا وجود لهم في بيثون. بدلا من المتغيرات ، هناك أسماء .

قد يبدو مشوهًا ، وفي أغلب الأحيان يكون كذلك. في معظم الأحيان ، يمكنك أن تأخذ أسماء في Python كمتغيرات ، ولكن عليك أن تفهم الفرق. هذا مهم بشكل خاص عند دراسة موضوع صعب مثل المؤشرات.

لتسهيل فهمك ، دعونا نرى كيف تعمل المتغيرات في C ، وما تمثله ، ثم نقارن بعمل الأسماء في Python.

المتغيرات في C


خذ الكود الذي يعرف المتغير x :

 int x = 2337; 

يمر تنفيذ هذا الخط القصير بعدة مراحل مختلفة:

  1. تخصيص ذاكرة كافية لعدد.
  2. احالة 2337 لموقع الذاكرة هذا.
  3. التعيين الذي يشير إلى x هذه القيمة.

قد تبدو الذاكرة المبسطة كما يلي:



هنا ، المتغير x له عنوان مزيف 0x7f1 وقيمة 2337 . إذا كنت تريد لاحقًا تغيير قيمة x ، فيمكنك القيام بذلك:

 x = 2338; 

يعين هذا الرمز المتغير x قيمة جديدة قدرها 2338 ، وبالتالي الكتابة فوق القيمة السابقة . هذا يعني أن المتغير x قابل للتغيير . نظام الذاكرة المحدّث للقيمة الجديدة:



يرجى ملاحظة أن موقع x لم يتغير ، فقط القيمة نفسها. هذا مهم. هذا يخبرنا أن x هو مكان في الذاكرة ، وليس مجرد اسم.

يمكنك أيضًا اعتبار هذه المشكلة جزءًا من مفهوم الملكية. من ناحية ، يمتلك x مكانًا في الذاكرة. أولاً ، x عبارة عن مربع فارغ يمكن أن يحتوي على عدد صحيح واحد فقط ، حيث يمكن تخزين قيم عدد صحيح.

عندما تقوم بتعيين x بعض القيمة ، فإنك تضع القيمة في مربع ينتمي إلى x . إذا كنت ترغب في تقديم متغير y جديد ، يمكنك إضافة هذا السطر:

 int y = x; 

ينشئ هذا الرمز مربعًا جديدًا يسمى y وينسخ القيمة من x إلى ذلك. تبدو دائرة الذاكرة الآن كما يلي:



لاحظ الموقع الجديد y - 0x7f5 . على الرغم من أن القيمة x تم نسخها إلى x ، إلا أن المتغير y يملك عنوانًا جديدًا في الذاكرة. لذلك ، يمكنك الكتابة فوق قيمة y دون التأثير على x :

 y = 2339; 

تبدو دائرة الذاكرة الآن كما يلي:



أكرر: لقد غيرت قيمة y ، ولكن ليس الموقع. بالإضافة إلى ذلك ، لم تؤثر على المتغير الأصلي x .

مع أسماء في بيثون ، فإن الوضع مختلف تماما.

أسماء في بيثون


لا توجد متغيرات في Python ، الأسماء بدلاً من ذلك. يمكنك استخدام مصطلح "المتغيرات" وفقًا لتقديرك ، ولكن من المهم معرفة الفرق بين المتغيرات والأسماء.

لنأخذ الرمز المكافئ من المثال C أعلاه ونكتبه في Python:

 >>> x = 2337 

كما في C ، يمر الكود بعدة خطوات منفصلة أثناء تنفيذ هذا:

  1. يتم إنشاء PyObject.
  2. يتم تعيين رمز PyObject للرمز.
  3. 2337 تعيين 2337 قيمة ل PyObject.
  4. يتم إنشاء الاسم x .
  5. تشير x إلى PyObject الجديد.
  6. يتم زيادة عدد مرجع PyObject بمقدار 1.

ملاحظة : PyObject ليس ككائن في Python ، هذا الكيان خاص بـ CPython ويمثل البنية الأساسية لجميع كائنات Python.

يتم تعريف PyObject على أنه بنية C ، لذلك إذا كنت تتساءل لماذا لا يمكنك الاتصال مباشرة بـ typecode أو العداد المرجعي ، فالسبب هو أنه ليس لديك إمكانية الوصول المباشر إلى الهياكل. يمكن أن تساعد أساليب الاتصال مثل sys.getrefcount () في الحصول على نوع من الأشياء الداخلية.

إذا تحدثنا عن الذاكرة ، فقد يبدو الأمر كما يلي:



هنا ، تختلف دائرة الذاكرة عن الدائرة في C الموضحة أعلاه. بدلاً من امتلاك x كتلة من الذاكرة تخزن القيمة 2337 ، فإن كائن Python الذي تم إنشاؤه حديثًا يمتلك الذاكرة التي يعيش عليها 2337 . اسم Python x لا يمتلك مباشرة أي عنوان في الذاكرة ، تماماً كما يمتلك متغير C خلية ثابتة.

إذا كنت ترغب في تعيين قيمة جديدة x ، فجرب هذا الرمز:

 >>> x = 2338 

سيختلف سلوك النظام عما يحدث في C ، ولكنه لن يختلف كثيرًا عن الارتباط الأصلي في Python.

في هذا الكود:

  • يتم إنشاء PyObject جديد.
  • يتم تعيين رمز PyObject للرمز.
  • 2 تعيين قيمة ل PyObject.
  • تشير x إلى PyObject الجديد.
  • يتم زيادة عدد مرجع PyObject الجديد بمقدار 1.
  • يتم تقليل عدد مرجع PyObject القديم بمقدار 1.

تبدو دائرة الذاكرة الآن كما يلي:



يوضح هذا الرسم التوضيحي أن x يشير إلى مرجع لكائن ولا يمتلك مساحة الذاكرة كما كان من قبل. سترى أيضًا أن الأمر x = 2338 ليس مهمة ، ولكنه ملزم للاسم x بالرابط.

بالإضافة إلى ذلك ، الكائن السابق (الذي يحتوي على القيمة 2337 ) موجود الآن في الذاكرة بعدد مرجعي 0 ، وسيتم إزالته بواسطة أداة تجميع مجمعي البيانات المهملة .

يمكنك إدخال اسم جديد y ، كما في المثال C:

 >>> y = x 

سيظهر اسم جديد في الذاكرة ، ولكن ليس بالضرورة كائن جديد:



الآن ترى أنه لم يتم إنشاء كائن Python جديد ، تم إنشاء اسم جديد فقط يشير إلى نفس الكائن. بالإضافة إلى ذلك ، زاد عداد مرجع الكائن بمقدار 1. يمكنك التحقق من معادلة هوية الكائنات لتأكيد هويتها:

 >>> y is x True 

يوضح هذا الرمز أن x و y هما كائن واحد. ولكن لا تخطئ: y لا يزال غير قابل للتغيير. على سبيل المثال ، يمكنك إجراء عملية إضافة باستخدام y :

 >>> y += 1 >>> y is x False 

بعد استدعاء الإضافة ، ستقوم بإرجاع كائن Python جديد. الآن تبدو الذاكرة كما يلي:



تم إنشاء كائن جديد ، وتشير y الآن إليه. من الغريب أن نحصل على نفس الحالة النهائية بالضبط إذا ربطنا y مباشرة بـ 2339 :

 >>> y = 2339 

بعد هذا التعبير ، نحصل على مثل هذه الحالة النهائية للذاكرة ، كما في عملية الإضافة. دعني أذكرك بأنك في Python لا تقوم بتعيين متغيرات ، لكن تربط الأسماء بالروابط.

حول المتدربين في بيثون


أنت الآن تفهم كيف يتم إنشاء كائنات جديدة في Python وكيف يتم إرفاق أسماء بها. لقد حان الوقت للحديث عن الأشياء المعتدلة.

لدينا رمز بايثون:

 >>> x = 1000 >>> y = 1000 >>> x is y True 

كما كان من قبل ، فإن x و y هي أسماء تشير إلى نفس كائن Python. لكن هذا الكائن الذي يحتوي على القيمة 1000 لا يمكن أن يكون له دائمًا عنوان الذاكرة نفسه. على سبيل المثال ، إذا قمت بإضافة رقمين وحصلت على 1000 ، فستحصل على عنوان آخر:

 >>> x = 1000 >>> y = 499 + 501 >>> x is y False 

هذه المرة ، السلسلة x is y بإرجاع False . إذا كنت محرجًا ، فلا تقلق. إليك ما يحدث عند تنفيذ هذا الرمز:

  1. يتم إنشاء كائن Python ( 1000 ).
  2. يتم إعطاء اسم x .
  3. يتم إنشاء كائن Python ( 499 ).
  4. يتم إنشاء كائن Python ( 501 ).
  5. هذان الكائنان تضيف ما يصل.
  6. يتم إنشاء كائن Python جديد ( 1000 ).
  7. أعطيت اسم y .

تفسيرات فنية : الخطوات الموضحة تحدث فقط عندما يتم تنفيذ هذا الرمز داخل REPL. إذا أخذت المثال أعلاه ، الصقه في الملف وتشغيله ، ثم السطر x is y سيعود True .

السبب هو الذكاء السريع لبرنامج التحويل البرمجي CPython ، الذي يحاول تنفيذ تحسينات ثقب الباب التي تساعد على حفظ خطوات تنفيذ التعليمات البرمجية قدر الإمكان. يمكن العثور على التفاصيل في الكود المصدري لمحسن ثقب الصخور CPython .

ولكن أليس هذا تبذير؟ حسنًا ، نعم ، لكنك تدفع هذا السعر مقابل كل فوائد بيثون الرائعة. لا تحتاج إلى التفكير في حذف هذه الأشياء الوسيطة ، ولا تحتاج حتى إلى معرفة وجودها! المزاح هو أن هذه العمليات تتم بسرعة نسبية ، ولن تعرف عنها حتى تلك اللحظة.

لاحظ مبدعو Python بحكمة هذا النفقات العامة وقرروا إجراء العديد من التحسينات. النتيجة هي السلوك الذي قد يفاجئ المبتدئين:

 >>> x = 20 >>> y = 19 + 1 >>> x is y True 

في هذا المثال ، الكود هو نفسه كما هو مذكور أعلاه ، باستثناء أننا نحصل على True . كل شيء عن الأشياء المعتدلة. يقوم Python بإنشاء مجموعة فرعية محددة من الكائنات في الذاكرة مسبقًا وتخزينها في مساحة الاسم العالمية للاستخدام اليومي.

أي الكائنات تعتمد على تطبيق بايثون؟ في CPython 3.7 ، المعتقلون هم:

  1. أعداد صحيحة تتراوح من -5 إلى 256 .
  2. سلاسل تحتوي فقط على أحرف ASCII أو أرقامها أو الشرطة السفلية.

وذلك لأن هذه المتغيرات غالباً ما تستخدم في العديد من البرامج. بواسطة interning ، يمنع Python تخصيص الذاكرة للكائنات الثابتة.

سيتم تدوين الخطوط التي يقل حجمها عن 20 حرفًا والتي تحتوي على أحرف ASCII أو أرقامها أو علاماتها السفلية لأن من المفترض أن تستخدم كمعرفات:

 >>> s1 = "realpython" >>> id(s1) 140696485006960 >>> s2 = "realpython" >>> id(s2) 140696485006960 >>> s1 is s2 True 

هنا s1 و s2 أشر إلى نفس العنوان في الذاكرة. إذا لم نقم بإدخال حرف أو رقم أو تسطير ASCII ، فسنحصل على نتيجة مختلفة:

 >>> s1 = "Real Python!" >>> s2 = "Real Python!" >>> s1 is s2 False 

يستخدم هذا المثال علامة تعجب ، لذلك لا يتم تدوين السلاسل وتكون كائنات مختلفة في الذاكرة.

المكافأة : إذا كنت تريد أن تشير هذه الكائنات إلى نفس الكائن sys.intern() ، فيمكنك استخدام sys.intern() . توجد طريقة واحدة لاستخدام هذه الميزة موضحة في الوثائق:

تعتبر سلسلة التدوير مفيدة لزيادة طفيفة في أداء بحث القاموس: إذا تم اختبار المفاتيح الموجودة في القاموس والمفتاح المطلوب البحث فيه ، فيمكن إجراء مقارنات أساسية (بعد التجزئة) عن طريق مقارنة المؤشرات بدلاً من السلاسل. ( المصدر )

وغالبا ما يخلط المعتقلون بين المبرمجين. فقط تذكر أنه إذا بدأت الشك ، فيمكنك دائمًا استخدام id() وتحديد معادلة الكائنات.

مؤشر بايثون مضاهاة


حقيقة أن المؤشرات غائبة في بيثون لا تعني أنه لا يمكنك الاستفادة من المؤشرات. هناك بالفعل عدة طرق لمحاكاة المؤشرات في بيثون. هنا ننظر إلى اثنين منهم:

  1. استخدم كمؤشرات لأنواع قابلة للتغيير.
  2. باستخدام كائنات بيثون أعدت خصيصا.

استخدم كمؤشرات نوع قابلة للتغيير


أنت تعرف بالفعل ما هي أنواع قابلة للتغيير. وبفضل قابليتها للتغير ، يمكننا محاكاة سلوك المؤشرات. لنفترض أنك بحاجة إلى تكرار هذا الرمز:

 void add_one(int *x) { *x += 1; } 

يأخذ هذا الرمز مؤشرًا إلى رقم ( *x ) ويزيد القيمة بمقدار 1. فيما يلي الوظيفة الرئيسية لتنفيذ التعليمات البرمجية:

 #include <stdi.h> int main(void) { int y = 2337; printf("y = %d\n", y); add_one(&y); printf("y = %d\n", y); return 0; } 

في الجزء أعلاه ، قمنا بتعيين y إلى 2337 ، 2337 القيمة الحالية ، وقمنا 2337 بمقدار 1 ، ثم عرضنا قيمة جديدة. يظهر التالي على الشاشة:

 y = 2337 y = 2338 

إحدى الطرق لتكرار هذا السلوك في Python هي استخدام نوع قابل للتغيير. على سبيل المثال ، قم بتطبيق قائمة وتغيير العنصر الأول:

 >>> def add_one(x): ... x[0] += 1 ... >>> y = [2337] >>> add_one(y) >>> y[0] 2338 

add_one(x) إلى العنصر الأول وتزيد من قيمته بمقدار 1. يعني استخدام القائمة أنه نتيجة لذلك نحصل على القيمة التي تم تغييرها. لذلك هناك مؤشرات في بيثون؟ لا. أصبح السلوك الموضح ممكنًا لأن القائمة نوع قابل للتغيير. إذا حاولت استخدام tuple ، فسوف تحصل على خطأ:

 >>> z = (2337,) >>> add_one(z) 

تتبع للخلف (آخر مكالمات تستمر آخر):

  File "<stdin>", line 1, in <module> File "<stdin>", line 2, in add_one TypeError: 'tuple' object does not support item assignment 

يوضح هذا الرمز ثبات tuple ، لذلك لا يدعم تخصيص العنصر.

list النوع الوحيد القابل للتغيير ؛ يتم محاكاة مؤشرات الأجزاء أيضًا باستخدام dict .

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

 >>> counters = {"func_calls": 0} >>> def bar(): ... counters["func_calls"] += 1 ... >>> def foo(): ... counters["func_calls"] += 1 ... bar() ... >>> foo() >>> counters["func_calls"] 2 

في هذا المثال ، يستخدم القاموس عدادات لتتبع عدد استدعاءات الوظائف. بعد استدعاء foo() زاد العداد بمقدار 2 ، كما هو متوقع. وكل ذلك بفضل dict .

لا تنس ، هذا مجرد محاكاة لسلوك المؤشر ، لا علاقة له بالمؤشرات الحقيقية في C و C ++. يمكننا القول أن هذه العمليات أغلى مما لو أجريت في C أو C ++.

باستخدام كائنات بيثون


تُعد dict طريقة رائعة لمحاكاة المؤشرات في Python ، ولكن من الممل أحيانًا تذكر الاسم الرئيسي الذي استخدمته. خاصة إذا كنت تستخدم القاموس في أجزاء مختلفة من التطبيق. يمكن لصف بيثون المخصص المساعدة هنا.

لنفترض أنك بحاجة إلى تتبع المقاييس في التطبيق. طريقة رائعة لتجاهل التفاصيل المزعجة هي إنشاء فصل دراسي:

 class Metrics(object): def __init__(self): self._metrics = { "func_calls": 0, "cat_pictures_served": 0, } 

يحدد هذا الرمز فئة Metrics . لا يزال يستخدم القاموس لتخزين البيانات _metrics في _metrics عضو _metrics . هذا سيعطيك قابلية التحويل المطلوبة. الآن تحتاج فقط للوصول إلى هذه القيم. يمكنك القيام بذلك باستخدام الخصائص:

 class Metrics(object): # ... @property def func_calls(self): return self._metrics["func_calls"] @property def cat_pictures_served(self): return self._metrics["cat_pictures_served"] 

نحن هنا نستخدم property . إذا كنت جديدًا في مجال الديكور ، فاقرأ مقالة Primer on Python Decorators . في هذه الحالة ، يسمح لك func_calls بالوصول إلى func_calls و cat_pictures_served ، كما لو كانت سمات:

 >>> metrics = Metrics() >>> metrics.func_calls 0 >>> metrics.cat_pictures_served 0 

حقيقة أنه يمكنك الرجوع إلى هذه الأسماء كسمات تعني أنك مستخلص من حقيقة أن هذه القيم مخزنة في القاموس. بالإضافة إلى ذلك ، يمكنك جعل أسماء السمات أكثر وضوحًا. بالطبع ، يجب أن تكون قادرًا على زيادة القيم:

 class Metrics(object): # ... def inc_func_calls(self): self._metrics["func_calls"] += 1 def inc_cat_pics(self): self._metrics["cat_pictures_served"] += 1 

:

  1. inc_func_calls()
  2. inc_cat_pics()

metrics . , , :

 >>> metrics = Metrics() >>> metrics.inc_func_calls() >>> metrics.inc_func_calls() >>> metrics.func_calls 2 

func_calls inc_func_calls() Python. , - metrics , .

: , inc_func_calls() inc_cat_pics() @property.setter int , .

Metrics :

 class Metrics(object): def __init__(self): self._metrics = { "func_calls": 0, "cat_pictures_served": 0, } @property def func_calls(self): return self._metrics["func_calls"] @property def cat_pictures_served(self): return self._metrics["cat_pictures_served"] def inc_func_calls(self): self._metrics["func_calls"] += 1 def inc_cat_pics(self): self._metrics["cat_pictures_served"] += 1 

ctypes


, - Python, CPython? ctypes , C. ctypes, Extending Python With C Libraries and the «ctypes» Module .

, , . - add_one() :

 void add_one(int *x) { *x += 1; } 

, x 1. , (shared) . , add.c , gcc:

 $ gcc -c -Wall -Werror -fpic add.c $ gcc -shared -o libadd1.so add.o 

C add.o . libadd1.so .

libadd1.so . ctypes Python:

 >>> import ctypes >>> add_lib = ctypes.CDLL("./libadd1.so") >>> add_lib.add_one <_FuncPtr object at 0x7f9f3b8852a0> 

ctypes.CDLL , libadd1 . add_one() , , Python-. , . Python , .

, ctypes :

 >>> add_one = add_lib.add_one >>> add_one.argtypes = [ctypes.POINTER(ctypes.c_int)] 

, C. , , :

 >>> add_one(1) Traceback (most recent call last): File "<stdin>", line 1, in <module> ctypes.ArgumentError: argument 1: <class 'TypeError'>: \ expected LP_c_int instance instead of int 

Python , add_one() , . , ctypes . :

 >>> x = ctypes.c_int() >>> x c_int(0) 

x 0 . ctypes byref() , .

: .

, . , .

add_one() :

 >>> add_one(ctypes.byref(x)) 998793640 >>> x c_int(1) 

! ممتاز 1. , Python .

استنتاج


Python . , Python.

Python:

  • .
  • Python- .
  • ctypes.

Python .

Source: https://habr.com/ru/post/ar454324/


All Articles