Почему использование eval - плохая практика? - PullRequest
117 голосов
/ 02 декабря 2009

Я использую следующий класс для простого хранения данных моих песен.

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 представляется плохой практикой и небезопасной для использования. Если это так, может кто-нибудь объяснить мне, почему и показать мне лучший способ определения вышеупомянутого класса?

Ответы [ 8 ]

172 голосов
/ 02 декабря 2009

Да, использование 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)

EDIT:

В некоторых случаях вы должны использовать eval или exec. Но они редки. Использование eval в вашем случае наверняка является плохой практикой. Я подчеркиваю плохую практику, потому что eval и exec часто используются не в том месте.

РЕДАКТИРОВАТЬ 2:

Похоже, некоторые не согласны с тем, что eval «очень опасен и небезопасен» в случае с OP. Это может быть верно для этого конкретного случая, но не в целом. Вопрос был общим, и причины, которые я перечислил, верны и для общего случая.

РЕДАКТИРОВАТЬ 3: Переупорядочены точки 1 и 4

28 голосов
/ 02 декабря 2009

Использование eval является слабым, а не явно плохим практикой.

  1. Это нарушает «Фундаментальный принцип программного обеспечения». Ваш источник не является общей суммой исполняемого файла. В дополнение к вашему источнику есть аргументы для eval, которые должны быть четко поняты. По этой причине это инструмент последней инстанции.

  2. Обычно это знак бездумного замысла. Там редко бывают веские причины для динамического исходного кода, созданного на лету. Практически все можно сделать с помощью делегирования и других методов проектирования ОО.

  3. Это приводит к относительно медленной на лету компиляции небольших фрагментов кода. Накладные расходы, которых можно избежать, используя лучшие шаблоны проектирования.

Как сноска, в руках невменяемых социопатов это может не сработать. Однако, когда сталкиваешься с ненормальными социопатическими пользователями или администраторами, лучше вообще не давать им интерпретируемый Python. В руках действительно зла, Python может быть ответственностью; eval не увеличивает риск вообще.

22 голосов
/ 02 декабря 2009

В этом случае да. Вместо

exec 'self.Foo=val'

Вы должны использовать встроенную функцию setattr:

setattr(self, 'Foo', val)
13 голосов
/ 06 мая 2016

Да, это:

Взлом с использованием 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]"
6 голосов
/ 02 декабря 2009

Стоит отметить, что для конкретной проблемы существует несколько альтернатив использованию 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 - проблема безопасности выполнения кода, который вы не контролируете, практическая проблема кода, который вы не можете отлаживать, и т. Д. Но еще более важной причиной является то, что вам вообще не нужно его использовать. Python предоставляет так много своих внутренних механизмов программисту, что вам действительно не нужно писать код, который пишет код.

5 голосов
/ 27 ноября 2016

Другие пользователи указали, как можно изменить ваш код, чтобы он не зависел от 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.

2 голосов
/ 29 мая 2018

Когда eval() используется для обработки предоставленного пользователем ввода, вы разрешаете пользователю Drop-to-REPL , предоставляя что-то вроде этого:

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

Вы можете сойти с рук, но обычно вам не нужны векторы для выполнения произвольного кода в ваших приложениях.

0 голосов
/ 29 октября 2018

В дополнение к ответу @Nadia Alramli, поскольку я новичок в Python и хотел проверить, как использование eval повлияет на тайминги , я попробовал небольшую программу, и ниже были наблюдения: 1004 *

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