Warum ist die Verwendung von 'eval' eine schlechte Praxis?

137

Ich verwende die folgende Klasse, um Daten meiner Songs einfach zu speichern.

class Song:
    """The class to store the details of each song"""
    attsToStore=('Name', 'Artist', 'Album', 'Genre', 'Location')
    def __init__(self):
        for att in self.attsToStore:
            exec 'self.%s=None'%(att.lower()) in locals()
    def setDetail(self, key, val):
        if key in self.attsToStore:
            exec 'self.%s=val'%(key.lower()) in locals()

Ich bin der Meinung, dass dies viel erweiterbarer ist als das Ausschreiben eines if/elseBlocks. Allerdings evalscheint eine schlechte Praxis und unsicher zu verwenden , in Betracht gezogen werden. Wenn ja, kann mir jemand erklären, warum und mir einen besseren Weg zeigen, die obige Klasse zu definieren?

Nikwin
quelle
40
Wie haben Sie davon erfahren exec/evalund wussten es immer noch nicht setattr?
u0b34a0f6ae
3
Ich glaube, es war aus einem Artikel, in dem Python und Lisp verglichen wurden, als ich etwas über Eval erfahren habe.
Nikwin

Antworten:

194

Ja, die Verwendung von eval ist eine schlechte Praxis. Um nur einige Gründe zu nennen:

  1. Es gibt fast immer einen besseren Weg, dies zu tun
  2. Sehr gefährlich und unsicher
  3. Erschwert das Debuggen
  4. Langsam

In Ihrem Fall können Sie stattdessen setattr verwenden:

class Song:
    """The class to store the details of each song"""
    attsToStore=('Name', 'Artist', 'Album', 'Genre', 'Location')
    def __init__(self):
        for att in self.attsToStore:
            setattr(self, att.lower(), None)
    def setDetail(self, key, val):
        if key in self.attsToStore:
            setattr(self, key.lower(), val)

BEARBEITEN:

In einigen Fällen müssen Sie eval oder exec verwenden. Aber sie sind selten. Die Verwendung von eval in Ihrem Fall ist mit Sicherheit eine schlechte Praxis. Ich betone schlechte Praktiken, weil eval und exec häufig am falschen Ort verwendet werden.

EDIT 2:

Es sieht so aus, als ob einige anderer Meinung sind, dass die Bewertung im OP-Fall „sehr gefährlich und unsicher“ ist. Das mag für diesen speziellen Fall zutreffen, aber nicht allgemein. Die Frage war allgemein und die Gründe, die ich aufgelistet habe, gelten auch für den allgemeinen Fall.

EDIT 3: Neu geordnete Punkte 1 und 4

Nadia Alramli
quelle
22
-1: "Sehr gefährlich und unsicher" ist falsch. Die anderen drei sind hervorragend klar. Bitte ordnen Sie sie so um, dass 2 und 4 die ersten beiden sind. Es ist nur unsicher, wenn Sie von bösen Soziopathen umgeben sind, die nach Wegen suchen, Ihre Bewerbung zu untergraben.
S.Lott
51
@ S.Lott, Unsicherheit ist ein sehr wichtiger Grund, um eval / exec im Allgemeinen zu vermeiden. Viele Anwendungen wie Websites sollten besonders vorsichtig sein. Nehmen Sie das OP-Beispiel auf einer Website, auf der Benutzer den Songnamen eingeben sollen. Es muss früher oder später ausgenutzt werden. Sogar eine unschuldige Eingabe wie: Lass uns Spaß haben. verursacht einen Syntaxfehler und legt die Sicherheitsanfälligkeit offen.
Nadia Alramli
17
@ Nadia Alramli: Benutzereingaben und evalhaben nichts miteinander zu tun. Eine Anwendung, die grundlegend falsch entworfen wurde, ist grundlegend falsch entworfen. evalist nicht mehr die Hauptursache für schlechtes Design als die Division durch Null oder der Versuch, ein Modul zu importieren, von dem bekannt ist, dass es nicht existiert. evalist nicht unsicher. Anwendungen sind unsicher.
S.Lott
17
@jeffjose: Eigentlich ist es grundsätzlich schlecht / böse, weil es nicht parametrisierte Daten als Code behandelt (aus diesem Grund gibt es XSS-, SQL-Injection- und Stack-Smashes). @ S.Lott: "Es ist nur unsicher, wenn Sie von bösen Soziopathen umgeben sind, die nach Wegen suchen, Ihre Bewerbung zu untergraben." Cool, sagen wir, Sie machen ein Programm calcund um Zahlen hinzuzufügen, wird es ausgeführt print(eval("{} + {}".format(n1, n2)))und beendet. Jetzt vertreiben Sie dieses Programm mit einem Betriebssystem. Dann erstellt jemand ein Bash-Skript, das einige Zahlen von einer Stock-Site nimmt und sie mithilfe von hinzufügt calc. Boom?
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
57
Ich bin mir nicht sicher, warum Nadias Behauptung so umstritten ist. Es scheint mir einfach zu sein: eval ist ein Vektor für die Code-Injection und in einer Weise gefährlich, wie es die meisten anderen Python-Funktionen nicht tun. Das bedeutet nicht, dass Sie es überhaupt nicht verwenden sollten, aber ich denke, Sie sollten es mit Bedacht verwenden.
Owen S.
32

