Python: aveți o funcție definită de utilizator ca intrare, păstrând în același timp codul sursă inaccesibil?

Trebuie să scriu o piesă de software care să ia o funcție definită de utilizator (scrisă în Python) ca intrare.

Funcția definită de utilizator ia un vector de numere și returnează un vector de numere. Software-ul meu va apela această funcție de utilizator de mai multe ori (în același mod în care ar face o căutare de bază) și apoi va întoarce un rezultat.

Codul sursă al soft-ului meu va fi scris în Python (va folosi un * .pyd) sau în C ++ și trebuie ascuns de utilizator.

Care este cel mai bun mod (dacă există ...) pentru a realiza acest lucru? Ideal ar fi ca codul meu python să fie compilat în * .exe și utilizatorul să copieze și să-și insereze funcția într-o casetă de text, însă utilizarea lui de la interpretorul python ar trebui să fie acceptabilă.

2
@Levon: soluția evidentă este aceea de a crea un interpret micro-python, care ar trebui să fie posibil deoarece funcția utilizatorului va conține asignări, operații artemice, comparații și numai pentru bucle (sau așa sper); dar aceasta este o soluție foarte consumatoare de timp și incomod. Sper să fiu capabil să obțin ceva mai bun de la Python.
adăugat autor Yulia V, sursa
@ Stephan: Nu am încredere în utilizatorii :) dar nu am de ales - trebuie să ai încredere în ei pentru a scrie această funcție. De asemenea, aceasta este o cerință de management ...
adăugat autor Yulia V, sursa
Dacă este o cerință de management, probabil că nu este o cerință funcțională.
adăugat autor Pablo Ariel, sursa
Deci, aveți încredere în utilizatorii dvs. atât de mult încât să executați codul arbitrar de la ei, dar nu aveți încredere în ei atât de mult încât codul dvs. trebuie să fie complet inaccesibil?
adăugat autor Steven Rumbalski, sursa

1 răspunsuri

Iată un exemplu foarte limitat care arată cum ați putea să o faceți - Desigur, există câteva limitări aici - În principal, acest lucru funcționează numai dacă utilizatorul introduce doar o singură funcție. Dacă șirul pe care îl scriu arată mai mult:

a='garbage'
def foo():pass

sau chiar:

def bar():
    return foobar()

def foobar():
    return "foobar is a cool word, don't you think?"

atunci nu ai noroc. (Cu alte cuvinte, aceasta presupune că utilizatorul adaugă doar un singur domeniu spațiului de nume al funcției run_user). Desigur, ați putea verifica și ridicați o excepție sau orice dacă se dovedește că utilizatorul a adăugat prea mult ... Puteți, de asemenea, returnați funcția și utilizați-o așa cum a propus gauden.

def run_user(S):
    #S is the user's function as a string.
    lvars=None #make sure the name is in locals()
    lvars=set(locals())
    exec(S)  #exec isn't usually a good idea -- but I guess you're a very trusting person.
    usr_namespace=list(set(locals())-lvars)
    usr_func_name=usr_namespace[0]
    if(len(usr_namespace)>1):
        raise ValueError("User input too much into the namespace!")

    usr_func=locals()[usr_func_name]
    usr_func()  #comment this out if you don't want to run the function immediately
    return usr_func

usr_string="""
def foo():
     a="Blah"
     print "Hello World! "+a
"""

func_handle=run_user(usr_string)  #prints "Hello World! Blah"
#and to demonstrate that we can pass a handle to the function around:...
func_handle() #prints "Hello World! Blah" again.  

Rețineți că ați putea face acest lucru puțin mai sigur folosind codul exec al python 3 sau execfile din Python 3 unde ați putea limita spațiul de nume al funcției utilizatorului prin trecerea dicționarului '__builtins __': Niciuna} ca dicționar global

#python3.x
allowed=vars(__builtins__).copy()
allowed['__import__']=None
exec("import os",{'__builtins__':None},allowed)  #raises ImportError
exec("print(abs(-4))",{'__builtins__':None},allowed) #prints 4 as you'd expect.

M-aș aștepta ca același lucru să funcționeze cu execfile în cadrul python2.x cu condiția să fi scris șirul într-un fișier temporar ...

EDIT (to address the comments below)

Exemplul pe care îl oferiți cu eval se poate face mai simplu:

a=5
b=eval('a+5')  #b == 10

Cu toate acestea, nu este ceea ce ați cerut. Ceea ce ați cerut a fost că utilizatorul ar putea scrie o funcție , de exemplu:

def f(a):
    return a+5

