web-dev-qa-db-ja.com

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

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

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は悪い習慣と見なされ、使用するのは安全ではないようです。もしそうなら、誰かが私に理由を説明し、上記のクラスを定義するより良い方法を教えてもらえますか?

123
Nikwin

はい、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が「非常に危険で安全ではない」ということに異論があるようです。これはこの特定の場合に当てはまるかもしれませんが、一般的にはそうではありません。質問は一般的なものであり、リストした理由は一般的な場合にも当てはまります。

EDIT 3:順序変更されたポイント1および4

176
Nadia Alramli

evalの使用は弱く、明確な悪いプラクティスではありません。

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

  2. それは通常、軽率なデザインの兆候です。動的なソースコードがオンザフライで構築されるのに十分な理由はほとんどありません。委任およびその他のOO設計手法を使用して、ほとんどすべてを実行できます。

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

脚注として、社会障害者の手に渡ると、うまくいかないかもしれません。ただし、社会障害のあるユーザーまたは管理者との混乱に直面した場合、最初にPythonと解釈されないようにすることをお勧めします。本当に悪の手に、Pythonは責任を負います。 evalはリスクをまったく増加させません。

28
S.Lott

この場合、はい。の代わりに

exec 'self.Foo=val'

builtin function setattrを使用する必要があります。

setattr(self, 'Foo', val)
22
Josh Lee

はい、そうです:

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]"
13
Hackaholic

問題の特定の問題について、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())
_

...そして、Nonelocals()にある名前の属性に本当に割り当てたい場合:

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

オブジェクトに属性リストのデフォルト値を提供する別のアプローチは、クラスの___getattr___メソッドを定義することです:

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

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

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

6
Robert Rossney

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

test_unary.py ここで、(+|-|~)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はテストに便利です。

この検索をご覧くださいevalの場合、CPython gitリポジトリで実行されます。 evalを使用したテストが頻繁に使用されます。

eval()を使用してユーザー指定の入力を処理する場合、ユーザーは Drop-to-REPL を次のように指定できます。

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

あなたはそれで逃げるかもしれませんが、通常、あなたのアプリケーションで 任意のコード実行 のためのベクトルは必要ありません。

2
moooeeeep

@Nadia Alramliの答えに加えて、私はPythonが初めてであり、evalの使用がtimingsにどのように影響するかを確認したいので、以下の小さなプログラムが観察でした:

#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
0