「eval」を使用することが悪い習慣であるのはなぜですか?


138

次のクラスを使用して、曲のデータを簡単に保存しています。

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()

これは、if/elseブロックを書き出すよりもはるかに拡張可能だと思います。ただし、これevalは悪い習慣と見なされ、使用するのは安全ではないようです。もしそうなら、誰かが私に理由を説明し、上記のクラスを定義するより良い方法を私に示すことができますか?


40
どのようにして知りexec/eval、まだ知りませんでしたかsetattr
u0b34a0f6ae 2009

3
私はevalについて学んだよりもpythonとlispを比較する記事からのものだと思います。
Nikwin、2009

回答:


194

はい、evalの使用は悪い習慣です。いくつかの理由を挙げれば:

  1. ほとんどの場合、それを行うにはより良い方法があります
  2. 非常に危険で安全でない
  3. デバッグを困難にする
  4. スロー

あなたの場合、代わりにsetattrを使うことができます:

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)

編集:

evalまたはexecを使用しなければならない場合があります。しかし、それらはまれです。あなたのケースでevalを使用することは確かに悪い習慣です。evalとexecは間違った場所で頻繁に使用されるため、私は悪い習慣に重点を置いています。

編集2:

OPの場合、evalが「非常に危険で安全でない」という意見には、いくつかの意見の相違があります。これはこの特定のケースには当てはまるかもしれませんが、一般的には当てはまりません。質問は一般的であり、私が挙げた理由は一般的なケースにも当てはまります。

編集3: ポイント1と4を並べ替え


23
-1:「非常に危険で安全でない」は誤りです。他の3つは非常に明確です。2と4が最初の2つになるように並べ替えてください。アプリケーションを破壊する方法を模索している邪悪な社会人に囲まれている場合、それは安全ではありません。
S.Lott、2009

51
@ S.Lott、不安は一般的にeval / execを回避する非常に重要な理由です。ウェブサイトのような多くのアプリケーションは、特別な注意を払う必要があります。ユーザーが曲名を入力することを期待しているWebサイトのOPの例を見てみましょう。それは遅かれ早かれ悪用されることになっています。次のような無垢な入力でさえ、楽しみましょう。構文エラーが発生し、脆弱性が公開されます。
Nadia Alramli、2009

18
@Nadia Alramli:ユーザー入力であり、eval互いに何の関係もありません。根本的に誤設計されているアプリケーションは、根本的に誤設計されています。 evalゼロによる除算または存在しないことがわかっているモジュールをインポートしようとすることよりも、悪い設計の根本的な原因ではありません。 eval安全ではありません。アプリケーションは安全ではありません。
S.Lott、2009

17
@jeffjose:実際に、パラメータ化されていないデータをコードとして扱うため、根本的に悪い/悪です(これが、XSS、SQLインジェクション、スタックスマッシュが存在する理由です)。@ S.Lott:「アプリケーションを破壊する方法を模索している悪質な社会人に囲まれている場合、それは安全ではありません。」かっこいい、プログラムを作成し、calc数値を追加すると、プログラムが実行されprint(eval("{} + {}".format(n1, n2)))て終了するとします。このプログラムをいくつかのOSで配布します。次に、誰かがストックサイトからいくつかの数値を取得し、を使用してそれらを追加するbashスクリプトを作成しますcalc。ブーム?
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

57
なぜナディアの主張がそれほど論争的であるのか私にはわからない。それは私には簡単に思えます:evalはコードインジェクションのベクトルであり、他のほとんどのPython関数がそうではないように危険です。それはあなたがそれをまったく使うべきではないという意味ではありませんが、私はそれを慎重に使うべきだと思います。
オーウェンS.

32

使用evalは弱く、明らかに悪い習慣ではありません。

  1. 「ソフトウェアの基本原理」に違反しています。ソースは、実行可能ファイルの合計ではありません。ソースに加えて、への引数evalがあります。これは明確に理解する必要があります。このため、これは最後の手段です。

  2. それは通常、軽率なデザインの兆候です。オンザフライで構築された動的ソースコードを使用する正当な理由はめったにありません。委任やその他のオブジェクト指向の設計手法を使用して、ほとんど何でも実行できます。

  3. 小さなコードのオンザフライでのコンパイルが比較的遅くなります。より良い設計パターンを使用することで回避できるオーバーヘッド。

脚注として、混乱した社会主義者の手の中では、うまく機能しない可能性があります。ただし、混乱した社会性のあるユーザーまたは管理者に直面した場合、そもそも解釈されたPythonを彼らに与えないことが最善です。本当に悪の手に渡って、Pythonは責任を負うことができます。evalリスクはまったく増加しません。


7
@Owen S.ポイントはこれです。それevalはある種の「セキュリティの脆弱性」だと人々は言うでしょう。まるでPython-それ自体-は、だれでも修正できる一連の解釈されたソースではなかったようです。「評価はセキュリティホールです」と直面した場合、それは社会パスの手にあるセキュリティホールであるとしか想定できません。通常のプログラマーは、既存のPythonソースを変更し、直接問題を引き起こすだけです。eval魔法を介して間接的にではありません。
S.Lott、2011

