Почему использование eval - плохая практика?

python eval

34384 просмотра

8 ответа

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

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

Автор: Nikwin Источник Размещён: 12.11.2019 09:38

Ответы (8)


178 плюса

Решение

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

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

РЕДАКТИРОВАТЬ 3: Изменение порядка пунктов 1 и 4

Автор: Nadia Alramli Размещён: 02.12.2009 01:37

29 плюса

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

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

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

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

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

Автор: S.Lott Размещён: 02.12.2009 06:08

22 плюса

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

exec 'self.Foo=val'

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

setattr(self, 'Foo', val)
Автор: Josh Lee Размещён: 02.12.2009 01:38

13 плюса

Да, это:

Взломать с помощью 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]"
Автор: Hackaholic Размещён: 06.05.2016 08:41

6 плюса

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

Автор: Robert Rossney Размещён: 02.12.2009 06:19

5 плюса

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

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

Автор: Jim Fasarakis Hilliard Размещён: 27.11.2016 05:20

2 плюса

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

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

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

Автор: moooeeeep Размещён: 29.05.2018 09:47

0 плюса

В дополнение к @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
Автор: Lokeshwar Tailor Размещён: 29.10.2018 07:26
Вопросы из категории :
32x32