Почему использование 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
кажется плохой практикой и небезопасной в использовании. Если так, может кто-нибудь объяснить мне, почему и показать мне лучший способ определения вышеупомянутого класса?
8 ответов
Да, использование 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)
РЕДАКТИРОВАТЬ:
В некоторых случаях вы должны использовать eval или exec. Но они редки. Использование eval в вашем случае, безусловно, плохая практика. Я подчеркиваю плохую практику, потому что eval и exec часто используются не в том месте.
РЕДАКТИРОВАТЬ 2:
Похоже, что некоторые не согласны с тем, что eval является "очень опасным и небезопасным" в случае с OP. Это может быть верно для этого конкретного случая, но не в целом. Вопрос был общим, и причины, которые я перечислил, верны и для общего случая.
РЕДАКТИРОВАТЬ 3: Изменение порядка пунктов 1 и 4
С помощью eval
слаб, не совсем плохая практика.
Это нарушает "Фундаментальный принцип программного обеспечения". Ваш источник не является суммой того, что исполняется. В дополнение к вашему источнику, есть аргументы
eval
, который должен быть четко понят. По этой причине это инструмент последней инстанции.Обычно это признак бездумного замысла. Там редко бывают веские причины для динамического исходного кода, созданного на лету. Почти все может быть сделано с делегированием и другими методами проектирования ОО.
Это приводит к относительно медленной на лету компиляции небольших фрагментов кода. Накладные расходы, которых можно избежать, используя лучшие шаблоны проектирования.
В качестве сноски, в руках невменяемых социопатов, это может не сработать. Однако, когда сталкиваешься с ненормальными социопатическими пользователями или администраторами, лучше не давать им интерпретируемый Python. В руках действительно зла, Питон может быть пассивом; eval
не увеличивает риск вообще.
Да, это:
Взломать с помощью 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]"
В этом случае да. Вместо
exec 'self.Foo=val'
Вы должны использовать встроенную функцию setattr
:
setattr(self, 'Foo', val)
Стоит отметить, что для конкретной проблемы есть несколько альтернатив 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 предоставляет так много своих внутренних механизмов программисту, что вам редко нужно писать код, который пишет код.
Другие пользователи указали, как ваш код может быть изменен, чтобы не зависеть от 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 git; Тестирование с помощью eval активно используется.
Когда eval()
используется для обработки предоставленного пользователем ввода, вы разрешаете пользователю Drop-to-REPL предоставлять что-то вроде этого:
"__import__('code').InteractiveConsole(locals=globals()).interact()"
Вы можете сойти с рук, но обычно вам не нужны векторы для выполнения произвольного кода в ваших приложениях.
В дополнение к ответу @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