Die Verwendung evalist schwach, keine eindeutig schlechte Praxis.

  1. Es verstößt gegen das "Grundprinzip der Software". Ihre Quelle ist nicht die Summe der ausführbaren Dateien. Neben Ihrer Quelle gibt es die Argumente eval, die klar verstanden werden müssen. Aus diesem Grund ist es das Werkzeug der letzten Instanz.

  2. Es ist normalerweise ein Zeichen für gedankenloses Design. Es gibt selten einen guten Grund für dynamischen Quellcode, der im laufenden Betrieb erstellt wird. Mit Delegation und anderen OO-Designtechniken kann fast alles getan werden.

  3. Dies führt zu einer relativ langsamen schnellen Kompilierung kleiner Codeteile. Ein Overhead, der durch die Verwendung besserer Entwurfsmuster vermieden werden kann.

Als Fußnote, in den Händen von gestörten Soziopathen, kann es nicht gut funktionieren. Wenn Sie jedoch mit gestörten soziopathischen Benutzern oder Administratoren konfrontiert werden, ist es am besten, ihnen nicht zuerst interpretiertes Python zu geben. In den Händen des wahrhaft Bösen kann Python eine Haftung übernehmen; evalerhöht das Risiko überhaupt nicht.

S.Lott
quelle
7
@ Owen S. Der Punkt ist dies. Die Leute werden Ihnen sagen, dass dies evaleine Art "Sicherheitslücke" ist. Als ob Python - selbst - nicht nur eine Menge interpretierter Quellen wäre, die jeder ändern könnte. Wenn man mit der "Bewertung ist eine Sicherheitslücke" konfrontiert wird, kann man nur annehmen, dass es sich um eine Sicherheitslücke in den Händen von Soziopathen handelt. Normale Programmierer ändern lediglich die vorhandene Python-Quelle und verursachen ihre Probleme direkt. Nicht indirekt durch evalMagie.
S.Lott
14
Nun, ich kann Ihnen genau sagen, warum ich sagen würde, dass eval eine Sicherheitslücke ist, und dies hat mit der Vertrauenswürdigkeit der Zeichenfolge zu tun, die als Eingabe angegeben wird. Wenn diese Zeichenfolge ganz oder teilweise von der Außenwelt stammt, besteht die Möglichkeit eines Skriptangriffs auf Ihr Programm, wenn Sie nicht vorsichtig sind. Aber das ist die Störung eines externen Angreifers, nicht des Benutzers oder Administrators.
Owen S.
6
@OwenS.: "Wenn diese Zeichenfolge ganz oder teilweise von der Außenwelt kommt" Oft falsch. Dies ist keine "vorsichtige" Sache. Es ist schwarz und weiß. Wenn der Text von einem Benutzer stammt, kann er niemals als vertrauenswürdig eingestuft werden. Pflege ist nicht wirklich ein Teil davon, es ist absolut nicht vertrauenswürdig. Andernfalls stammt der Text von einem Entwickler, Installer oder Administrator und kann als vertrauenswürdig eingestuft werden.
S.Lott
8
@OwenS.: Es ist nicht möglich, einer Zeichenfolge nicht vertrauenswürdigen Python-Codes zu entkommen, die ihn vertrauenswürdig macht. Ich stimme den meisten Ihrer Aussagen zu, mit Ausnahme des "vorsichtigen" Teils. Es ist eine sehr klare Unterscheidung. Code von außen ist nicht vertrauenswürdig. AFAIK, keine Menge an Entweichen oder Filtern kann es reinigen. Wenn Sie eine Escape-Funktion haben, die Code akzeptabel macht, teilen Sie diese bitte mit. Ich hätte nicht gedacht, dass so etwas möglich ist. Zum Beispiel while True: passwäre es schwierig, mit einer Art Flucht aufzuräumen.
S.Lott
2
@OwenS.: "Als Zeichenfolge gedacht, nicht als beliebiger Code". Das hat nichts damit zu tun. Dies ist nur ein Zeichenfolgenwert, den Sie niemals durchlaufen würden eval(), da es sich um eine Zeichenfolge handelt. Code von der "Außenwelt" kann nicht bereinigt werden. Saiten von außen sind nur Saiten. Mir ist unklar, wovon du sprichst. Vielleicht sollten Sie einen vollständigeren Blog-Beitrag bereitstellen und hier darauf verlinken.
S.Lott
23

