Почему использование "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

Да, использование eval - плохая практика. Чтобы назвать несколько причин:

  • Существует почти всегда лучший способ сделать это.
  • Очень опасно и небезопасно
  • затрудняет отладку
  • Низкая

В вашем случае вы можете использовать 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

Ответ 2

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

  • Он нарушает "Основополагающий принцип программного обеспечения". Ваш источник не является общей суммой исполняемого файла. В дополнение к вашему источнику есть аргументы eval, которые должны быть четко поняты. По этой причине это инструмент последней инстанции.

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

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

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

Ответ 3

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

exec 'self.Foo=val'

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

setattr(self, 'Foo', val)

Ответ 4

Да, это:

Взлом с использованием 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]"

Ответ 5

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

Ответ 6

Другие пользователи указали, как можно изменить ваш код, чтобы он не зависел от eval; Я предложу законный вариант использования eval, который встречается даже в CPython: тестирование.

Вот один пример, который я нашел в test_unary.py где (+|-|~)b'a' вызывает ли (+|-|~)b'a' TypeError (+|-|~)b'a':

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 активно используется.

Ответ 7

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

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

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

Ответ 8

В дополнение к ответу @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