2.34. الشيفرة الديناميكية¶
ثلاث دوال مُدمجة تأخذ سلسلة نصية من شيفرة Python المصدرية وتشغّلها: eval() و exec() و compile(). وهي معًا تتيح للشيفرة أن تبني وتنفّذ شيفرة إضافية في وقت التشغيل -- وهو أحيانًا الأداة الصحيحة تمامًا، لكنه في الغالب الأعم مصدر للأخطاء والثغرات الأمنية.
تحذير
تنفّذ هذه الدوال شيفرة Python اعتباطية. وتمرير مدخلات المستخدم إلى eval أو exec -- سلسلة نصية من ملف يمكن للمستخدم تحريره، أو حمولة وردت عبر الشبكة، أو قيمة كُتبت عند موجّه REPL -- يتيح لتلك المدخلات أن تفعل أي شيء يمكن للبرنامج النصي المستدعي فعله، حتى حذف كل ملف على الجهاز. استخدمها بتعمّد، ولا تستخدمها أبدًا داخل شيفرة تعمل تلقائيًا، ولا على بيانات لا تتحكم فيها.
2.34.1. eval¶
تشغّل eval() تعبيرًا واحدًا وتُعيد قيمته:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
يرى التعبير المتغيرات العامة والمحلية للمستدعي افتراضيًا، ولهذا تُحَلّ name في المثال الثاني. وتمرير قواميس صريحة يتيح لك عزل التقييم في بيئة معزولة (sandbox):
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
حتى مع العزل، تبقى eval خطرة. فهناك تقنيات معروفة للهروب من مثل هذه البيئات المعزولة؛ لا تعتمد على حيلة __builtins__ الفارغة وحدها مع المدخلات غير الموثوقة.
2.34.2. exec¶
تشغّل exec() كتلة من الشيفرة بدلًا من تعبير واحد -- عبارات وتعريفات دوال وحلقات -- وتُعيد None:
exec("for i in range(3): print(i)")
الخرج:
0
1
2
يمكن للكتلة أن تعرّف أسماء تصبح متاحة بعد ذلك، مع بعض التحفظات حول النطاق المحلي مقابل العام. ونادرًا ما تتصرف exec داخل دالة بالطريقة التي يتوقعها الكاتب؛ فإن احتجت إليها، شغّلها على مستوى الوحدة.
2.34.3. compile¶
تحوّل compile() سلسلة نصية مصدرية إلى كائن شيفرة يمكن تمريره إلى eval() أو exec() لاحقًا. استخدمها عندما يُشغَّل المصدر نفسه مرات عديدة -- إذ يحدث التحليل مرة واحدة، ويكون التنفيذ أسرع:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
الخرج:
0
1
4
9
16
الوسيط الأوسط هو تسمية تظهر في تتبّعات الاستثناءات (tracebacks) إن أطلقت الشيفرة استثناءً. والوسيط الثالث هو "eval" لتعبير واحد، أو "exec" لكتلة، أو "single" لعبارة بأسلوب تفاعلي تطبع نتيجتها.
2.34.4. متى تلجأ إلى هذه الأدوات¶
نادرًا جدًا. فمعظم حالات الاستخدام التي يتخيلها المبتدئون لها بدائل أكثر أمانًا:
قراءة ملف إعدادات. استخدم
json-- بيانات منظّمة، بلا تنفيذ.تقييم قيمة عددية يكتبها المستخدم. استخدم
int()/float()للتحليل، ثم العمليات الحسابية. وإذا احتاج المستخدم حقًا إلى إدخال صيغة، فاستخدم مُحلِّل تعبيرات صغيرًا، لاeval.
عندما تحتاج فعلًا إلى eval / exec / compile، اعزل موضع الاستدعاء، وسجّل السلسلة النصية الدقيقة التي توشك على تنفيذها، وعامِل المصدر باعتباره أكثر شيء مريب في شيفرتك.