Path: csiph.com!usenet.pasdenom.info!aioe.org!news.stack.nl!newsfeed.xs4all.nl!newsfeed4.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.000 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'scripts': 0.03; 'ideally': 0.04; 'cpython': 0.05; 'explicitly': 0.05; 'subject:Python': 0.06; '"""': 0.07; '(python': 0.07; '(using': 0.07; 'automate': 0.07; 'definitions': 0.07; 'rename': 0.07; "subject:' ": 0.07; 'boundaries.': 0.09; 'classes.': 0.09; 'defines': 0.09; 'exec': 0.09; 'function,': 0.09; 'functioning': 0.09; 'separately': 0.09; 'api': 0.11; 'cc:addr:python-list': 0.11; 'python': 0.11; 'def': 0.12; 'stored': 0.12; 'changes': 0.15; '23,': 0.16; 'api,': 0.16; 'dictionary,': 0.16; 'dictionary.': 0.16; 'from:addr:rosuav': 0.16; 'from:name:chris angelico': 0.16; 'globals': 0.16; 'it),': 0.16; 'mutable': 0.16; 'pyqt,': 0.16; 'reasonably': 0.16; 'reload': 0.16; 'retained': 0.16; 'skip:{ 40': 0.16; 'stuff!': 0.16; 'thoughts?': 0.16; 'subject:python': 0.16; 'wrote:': 0.18; 'code.': 0.18; 'bit': 0.19; 'app': 0.19; 'module': 0.19; 'patrick': 0.19; '>>>': 0.22; 'appears': 0.22; 'example': 0.22; 'separate': 0.22; 'cc:addr:python.org': 0.22; 'instance,': 0.24; 'module,': 0.24; '(or': 0.24; 'cc:2**0': 0.24; 'sort': 0.25; 'source': 0.25; 'script': 0.25; 'equivalent': 0.26; '(for': 0.26; 'defined': 0.27; 'gets': 0.27; 'header:In-Reply-To:1': 0.27; 'idea': 0.28; 'skip:g 30': 0.30; 'message-id:@mail.gmail.com': 0.30; 'code': 0.31; 'complete,': 0.31; 'easy,': 0.31; 'object.': 0.31; 'run': 0.32; 'could': 0.34; 'something': 0.35; 'done.': 0.35; 'but': 0.35; 'received:google.com': 0.35; 'there': 0.35; 'version': 0.36; 'c++': 0.36; 'functions.': 0.36; 'doing': 0.36; 'should': 0.36; 'skip:o 20': 0.38; 'hat': 0.38; 'nov': 0.38; 'handle': 0.38; 'pm,': 0.38; 'skip:p 20': 0.39; 'users': 0.40; 'how': 0.40; 'around.': 0.60; 'new': 0.61; "you're": 0.61; 'first': 0.61; 'complete': 0.62; 'reach': 0.63; 'name': 0.63; 'soon': 0.63; 'provide': 0.64; 'kept': 0.65; 'note:': 0.66; 'disappear': 0.84; 'it\xe2\x80\x99s': 0.84; 'off,': 0.84; 'pike': 0.84; 'absolutely': 0.87; 'to:none': 0.92 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=mime-version:in-reply-to:references:date:message-id:subject:from:cc :content-type:content-transfer-encoding; bh=2MJNSKppD7nNewI9urYmUIquqP15Rly8yLcx0pOWy6c=; b=NR2TDqcF7TY02VTraSKw42ka4LkboZZuwnyQ45mUC3EmMUqRTbNMwo8TkWyGnqFtH7 0N9Qiu3tZl5sToDpgTZizWAv/hp+MwoJp39cfLGK8DTW3Lr6QffP2JRRzYPYZu0xoSkw 8GB1CPd4UI5M5zv2YEfD3Q7k3j/q6ypcQwFa8TJ3vbCRqmA6bR/NDLd7WeAz1Ed/8j2H ljgnh2YiFjQU2Wf7k8+l85YLH2Tvha5cGrpptw7NHzXhW42pp9k6FGqun2eQoAUtJ/96 qkH5KNQ/Y4byicqARh5leefevt2w0NOTmck/k4Hk1iH8dqyQJ6sOWEtpByoKom6t4q2Q mKag== MIME-Version: 1.0 X-Received: by 10.107.17.79 with SMTP id z76mr12589327ioi.27.1416734676370; Sun, 23 Nov 2014 01:24:36 -0800 (PST) In-Reply-To: <05D7659F-9D0B-466B-833E-8454DCF0F57A@gmail.com> References: <05D7659F-9D0B-466B-833E-8454DCF0F57A@gmail.com> Date: Sun, 23 Nov 2014 20:24:36 +1100 Subject: Re: Embedded python 'scripting engine' inside Python app From: Chris Angelico Cc: "python-list@python.org" Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: quoted-printable X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 60 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1416734680 news.xs4all.nl 2907 [2001:888:2000:d::a6]:59434 X-Complaints-To: abuse@xs4all.nl Xref: csiph.com comp.lang.python:81350 On Sun, Nov 23, 2014 at 4:48 PM, Patrick Stinson wr= ote: > I am writing a python app (using PyQt, but that=E2=80=99s not important h= ere), and want my users to be able to write their own scripts to automate t= he app=E2=80=99s functioning using an engine API hat I expose. I have exten= sive experience doing this in a C++ app with the CPython api, but have no i= dea how to do this outside of calling exec() from with in Python :) > > Ideally their script would compile when the source changes and retain it= =E2=80=99s state and respond to callbacks from the api object. It appears t= his won=E2=80=99t work with exec() because the script=E2=80=99s definitions= and state disappear as soon as the exec() call is complete, and the script= doesn=E2=80=99t seem to be able to access it=E2=80=99s own defined functio= ns and classes. > > Thoughts? Fun stuff! First off, a cautionary note: Security-wise, this is absolutely equivalent to your users editing your source code. Be aware that you're giving them complete control. What you should be able to do is exec the script in a specific global dictionary. Here's some example code (Python 3.4): >>> script =3D """ def init(): print("Initializing") def on_some_event(status): trigger_some_action("Status is now "+status) """ >>> def trigger_some_action(msg): print("Action triggered.",msg) >>> globl =3D {"trigger_some_action":trigger_some_action} >>> exec(script,globl) >>> globl["init"]() Initializing >>> globl["on_some_event"]("Something happened") Action triggered. Status is now Something happened You can provide globals like this, or you can create an importable module for the scripts to call on. (Or both. Create a module, and pre-import it automatically.) The script defines functions with specific names and/or calls your functions to register hooks; you can reach into the globals to trigger functions. One way to handle updates to the code would be to exec it in the same globals dictionary. That has its complexities (for instance, if you rename a function, the old version will still exist under the old name unless you explicitly del it), but it can be very convenient. Alternatively, you could have the new version run in a new dictionary, but important state can be kept in separate dictionaries. That's how I manage things with a Pike program - all code gets reloaded cleanly, but retained state is stored separately in a mutable object that gets passed around. There are several ways this sort of thing can be done. It's reasonably easy, as long as you take a bit of care across the reload boundaries. ChrisA