Primul caz va funcționa, dar utilizatorul trebuie să știe că numele variabilei este "a".

a=5
b=eval('x+5') #won't work -- x isn't defined

Ei trebuie, de asemenea, să știe cum să adăugați vectorii - (Dacă utilizați tablouri ample care sunt banale, dar am crezut că o voi menționa doar în cazul în care nu sunteți). Și nu pot face expresii complexe (expresii lungi folosind mai multe condiționalități, bucle, etc.), fără o muncă decentă și zgârierea capului.

Ultimul caz este puțin mai bun (în opinia mea), deoarece este mult mai general. Puteți obține funcția folosind metoda pe care am descris-o (eliminând partea de acolo unde funcționez efectiv funcția) și utilizatorul poate folosi orice nume de variabilă pe care îl doresc - atunci le folosiți doar funcția. De asemenea, ele pot face lucruri precum buclele și pot folosi expresii mult mai complexe decât ați putea face într-o singură linie cu eval . Singurul lucru pe care îl plătiți pentru acest lucru este că utilizatorul trebuie să scrie def func (...): și return some_value la sfârșitul lui care, dacă știu, fi complet intuitiv.

ss="""
def foo(x):
    return 5+x
"""

a=5
func=run_user(ss)
result=func(a)     #result = 10

Acest lucru are și avantajul că șirul nu trebuie să fie re-analizat de fiecare dată când doriți să apelați funcția. Odată ce ai func , poți să o folosești totuși ori ori de câte ori vrei. De asemenea, rețineți că, cu soluția mea, nici măcar nu trebuie să știți numele funcției definite de utilizator. Odată ce ați obiect al funcției, numele este irelevant.

3
adăugat
Vă mulțumim pentru răspunsurile dvs.! Doar verificat manualul python (nu știu de ce nu am făcut-o înainte ...) pentru a afla mai multe despre exec() și a găsit un exemplu în partea de jos a docs.python.org/library/… care este foarte asemănător cu ideea lui mgilson, dar permite evitarea localnicilor ). Nu a încercat încă, dar arată promițător. Multumesc din nou.
adăugat autor Yulia V, sursa
>>> parser import >>> st = parser.expr ('a + 5') >>> cod = st.compile ('file.py') >>> a = 5 >>> eval (code) că eval() returnează valoarea funcției care rezultă din parsare
adăugat autor Yulia V, sursa
@mgilson Credeți că acest lucru va funcționa sau există vreun dezavantaj ascuns? Am folosit Python timp de 3 săptămâni sau nu, nu pot fi siguri.
adăugat autor Yulia V, sursa
@gauden: Mulțumesc - Deși aș fi sugerat să treci un mâner la funcția în jurul valorii de dacă nu ar fi avut deja. Cred că văzând prima mea reacție am făcut-o să mă gândesc puțin mai mult la problemă. Și, în cele din urmă, cred că întoarcerea funcției și trecerea acesteia după cum ați propus este probabil cea mai bună idee ... Întrebarea dificilă a fost: "Cum obțin un rol în funcție?" ... Apoi a devenit, cum pot să fac acest lucru un pic mai în siguranță. De asemenea, cred că acest lucru ar putea fi curățat puțin ... dar la un moment dat nu mai merită;).
adăugat autor mgilson, sursa
@YuccaV: Nu sunt complet sigur că puteți să scăpați fără apel la localnici sau vars sau altă formă de introspecție. La un anumit nivel, trebuie să aveți posibilitatea de a obține un mâner pe funcție, astfel încât să puteți folosi. (compilați nu vă va da mânerul - doar un obiect adecvat pentru exec mai târziu). Dacă reușiți să faceți acest lucru fără a apela localnicilor - mă interesează să-l văd. Asigurați-vă că îl postați și spuneți-mi.
adăugat autor mgilson, sursa
@YuccaV: De asemenea, rețineți că setul set (local() .sub ()) este identic cu setul (locals ()) < pic).
adăugat autor mgilson, sursa
@YuccaV: Am editat (sperăm) adresa dvs. de întrebare.
adăugat autor mgilson, sursa
+1 aceasta este o soluție superioară pentru mine.
adăugat autor gauden, sursa
:) Ce pot spune, @mgilson? Voi șterge oricum răspunsul meu și voi lăsa această idee să rezumați în propriul tău comentariu - să treci un mâner într-o funcție - pentru ca tu să adaugi o editură în a ta, dacă consideri potrivită.
adăugat autor gauden, sursa
Python România
Python România
100 participanți

Comunitatea pasionaților de Python din România.