In diesem Fall ja. Anstatt

exec 'self.Foo=val'

Sie sollten die eingebaute Funktion verwenden setattr:

setattr(self, 'Foo', val)
Josh Lee
quelle
16

Ja, so ist es:

Hack mit Python:

>>> eval(input())
"__import__('os').listdir('.')"
...........
...........   #dir listing
...........

Der folgende Code listet alle Aufgaben auf, die auf einem Windows-Computer ausgeführt werden.

>>> eval(input())
"__import__('subprocess').Popen(['tasklist'],stdout=__import__('subprocess').PIPE).communicate()[0]"

Unter Linux:

>>> eval(input())
"__import__('subprocess').Popen(['ps', 'aux'],stdout=__import__('subprocess').PIPE).communicate()[0]"
Hackaholic
quelle
7

Es ist erwähnenswert, dass es für das jeweilige Problem mehrere Alternativen zur Verwendung gibt eval:

Das einfachste ist, wie bereits erwähnt, die Verwendung von setattr:

def __init__(self):
    for name in attsToStore:
        setattr(self, name, None)

Ein weniger offensichtlicher Ansatz besteht darin, das Objekt des __dict__Objekts direkt zu aktualisieren . Wenn Sie lediglich die Attribute initialisieren möchten, ist Nonedies weniger einfach als oben beschrieben. Aber bedenken Sie Folgendes:

def __init__(self, **kwargs):
    for name in self.attsToStore:
       self.__dict__[name] = kwargs.get(name, None)

Auf diese Weise können Sie Schlüsselwortargumente an den Konstruktor übergeben, z.

s = Song(name='History', artist='The Verve')

Es ermöglicht Ihnen auch, Ihre Verwendung locals()expliziter zu gestalten, z.

s = Song(**locals())

... und wenn Sie wirklich Noneden Attributen zuweisen möchten, deren Namen sich befinden in locals():

s = Song(**dict([(k, None) for k in locals().keys()]))

Ein anderer Ansatz zum Bereitstellen von Standardwerten für ein Objekt für eine Liste von Attributen besteht darin, die __getattr__Methode der Klasse zu definieren :

def __getattr__(self, name):
    if name in self.attsToStore:
        return None
    raise NameError, name

