[python] ‘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나쁜 습관으로 간주되어 사용하기에 안전하지 않은 것으로 보입니다. 그렇다면 누구에게 나에게 이유를 설명하고 위의 클래스를 정의하는 더 좋은 방법을 보여줄 수 있습니까?



답변

예, 평가를 사용하는 것은 나쁜 습관입니다. 몇 가지 이유를 말하면 다음과 같습니다.

  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를 사용해야하는 경우가 있습니다. 그러나 그들은 드 rare니다. 귀하의 경우 eval을 사용하는 것은 확실히 나쁜 습관입니다. eval과 exec가 종종 잘못된 장소에서 사용되기 때문에 나쁜 연습을 강조하고 있습니다.

편집 2 :

OP 사례에서 평가가 ‘매우 위험하고 안전하지 않다’는 의견에 동의하지 않는 것 같습니다. 이 특정 경우에 해당하지만 일반적으로 그렇지는 않습니다. 질문은 일반적이었고 내가 열거 한 이유는 일반적인 경우에도 마찬가지입니다.

편집 3 :
순서 1과 4를 재정렬


답변

사용 eval은 약하지만 분명히 나쁜 습관 은 아닙니다 .

  1. “기본 소프트웨어 원칙”을 위반합니다. 소스는 실행 가능한 총계가 아닙니다. 출처 외에도에 대한 논쟁이 있으며, eval분명히 이해해야합니다. 이런 이유로, 그것은 최후의 수단입니다.

  2. 일반적으로 사려 깊은 디자인의 표시입니다. 동적 소스 코드를 작성해야하는 이유는 거의 없습니다. 위임 및 기타 OO 디자인 기술로 거의 모든 작업을 수행 할 수 있습니다.

  3. 작은 코드 조각을 비교적 빠르게 컴파일합니다. 더 나은 디자인 패턴을 사용하면 피할 수있는 오버 헤드가 발생합니다.

각주로서, 소외된 소시 오 패스의 손에, 그것은 잘 작동하지 않을 수 있습니다. 그러나 소외된 사회 병리 적 사용자 나 관리자와 대면 할 때는 먼저 해석 된 파이썬을 제공하지 않는 것이 가장 좋습니다. 진정한 악의 손에 파이썬은 책임을 질 수 있습니다. eval전혀 위험을 증가시키지 않습니다.


답변

이 경우에 그렇습니다. 대신에

exec 'self.Foo=val'

내장 함수를 사용해야합니다 setattr:

setattr(self, 'Foo', val)


답변

예, 다음과 같습니다.

파이썬을 사용하여 해킹 :

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

아래 코드는 Windows 컴퓨터에서 실행되는 모든 작업을 나열합니다.

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

리눅스에서 :

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


답변

문제의 특정 문제에 대해 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()]))

속성 목록에 대한 기본값을 객체에 제공하는 또 다른 방법은 클래스의 __getattr__메소드 를 정의하는 것입니다.

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

명명 된 속성을 일반적인 방식으로 찾지 못하면이 메소드가 호출됩니다. 이 접근 방식은 생성자에서 속성을 설정하거나 업데이트하는 것보다 다소 덜 간단합니다.__dict__ 하지 않지만 실제로 존재하지 않는 한 실제로 속성을 만들지 않는 장점이 있습니다. 이는 클래스의 메모리 사용량을 상당히 줄일 수 있습니다.

이 모든 것의 요점 : 일반적으로 피해야 할 많은 이유 eval가 있습니다. 제어 할 수없는 코드를 실행하는 보안 문제, 디버깅 할 수없는 코드의 실제 문제 등. 그러나 훨씬 더 중요한 이유 일반적으로 사용하지 않아도됩니다. 파이썬은 내부 메커니즘의 많은 부분을 프로그래머에게 노출 시키므로 코드를 작성하는 코드를 거의 작성할 필요가 없습니다.


답변

다른 사용자들은 코드에 의존하지 않기 위해 코드를 어떻게 변경할 수 있는지 지적했다 eval. evalCPython에서도 찾아 볼 수있는 합법적 인 사용 사례를 제공 할 것입니다 : 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테스트에 편리합니다.

evalCPython git 저장소에서 수행 된 이 검색살펴보십시오 . eval 테스트는 많이 사용됩니다.


답변

eval()를 사용하여 사용자 제공 입력을 처리 할 때 사용자는 다음과 같은 것을 제공 하여 REP (drop-to-REPL) 를 수행 할 수 있습니다.

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

그것으로 벗어날 수도 있지만 일반적으로 응용 프로그램에서 임의의 코드 실행 을 위한 벡터를 원하지 않습니다 .