هل يمكنك إضافة عبارات جديدة إلى بنية Python؟



Answers

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

على سبيل المثال ، دعنا نقول أننا نريد تقديم عبارة "myprint" ، بدلاً من الطباعة على الشاشة بدلاً من ذلك ، سجِّل الدخول إلى ملف معين. أي:

myprint "This gets logged to file"

سيكون معادلا ل

print >>open('/tmp/logfile.txt','a'), "This gets logged to file"

هناك العديد من الخيارات حول كيفية إجراء الاستبدال ، من استبدال regex إلى إنشاء AST ، لكتابة المحلل اللغوي الخاص بك اعتمادًا على مدى قرب بناء الجملة الخاص بك يطابق python الموجودة. نهج وسيط جيد هو استخدام وحدة tokenizer. هذا يجب أن يسمح لك بإضافة كلمات رئيسية جديدة ، هياكل تحكم إلخ أثناء تفسير المصدر بشكل مشابه لمترجم python ، وبالتالي تجنب حلول تكسير الخام الخام قد يسبب. للحصول على "myprint" أعلاه ، يمكنك كتابة رمز التحويل التالي:

import tokenize

LOGFILE = '/tmp/log.txt'
def translate(readline):
    for type, name,_,_,_ in tokenize.generate_tokens(readline):
        if type ==tokenize.NAME and name =='myprint':
            yield tokenize.NAME, 'print'
            yield tokenize.OP, '>>'
            yield tokenize.NAME, "open"
            yield tokenize.OP, "("
            yield tokenize.STRING, repr(LOGFILE)
            yield tokenize.OP, ","
            yield tokenize.STRING, "'a'"
            yield tokenize.OP, ")"
            yield tokenize.OP, ","
        else:
            yield type,name

(هذا يجعل myprint كلمة رئيسية بشكل فعال ، لذا فإن الاستخدام كمتغير في مكان آخر سيسبب مشاكل على الأرجح)

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

import new
def myimport(filename):
    mod = new.module(filename)
    f=open(filename)
    data = tokenize.untokenize(translate(f.readline))
    exec data in mod.__dict__
    return mod

هذا يتطلب منك التعامل مع التعليمات البرمجية المخصصة بشكل مختلف عن وحدات الثعبان العادية. بمعنى " some_mod = myimport("some_mod.py") " بدلاً من " import some_mod "

حل آخر أنيق إلى حد ما (وإن كان على ما يرام) هو إنشاء تشفير مخصص (انظر PEP 263 ) كما توضح this الوصفة. يمكنك تنفيذ ذلك على النحو التالي:

import codecs, cStringIO, encodings
from encodings import utf_8

class StreamReader(utf_8.StreamReader):
    def __init__(self, *args, **kwargs):
        codecs.StreamReader.__init__(self, *args, **kwargs)
        data = tokenize.untokenize(translate(self.stream.readline))
        self.stream = cStringIO.StringIO(data)

def search_function(s):
    if s!='mylang': return None
    utf8=encodings.search_function('utf8') # Assume utf8 encoding
    return codecs.CodecInfo(
        name='mylang',
        encode = utf8.encode,
        decode = utf8.decode,
        incrementalencoder=utf8.incrementalencoder,
        incrementaldecoder=utf8.incrementaldecoder,
        streamreader=StreamReader,
        streamwriter=utf8.streamwriter)

codecs.register(search_function)

الآن بعد تشغيل هذا الكود (على سبيل المثال ، يمكنك وضعه في .pythonrc أو site.py) سيتم تلقائيًا ترجمة أي كود يبدأ بالتعليق "# coding: mylang" من خلال خطوة المعالجة السابقة. على سبيل المثال.

# coding: mylang
myprint "this gets logged to file"
for i in range(10):
    myprint "so does this : ", i, "times"
myprint ("works fine" "with arbitrary" + " syntax" 
  "and line continuations")

المحاذير:

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

Question

هل يمكنك إضافة عبارات جديدة (مثل print ، raise ، with ) إلى بناء جملة بايثون؟

قل ، للسماح ..

mystatement "Something"

أو،

new_if True:
    print "example"

ليس كثيرًا إذا كان يجب عليك ذلك ، بل بالأحرى إذا كان ذلك ممكنًا (قصراً في تعديل شفرة مترجم الشفرات)




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

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

ومع ذلك ، لست متأكدًا من سبب رغبتك في ذلك. تجعل ميزات بيثون الموجهة للكائنات من السهل جدًا تحقيق نتائج مشابهة مع اللغة كما هي.




إنه لا يضيف عبارات جديدة إلى صيغة اللغة بالضبط ، لكن وحدات الماكرو هي أداة قوية: https://github.com/lihaoyi/macropy




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




من الممكن القيام بذلك باستخدام EasyExtend :

EasyExtend (EE) هو مولد preprocessor وإطار metaprogramming مكتوب في بيثون نقية ومتكاملة مع CPython. الغرض الرئيسي من EasyExtend هو إنشاء لغات الإمتداد أي إضافة بناء جملة مخصصة وعلم الدلالة إلى Python.




إليك طريقة بسيطة جدًا ولكنها كريبة لإضافة عبارات جديدة ، في الوضع التفسري فقط . أنا أستخدمه لأوامر 1-حرف صغيرة لتحرير التعليقات الجينية باستخدام sys.displayhook فقط ، ولكن فقط حتى أتمكن من الإجابة على هذا السؤال أضفت sys.excepthook للأخطاء النحوية كذلك. هذا الأخير هو حقا قبيحة ، وجلب رمز الخام من المخزن المؤقت للقراءة. الفائدة هي أنه من السهل إضافة عبارات جديدة بهذه الطريقة.


jcomeau@intrepid:~/$ cat demo.py; ./demo.py
#!/usr/bin/python -i
'load everything needed under "package", such as package.common.normalize()'
import os, sys, readline, traceback
if __name__ == '__main__':
    class t:
        @staticmethod
        def localfunction(*args):
            print 'this is a test'
            if args:
                print 'ignoring %s' % repr(args)

    def displayhook(whatever):
        if hasattr(whatever, 'localfunction'):
            return whatever.localfunction()
        else:
            print whatever

    def excepthook(exctype, value, tb):
        if exctype is SyntaxError:
            index = readline.get_current_history_length()
            item = readline.get_history_item(index)
            command = item.split()
            print 'command:', command
            if len(command[0]) == 1:
                try:
                    eval(command[0]).localfunction(*command[1:])
                except:
                    traceback.print_exception(exctype, value, tb)
        else:
            traceback.print_exception(exctype, value, tb)

    sys.displayhook = displayhook
    sys.excepthook = excepthook
>>> t
this is a test
>>> t t
command: ['t', 't']
this is a test
ignoring ('t',)
>>> ^D




Related