Diese Methode wird aufgerufen, wenn das benannte Attribut nicht auf normale Weise gefunden wird. Dieser Ansatz ist etwas unkomplizierter als das einfache Festlegen der Attribute im Konstruktor oder das Aktualisieren des__dict__ Er hat jedoch den Vorteil, dass das Attribut nur dann erstellt wird, wenn es vorhanden ist, was die Speichernutzung der Klasse erheblich reduzieren kann.

Der Sinn all dessen: Es gibt im Allgemeinen viele Gründe, die zu vermeiden sind eval- das Sicherheitsproblem beim Ausführen von Code, den Sie nicht kontrollieren, das praktische Problem des Codes, den Sie nicht debuggen können usw. Aber ein noch wichtigerer Grund ist, dass Sie es im Allgemeinen nicht verwenden müssen. Python stellt dem Programmierer so viele seiner internen Mechanismen zur Verfügung, dass Sie selten wirklich Code schreiben müssen, der Code schreibt.

Robert Rossney
quelle
1
Ein anderer Weg, der wohl mehr (oder weniger) Pythonic ist: Anstatt das Objekt __dict__direkt zu verwenden, geben Sie dem Objekt ein tatsächliches Wörterbuchobjekt, entweder durch Vererbung oder als Attribut.
Josh Lee
1
"Ein weniger offensichtlicher Ansatz besteht darin, das Diktierobjekt des Objekts direkt zu aktualisieren. " => Beachten Sie, dass dadurch alle Deskriptoren (Eigenschaften oder andere) oder __setattr__Überschreibungen umgangen werden , was zu unerwarteten Ergebnissen führen kann. setattr()hat dieses Problem nicht.
Bruno Desthuilliers
5

Andere Benutzer wiesen darauf hin, wie Ihr Code geändert werden kann, um nicht davon abhängig zu sein eval. Ich werde einen legitimen Anwendungsfall für die Verwendung anbieten eval, der sogar in CPython zu finden ist: Testen .

Hier ist ein Beispiel, in test_unary.pydem ich einen Test gefunden habe, ob (+|-|~)b'a'ein TypeError:

def test_bad_types(self):
    for op in '+', '-', '~':
        self.assertRaises(TypeError, eval, op + "b'a'")
        self.assertRaises(TypeError, eval, op + "'a'")

Die Verwendung ist hier eindeutig keine schlechte Praxis; Sie definieren die Eingabe und beobachten lediglich das Verhalten. evalist praktisch zum Testen.

Schauen Sie sich diese Suche an eval, die im CPython-Git-Repository durchgeführt wird. Tests mit eval werden häufig verwendet.

Dimitris Fasarakis Hilliard
quelle
2

Wenn eval()zum Verarbeiten von vom Benutzer bereitgestellten Eingaben verwendet wird, aktivieren Sie den Benutzer für Drop-to-REPL , wobei Folgendes bereitgestellt wird:

"__import__('code').InteractiveConsole(locals=globals()).interact()"

Sie können damit durchkommen, aber normalerweise möchten Sie keine Vektoren für die Ausführung von beliebigem Code in Ihren Anwendungen.

moooeeeep
quelle
1

Zusätzlich zur Antwort von @Nadia Alramli habe ich ein kleines Programm ausprobiert, da ich neu in Python bin und unbedingt prüfen wollte, wie sich die Verwendung evalauf das Timing auswirkt. Nachfolgend sind die Beobachtungen aufgeführt:

#Difference while using print() with eval() and w/o eval() to print an int = 0.528969s per 100000 evals()

from datetime import datetime
def strOfNos():
    s = []
    for x in range(100000):
        s.append(str(x))
    return s

strOfNos()
print(datetime.now())
for x in strOfNos():
    print(x) #print(eval(x))
print(datetime.now())

#when using eval(int)
#2018-10-29 12:36:08.206022
#2018-10-29 12:36:10.407911
#diff = 2.201889 s

#when using int only
#2018-10-29 12:37:50.022753
#2018-10-29 12:37:51.090045
#diff = 1.67292
Lokeshwar Schneider
quelle