exec() - Code funktioniert nicht in Funktion

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
JonSnow
User
Beiträge: 8
Registriert: Montag 27. September 2021, 12:12

Hallo Ihr Lieben :)

ich bin gerade dabei Python zu lernen und bin auf ein Problem gestoßen, das ich nicht beheben kann. Daher dachte ich, ich wende mich an Euch :)
Das Programm erstellt eine Liste zufälliger Länge und beinhaltet zufällige Ganzzahlwerte (zwischen 1 und 100).

Der Anwender kann nun entscheiden, ob diese Werte addiert, subtrahiert, multipliziert oder dividiert werden sollen. Um zu vermeiden, dass ich vier Unterfunktionen einrichten muss, habe ich hier mit exec() gearbeitet. Solange ich den Code nicht in eine Funktion presse, funktioniert alles prima. Sobald ich aber eine Funktion außenrum spanne, funktioniert exec() offensichtlich nicht mehr.

- Woran liegt das?
- Was muss ich machen, dass das auch innerhalb einer Funktion richtig funktioniert?
- Kann exec() ein Sicherheitsrisiko werden und ist eher von der Benutzung abzuraten?

Vielen Dank im Voraus für die vielen Antworten ;)

Euer Jon Snow

..:: Ab hier der Code ::..

Folgender Code funktioniert ohne Mullen und Knullen

Code: Alles auswählen

import random
#Liste mit beliebligen Zufallszahlen anlegen und deren Mittelwert berechnen
liste = []
anzElemente = random.randint(5,12)
for m in range(0,anzElemente):
    liste.append(random.randint(1,100))

# Debugzwecke
print (liste)

# Übergabe der mathematischen Operation
mathOperator = input("Bitte geben Sie den mathematischen Operator an: ")
if mathOperator == "/" or mathOperator == "*":
    ergebnis = 1
elif mathOperator == "+" or mathOperator == "-":
    ergebnis = 0
else:
    print ("Falsche Eingabe")
    quit()

# Berechnung
formel = "ergebnis = ergebnis" + mathOperator + "n"
for n in liste:
    exec(formel)
print (ergebnis)

Der exakt selbe Code innerhalb einer Funktion, funktioniert nicht mehr

Code: Alles auswählen

def rechnen():
    import random
    #Liste mit beliebligen Zufallszahlen anlegen und deren Mittelwert berechnen
    liste = []
    anzElemente = random.randint(5,12)
    for m in range(0,anzElemente):
        liste.append(random.randint(1,100))
    #debug Zwecke
    print (liste)
    
    # Übergabe der mathematischen Operation
    mathOperator = input("Bitte geben Sie den mathematischen Operator an: ")
    if (mathOperator == "/") or (mathOperator) == "*":
        ergebnis = 1
    elif (mathOperator == "+") or (mathOperator == "-"):
        ergebnis = 0
    else:
        print ("Falsche Eingabe")
        quit()

    # Berechnung
    formel = "ergebnis = ergebnis" + mathOperator + "n"
    for n in liste:
        exec(formel)
    return ergebnis

print(rechnen())
"Alles ist Eins, außer der Null"- Wau Holland
einfachTobi
User
Beiträge: 491
Registriert: Mittwoch 13. November 2019, 08:38

exec() sollte nicht verwendet werden. Der Nutzer kann beliebigen Code eingeben und ausführen lassen. Beispiel:

Code: Alles auswählen

a = 0
b = 2
eingabe = "; import os; print(os.listdir()); "  # das ist die Nutzereingabe
formel  = "a = a" + eingabe + "b"
exec(formel)
Hier wird nur der Inhalt des Verzeichnisses ausgegeben - die Möglichkeiten wären natürlich vielfältig.
Das ist zwar bei deinem Code nicht direkt möglich, aber der ist ja auch noch überschaubar. Sobald man komplexeren Code schreibt, kann einem ein solcher Fall durchrutschen und dann ist eben die Sicherheitslücke geboren.

Verwende eher ein Wörterbuch und operator. Ergänzend: Verwende keine unnötigen Abkürzungen für Namen. Variablen werden klein_mit_unterstrich geschrieben. Am besten halte dich an PEP8.
JonSnow
User
Beiträge: 8
Registriert: Montag 27. September 2021, 12:12

(Ich weiß nicht, ob meine Antwort auch moderiert wird, es kam leider kein entsprechender Hinweis. Die Antwort die ich über Schnellantworten verfasst habe, ist nach dem Absenden offensichtlich im Nirvana versunken. Falls es doch an der Moderation liegen sollte, so bitte ich darum, dass diese Nachricht gelöscht wird. Möchte nicht für Dubletten verantwortlich sein)

Hallo Tobi,

vielen Dank für die sehr schnelle Antwort und den Hinweis zu Dict und operator :) Das Sicherheitsrisiko über die Eingabe von Code des Users ist einleuchtend. Ich werde diese Funktion nicht oder nur ohne Manipulationsmöglichkeiten seitens des Anwenders nutzen.

Trotz allem bleibt die Frage: Warum funktioniert der Code in main aber nicht in einer Funktion?

Lieben Gruß,

JonSnow aka einfachDaniel ;)
"Alles ist Eins, außer der Null"- Wau Holland
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

- Woran liegt das?
weil `exec` im lokalen Namespace ausgeführt wird, das aber von lokalen Funktionen aus Optimierungsgründen nicht unterstützt wird.

- Was muss ich machen, dass das auch innerhalb einer Funktion richtig funktioniert?
man muß die Namespaces passenden setzen.