14
ええと、evalがセキュリティの脆弱性であると私が言う理由を正確に説明できます。これは、入力として指定された文字列の信頼性に関係しています。その文字列の全体または一部が外部の世界からのものである場合、注意していないと、プログラムがスクリプト攻撃を受ける可能性があります。しかし、それはユーザーや管理者ではなく、外部の攻撃者の混乱です。
オーウェンS.

6
@OwenS .:「その文字列の全体または一部が外部の世界からのものである場合」しばしば誤り。これは「注意深い」ことではありません。黒と白です。テキストがユーザーからのものである場合、それを信頼することできません。ケアは本当にその一部ではありません、それは絶対に信頼できません。それ以外の場合、テキストは開発者、インストーラー、または管理者からのものであり、信頼できます。
S.Lott、2012年

8
@OwenS .:信頼できるPythonコードの文字列がエスケープされる可能性はありません。「注意深い」部分を除いて、あなたが言っていることのほとんどに同意します。それは非常に鮮明な違いです。外部からのコードは信頼できません。私の知る限り、エスケープやフィルタリングの量はそれをクリーンアップできません。コードを許容できるようなエスケープ関数がある場合は、共有してください。そんなことは可能だとは思わなかった。たとえばwhile True: pass、なんらかのエスケープでクリーンアップするのは難しいでしょう。
S.Lott、2012年

2
@OwenS .:「文字列であり、任意のコードではありません」。それは無関係です。それは文字列なeval()ので、通過することのない文字列値です。「外の世界」のコードはサニタイズできません。外界からの弦は単なる弦です。あなたが何を話しているのかよくわかりません。おそらく、より完全なブログ投稿とそれへのリンクをここに提供する必要があります。
S.Lott、2012年


16

はい、そうです:

Pythonを使用したハック:

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

以下のコードは、Windowsマシンで実行されているすべてのタスクをリストします。

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

Linuxの場合:

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

7

問題となっている特定の問題については、を使用する代わりにいくつかの方法がありますeval

指摘したように、最も簡単なのは以下を使用することsetattrです。

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

それほど明白ではないアプローチは、オブジェクトの__dict__オブジェクトを直接更新することです。属性をNoneに初期化するだけの場合、これは上記より簡単ではありません。しかし、これを考慮してください:

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

これにより、キーワード引数をコンストラクタに渡すことができます。例:

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

また、次のように、locals()より明示的に使用することもできます。

s = Song(**locals())

...そして、None名前が次の場所にある属性に本当に割り当てたい場合locals()

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

オブジェクトに属性のリストのデフォルト値を提供するもう1つの__getattr__方法は、クラスのメソッドを定義することです。

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

このメソッドは、名前付き属性が通常の方法で見つからない場合に呼び出されます。このアプローチは、単にコンストラクターで属性を設定するか、を更新するよりも簡単__dict__ではありませんが、属性が存在しない限り実際には作成されないというメリットがあり、クラスのメモリ使用量を大幅に削減できます。

このすべてのポイント:一般に、回避すべき多くの理由がありますeval-制御できないコードを実行することのセキュリティ問題、デバッグできないコードの実際的な問題など。しかし、さらに重要な理由つまり、通常は使用する必要はありません。Pythonは、内部メカニズムの多くをプログラマーに公開しているため、コードを記述するコードを記述する必要はほとんどありません。


1
間違いなく多かれ少なかれPythonicである別の方法:オブジェクトの__dict__直接を使用する代わりに、継承を通じて、または属性として、オブジェクトに実際のディクショナリオブジェクトを指定します。
ジョシュリー

1
「あまり明確でないアプローチは、オブジェクトのdictオブジェクトを直接更新することです」=>これにより、記述子(プロパティまたはその他)または__setattr__オーバーライドがバイパスされ、予期しない結果が生じる可能性があることに注意してください。setattr()この問題はありません。
Bruno desthuilliers

5

他のユーザーは、コードに依存しないようにコードを変更する方法を指摘しましたeval。を使用するための正当なユースケースを提供します。これevalは、CPythonでも見つかるテストです。

がをレイズするtest_unary.pyかどうかのテストで見つかった1つの例を次に示します。(+|-|~)b'a'TypeError

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

ここでの使用法は明らかに悪い習慣ではありません。入力を定義し、動作を観察するだけです。evalテストに便利です。

evalCPython gitリポジトリで実行されたこのの検索見てください。evalを使用したテストは頻繁に使用されます。


2

eval()がユーザー指定の入力を処理するために使用される場合、ユーザーは次のようなものを提供Drop-to-REPLを実行できます。

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

あなたはそれでうまくいくかもしれませんが、通常あなたはあなたのアプリケーションで任意のコードを実行するためのベクトルを望んでいません。


1

@Nadia Alramliの回答に加えて、私はPythonに不慣れで、使用evalタイミングにどのように影響するかを確認したいと思っていたので、小さなプログラムを試してみました。

#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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.