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