- Kann exec() ein Sicherheitsrisiko werden und ist eher von der Benutzung abzuraten?
Ja, großes Sicherheitsrisiko -> nicht benutzen.


Sonstige Anmerkungen: Importe stehen immer am Anfang der Datei, nicht in irgendwelchen Funktionen. Variablen werden generell komplett klein geschrieben und enthalten keine Abkürzungen: anzahl_elemente.
`quit` ist keine offizielle Funktion. Sollte auch nicht innerhalb einer Funktion benutzt werden. Ein Programm endet, wenn die `main`-Funktion verlassen wird.

Da die mathematische Operation für jeden Operator so unterschiedlich ist, lohnt es sich nicht, da was komplizierteres als ein einfaches if-elif zu benutzen:

Code: Alles auswählen

import random
from math import prod

def rechnen(math_operator):
    #Liste mit beliebligen Zufallszahlen anlegen und deren Mittelwert berechnen
    anzahl_elemente = random.randint(5,12)
    zahlen = [
        random.randint(1,100)
        for _ in range(anzahl_elemente)
    ]
    if math_operator == "/":
        return 1 / prod(zahlen)
    elif math_operator == "*":
        return prod(zahlen)
    elif math_operator == "+":
        return sum(zahlen)
    elif math_operator == "-":
        return -sum(zahlen)
    raise ValueError(math_operator)

def main():
    math_operator = input("Bitte geben Sie den mathematischen Operator an: ")
    if math_operator not in ['/', '*', '+', '-']:
        print("Falsche Eingabe")
        return
    print(rechnen(math_operator))

if __name__ == "__main__":
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

So unterschiedlich finde ich die Operationen nicht. Die Unterscheiden sich doch nur durch den Operator und den Startwert.

Code: Alles auswählen

#!/usr/bin/env python3
from functools import reduce
from operator import add, mul, sub, truediv
from random import randint

OPERATOR_ZU_FUNKTION_UND_STARTWERT = {
    "*": (mul, 1),
    "+": (add, 0),
    "-": (sub, 0),
    "/": (truediv, 1),
}


def main():
    werte = [randint(1, 100) for m in range(randint(5, 12))]
    print(werte)

    operator = input("Bitte geben Sie den mathematischen Operator an: ")
    if operator in OPERATOR_ZU_FUNKTION_UND_STARTWERT:
        funktion, startwert = OPERATOR_ZU_FUNKTION_UND_STARTWERT[operator]
        print(reduce(funktion, werte, startwert))
    else:
        print("Falsche Eingabe")


if __name__ == "__main__":
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
JonSnow
User
Beiträge: 8
Registriert: Montag 27. September 2021, 12:12

Hallo Sirius3,

vielen Dank auch Dir für die schnelle und ausführliche Antwort inklusive Codebeispiel. Die mathematische Bibliothek sollen wir für diese Aufgabe nicht benutzen. Deswegen die umständliche Aufmachung. Ich hatte es zunächst alles in elif gepackt und kam dann auf die Idee, dass ich die Formel auch anpassen könnte. So kam ich auf exec()

Genial finde ich:

Code: Alles auswählen

    zahlen = [
        random.randint(1,100)
        for _ in range(anzahl_elemente)
    ]
Danke dafür!


Ebenso das 'not in'. Sehr viel hübscher als die Boolean Kolonne ;) Das Return, um die Funktion im Fehlerfall zu verlassen, ist auch eine gute Möglichkeit!

Code: Alles auswählen

    if math_operator not in ['/', '*', '+', '-']:
        print("Falsche Eingabe")
        return
[code]

Auch das hier ist eine praktische Sache. Dafür gleich nochmal ein dickes Dankeschön
[code]
if __name__ == "__main__":
    main()
Wie ich jetzt allerdings die Namespaces anpassen kann, damit exec() auch innerhalb einer Funktion läuft, bleibt mir (noch) ein Rätsel. Aber aller Anfang ist schwer :D Wird schon werden. Da ich exec() innerhab der Funktion aufrufe und auch die Variablen, die ich in der Formel nutze, nur innerhalb der Funktion laufen, weiß ich nicht wo ich da jetzt noch Anpassungen im Namespace vornehmen kann/sollte. Die Variablen werden außerhalb der Funktion nicht verwendet.

Ich habe jetzt durch zwei Antworten tatsächlich einges an neuem Wissen aufnehmen können, das finde ich richtig klasse. Danke. Meine Anmeldung hat sich schon gelohnt.

Die Konventionen PEP 8 habe ich studiert und werde mich daran halten. Sollte Euch da noch was auffallen, dann wäre ich für Hinweise immer dankbar :)

Lieben Gruß,

JohnSnow
"Alles ist Eins, außer der Null"- Wau Holland
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Lösung verrate ich ja absichtlich nicht, nicht dass Du noch auf die Idee kommst, das tatsächlich einzusetzen.
JonSnow
User
Beiträge: 8
Registriert: Montag 27. September 2021, 12:12

Hi einfachTobi, sirius3 und __blackjack__,

so jetzt habe ich meine Python Klausur geschrieben. Eine glatte Eins, was will man mehr. Ich wollte mich noch mal bei Euch bedanken für die schnellen Antworten. Besser geht's nicht. Und vielen Dank @sirius3, dass Du mir das nicht verrätst. Finde ich gut :) Auch wenn meine direkt Frage nicht beantwortet wurde, konnte ich sehr viel aus Euren Antworten ziehen!

Ich werde diesem Forum treu bleiben :)

Lieben Gruß und ein schönes Wochenende wünscht Euch,

JonSnow
"Alles ist Eins, außer der Null"- Wau Holland
Antworten