'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에 대해 배운 것보다 파이썬과 lisp를 비교 한 기사에서 믿었다.
Nikwin

답변:


194

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

  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를 재정렬


22
-1 : "매우 위험하고 안전하지 않습니다"는 거짓입니다. 다른 세 개는 매우 명확합니다. 2와 4가 처음 두 개가되도록 다시 정렬하십시오. 응용 프로그램을 파괴하는 방법을 찾고있는 사악한 사회 경로에 둘러싸여 있다면 안전하지 않습니다.
S.Lott

51
@ S.Lott, 불안감은 일반적으로 eval / exec를 피하는 매우 중요한 이유입니다. 웹 사이트와 같은 많은 응용 프로그램은 특별한주의를 기울여야합니다. 사용자가 노래 이름을 입력 할 것으로 예상되는 웹 사이트에서 OP 예제를 보자. 조만간 악용 될 수밖에 없습니다. 무고한 입력조차 : 재미있게 보자. 구문 오류가 발생하고 취약점이 노출됩니다.
Nadia Alramli 2009

17
@Nadia Alramli : 사용자 입력과 eval서로 관련이 없습니다. 기본적으로 잘못 디자인 된 응용 프로그램은 기본적으로 잘못 디자인되었습니다. eval0으로 나누거나 존재하지 않는 것으로 알려진 모듈을 가져 오려고 시도하는 것보다 나쁜 설계의 근본 원인은 아닙니다. eval안전하지 않습니다. 응용 프로그램이 안전하지 않습니다.
S.Lott

17
@jeffjose : 사실, 그것은이다 그것은 코드로 unparamaterized 데이터를 처리 있기 때문에 (XSS, SQL 주입 및 스택 쳤이 존재하는 이유는) 근본적으로 나쁜 / 악. @ S.Lott : "응용 프로그램을 파괴하는 방법을 찾고있는 사악한 사회 경로에 둘러싸여있는 경우에만 안전하지 않습니다." 멋지다, 프로그램 calc을 만들고 숫자를 추가하여 실행 print(eval("{} + {}".format(n1, n2)))하고 종료 한다고 가정 해보십시오. 이제이 프로그램을 일부 OS와 함께 배포합니다. 그런 다음 누군가가 재고 사이트에서 일부 숫자를 가져 와서 bash 스크립트를 작성합니다 calc. 팔?
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

57
나디아의 주장이 왜 그렇게 논쟁 적인지 잘 모르겠습니다. eval은 코드 주입을위한 벡터이며 대부분의 다른 Python 함수가 아닌 방식으로 위험합니다. 그렇다고해서 전혀 사용해서는 안된다는 의미는 아니지만 신중하게 사용해야한다고 생각합니다.
Owen S.

32

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

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

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

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

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


7
@Owen S. 요점은 이것입니다. 사람들은 eval일종의 "보안 취약점"이라고 말할 것입니다 . 마치 파이썬 (자체)은 누구나 수정할 수있는 해석 된 소스가 아닙니다. "eval is a security hole"인 경우, 소시 오 패스의 손에있는 보안 구멍이라고 가정 할 수 있습니다. 일반 프로그래머는 기존 파이썬 소스를 수정하고 직접 문제를 일으 킵니다. 간접적으로eval마술을 .
S.Lott

14
글쎄, eval이 보안 취약점이라고 말하는 이유를 정확하게 말할 수 있으며 입력으로 제공된 문자열의 신뢰성과 관련이 있습니다. 해당 문자열이 전체 또는 일부 외부 세계에서 온 경우주의하지 않으면 프로그램에 스크립팅 공격이 발생할 수 있습니다. 그러나 그것은 사용자 나 관리자가 아닌 외부 공격자의 혼란입니다.
Owen S.

6
@OwenS .: "해당 문자열의 전체 또는 일부가 외부 세계에서 온 경우"종종 거짓입니다. 이것은 "신중한"것이 아닙니다. 흑백입니다. 텍스트가 사용자로부터 온 경우 절대 신뢰할 수 없습니다 . 관리는 실제로 그 일부가 아니며 절대 신뢰할 수 없습니다. 그렇지 않으면 텍스트는 개발자, 설치 관리자 또는 관리자가 제공하며 신뢰할 수 있습니다.
S. 로트

8
@OwenS. : 신뢰할 수없는 파이썬 코드 문자열을 탈출 할 수는 없습니다. "주의"부분을 제외하고 당신이하는 말의 대부분에 동의합니다. 매우 뚜렷한 차이입니다. 외부 세계의 코드는 신뢰할 수 없습니다. AFAIK, 이스케이프 또는 필터링 양은 정리할 수 없습니다. 코드를 수용 할 수있는 이스케이프 기능이 있으면 공유하십시오. 나는 그런 일이 가능하다고 생각하지 않았습니다. 예를 들어 while True: pass어떤 종류의 탈출로 정리하기가 어려울 것입니다.
S.Lott

2
@OwenS .: "임의의 코드가 아닌 문자열로 의도 됨". 관련이 없습니다. 그것은 문자열 값이므로 문자열 eval()이기 때문에 결코 통과하지 못할 것 입니다. "외부 세계"의 코드는 위생 처리 할 수 ​​없습니다. 외부 세계의 현은 단지 현입니다. 당신이 무슨 말을하는지 잘 모르겠습니다. 아마도보다 완전한 블로그 게시물을 제공하고 여기에 링크해야합니다.
S.Lott

23

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

exec 'self.Foo=val'

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

setattr(self, 'Foo', val)

16

예, 다음과 같습니다.

파이썬을 사용하여 해킹 :

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

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

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

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

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

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


1
아마도 파이썬이 더 많거나 적은 다른 방법 : 객체를 __dict__직접 사용하는 대신 상속을 통해 또는 속성으로 객체에 실제 사전 객체를 제공하십시오.
Josh Lee

1
"명확하지 않은 접근 방식은 객체의 dict 객체를 직접 업데이트하는 것입니다 "=> 이렇게하면 설명자 (속성 또는 기타)를 __setattr__무시 하거나 재정의되어 예기치 않은 결과가 발생할 수 있습니다. setattr()이 문제가 없습니다.
bruno desthuilliers 12

5

다른 사용자들은 코드에 의존하지 않기 위해 코드를 어떻게 변경할 수 있는지 지적했다 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 테스트는 많이 사용됩니다.


2

eval()를 사용하여 사용자 제공 입력을 처리 할 때 사용자는 다음과 같은 것을 제공 하여 REP (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.