تشغيل كود بايثون الموجود في سلسلة
سؤال
أنا أكتب محرك لعبة باستخدام pygame وbox2d، وفي منشئ الشخصيات، أريد أن أكون قادرًا على كتابة الكود الذي سيتم تنفيذه في أحداث keydown.
كانت خطتي هي أن يكون لدي محرر نصوص في أداة إنشاء الشخصيات يتيح لك كتابة تعليمات برمجية مشابهة لما يلي:
if key == K_a:
## Move left
pass
elif key == K_d:
## Move right
pass
سأقوم باسترداد محتويات محرر النصوص كسلسلة، وأريد تشغيل الكود بطريقة في طريقة الأحرف هذه:
def keydown(self, key):
## Run code from text editor
ما هي أفضل طريقة للقيام بذلك؟
المحلول
يمكنك استخدام ال eval(string)
طريقة للقيام بذلك.
تعريف
eval(code, globals=None, locals=None)
الكود هو مجرد كود بايثون قياسي - وهذا يعني أنه لا يزال بحاجة إلى وضع مسافة بادئة بشكل صحيح.
يمكن أن يكون لدى العوالم عادة __builtins__
محددة، والتي يمكن أن تكون مفيدة لأغراض أمنية.
مثال
eval("print('Hello')")
سوف طباعة hello
إلى وحدة التحكم.يمكنك أيضًا تحديد المتغيرات المحلية والعالمية للكود المراد استخدامه:
eval("print('Hello, %s'%name)", {}, {'name':'person-b'})
مخاوف أمنية
كن حذرا، رغم ذلك.سيتم تنفيذ أي إدخال المستخدم.يعتبر:
eval("import os;os.system('sudo rm -rf /')")
هناك عدد من الطرق للتغلب على ذلك.الأسهل هو القيام بشيء مثل:
eval("import os;...", {'os':None})
مما سيؤدي إلى استثناء، بدلاً من محو القرص الصلب الخاص بك.على الرغم من أن برنامجك يعمل على سطح المكتب، فقد يكون هذا مشكلة إذا قام الأشخاص بإعادة توزيع البرامج النصية، وهو ما أتصور أنه مقصود.
مثال غريب
وهنا مثال على استخدام eval
الغريب إلى حد ما:
def hello() : print('Hello')
def world() : print('world')
CURRENT_MOOD = 'happy'
eval(get_code(), {'contrivedExample':__main__}, {'hi':hello}.update(locals()))
ما يفعله هذا على خط التقييم هو:
- يعطي الوحدة الحالية اسمًا آخر (يصبح
contrivedExample
إلى البرنامج النصي).يمكن للمستهلك الاتصالcontrivedExample.hello()
الآن.) - إنه يحدد
hi
كما يشير إلىhello
- لقد قام بدمج هذا القاموس مع قائمة العالميات الحالية في وحدة التنفيذ.
يفشل
اتضح (شكرًا للمعلقين!) أنك تحتاج بالفعل إلى استخدام ملف exec
إفادة.عفوا كبيرة.الأمثلة المعدلة هي كما يلي:
exec
تعريف
(هذا يبدو مألوفًا!) Exec عبارة عن بيان:
exec "code" [in scope]
حيث النطاق هو قاموس لكل من المتغيرات المحلية والعالمية.إذا لم يتم تحديد ذلك، فسيتم تنفيذه في النطاق الحالي.
الكود هو مجرد كود بايثون قياسي - وهذا يعني أنه لا يزال بحاجة إلى وضع مسافة بادئة بشكل صحيح.
exec
مثال
exec "print('hello')"
سوف طباعة hello
إلى وحدة التحكم.يمكنك أيضًا تحديد المتغيرات المحلية والعالمية للكود المراد استخدامه:
eval "print('hello, '+name)" in {'name':'person-b'}
exec
مخاوف أمنية
كن حذرا، رغم ذلك.سيتم تنفيذ أي إدخال المستخدم.يعتبر:
exec "import os;os.system('sudo rm -rf /')"
طباعة البيان
كما لاحظ المعلقون أيضًا ، print
هو بيان في كافة إصدارات بايثون قبل 3.0.في 2.6، يمكن تغيير السلوك عن طريق الكتابة from __future__ import print_statement
.خلاف ذلك، استخدم:
print "hello"
بدلاً من :
print("hello")
نصائح أخرى
وكما أشار آخرون، يمكنك تحميل النص في سلسلة واستخدام exec "codestring"
. إذا الواردة في ملف بالفعل، وذلك باستخدام execfile سوف تجنب الحاجة إلى تحميله.
واحد ملاحظة الأداء: يجب تجنب execing رمز عدة مرات، وتحليل وتجميع مصدر الثعبان هو عملية بطيئة. بمعنى آخر. لم يكن لديك:
def keydown(self, key):
exec user_code
ويمكنك تحسين هذا قليلا من خلال تجميع المصدر إلى كائن الرمز (مع compile()
وإكسيك ذلك، أو أفضل، عن طريق إنشاء وظيفة منك ان تبقي حولها، وبناء مرة واحدة فقط. اما تتطلب من المستخدم لكتابة "صفر my_handler (وسائط ...) "، أو إلحاقها ذلك بنفسك، وتفعل شيئا مثل:
user_source = "def user_func(args):\n" + '\n'.join(" "+line for line in user_source.splitlines())
d={}
exec user_source in d
user_func = d['user_func']
وبعد ذلك في وقت لاحق:
if key == K_a:
user_func(args)
ويمكنك استخدام eval()
وحدة التقييم أو إكسيك. يجب عليك بالتأكيد قراءة بيثون إشارة المكتبة قبل البرمجة.