'Eval' kullanmak neden kötü bir uygulamadır?


138

Şarkılarımın verilerini kolayca saklamak için aşağıdaki sınıfı kullanıyorum.

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()

Bunun bir if/elseblok yazmaktan çok daha genişletilebilir olduğunu hissediyorum . Ancak, evalkötü bir uygulama ve kullanımı güvensiz olarak kabul edilmektedir. Eğer öyleyse, birisi bana nedenini açıklayabilir ve yukarıdaki sınıfı tanımlamanın daha iyi bir yolunu gösterebilir mi?


40
nasıl öğrendin exec/evalve hala bilmiyordun setattr?
u0b34a0f6ae

3
Ben eval hakkında öğrendiğimden daha python ve lisp karşılaştırma bir makaleden olduğuna inanıyorum.
Nikwin

Yanıtlar:


194

Evet, eval kullanmak kötü bir uygulamadır. Sadece birkaç nedeni belirtmek için:

  1. Bunu yapmanın neredeyse her zaman daha iyi bir yolu var
  2. Çok tehlikeli ve güvensiz
  3. Hata ayıklamayı zorlaştırır
  4. Yavaş

Sizin durumunuzda bunun yerine setattr kullanabilirsiniz :

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)

DÜZENLE:

Eval veya exec kullanmak zorunda olduğunuz bazı durumlar vardır. Ancak nadirdirler. Sizin durumunuzda eval kullanmak kötü bir uygulamadır. Kötü uygulama üzerinde duruyorum çünkü eval ve exec sıklıkla yanlış yerde kullanılıyor.

DÜZENLEME 2:

OP davasında eval'ün 'çok tehlikeli ve güvensiz' olduğu konusunda bazı anlaşmazlıklar var gibi görünüyor. Bu, bu özel durum için doğru olabilir, ancak genel olarak geçerli değildir. Soru geneldi ve listelediğim nedenler genel durum için de geçerli.

DÜZENLEME 3: Yeniden sıralanan nokta 1 ve 4


23
-1: "Çok tehlikeli ve güvensiz" yanlıştır. Diğer üçü son derece açık. Lütfen bunları 2 ve 4 ilk ikisi olacak şekilde yeniden sıralayın. Sadece başvurunuzu bozmanın yollarını arayan kötü sosyopatlarla çevrili olmanız güvenli değildir.
S.Lott

51
@ S.Lott, Güvensizlik genel olarak değerlendirme / icradan kaçınmak için çok önemli bir nedendir. Web siteleri gibi birçok uygulama daha fazla dikkat etmelidir. Kullanıcıların şarkı adını girmelerini bekleyen bir web sitesinde OP örneğini alın. Er ya da geç sömürülmek zorundadır. Masum bir girdi bile: Eğlenelim. sözdizimi hatasına neden olur ve güvenlik açığını ortaya çıkarır.
Nadia Alramli

18
@Nadia Alramli: Kullanıcı girdisi ve evalbirbirleriyle hiçbir ilgisi yok. Temelde yanlış tasarlanmış bir uygulama temelde yanlış tasarlanmıştır. evalkötü tasarımın temel nedeni, sıfıra bölmek veya var olmadığı bilinen bir modülü içe aktarmaya çalışmaktan başka bir şey değildir. evalgüvensiz değil. Uygulamalar güvensizdir.
S.Lott

17
@jeffjose: Aslında öyle o kodu olarak unparamaterized verileri davranıyor çünkü (XSS, SQL enjeksiyon ve yığın Smashes var bu yüzden) temelde kötü / kötülük. @ S.Lott: "Sadece başvurunuzu bozmanın yollarını arayan kötü sosyopatlarla çevriliyorsanız güvensizdir." Harika, yani bir program yaptığınızı calcve çalıştırdığınız print(eval("{} + {}".format(n1, n2)))ve çıktığı sayıları eklemek için söyleyin . Şimdi bu programı bazı işletim sistemlerine dağıtıyorsunuz. Sonra birisi bir stok sitesinden bazı numaraları alıp kullanarak bir bash betiği yapar calc. Boom?
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

57
Nadia'nın iddialarının neden bu kadar tartışmalı olduğundan emin değilim. Benim için basit görünüyor: eval, kod enjeksiyonu için bir vektördür ve diğer Python işlevlerinin çoğu olmayacak şekilde tehlikelidir. Bu, hiç kullanmamanız gerektiği anlamına gelmez, ama bence bunu akıllıca kullanmalısınız.
Owen

32

Kullanımı evalzayıftır, açıkça kötü bir uygulama değildir.

  1. "Yazılımın Temel Prensibi" ni ihlal eder. Kaynağınız, yürütülebilir dosyanın toplamı değil. Kaynağınıza ek olarak, evalaçıkça anlaşılması gereken argümanlar var . Bu nedenle, son çare aracıdır.

  2. Genellikle düşüncesiz tasarımın bir işaretidir. Anında oluşturulmuş dinamik kaynak kodu için nadiren iyi bir neden vardır. Yetkilendirme ve diğer OO tasarım teknikleri ile hemen hemen her şey yapılabilir.

  3. Küçük kod parçalarının anında yavaşça derlenmesine yol açar. Daha iyi tasarım desenleri kullanılarak önlenebilecek bir ek yük.

Dipnot olarak, dengesiz sosyopatların elinde iyi sonuç vermeyebilir. Bununla birlikte, dengesiz sosyopatik kullanıcılarla veya yöneticilerle karşılaştığında, en başta onlara yorumlanmış Python vermemek en iyisidir. Gerçekten kötülüğün elinde Python bir sorumluluk alabilir; evalriski hiç arttırmaz.


7
@Owen S. Mesele bu. Millet, evalbunun bir tür "güvenlik açığı" olduğunu söyleyecektir . Sanki Python - kendisi sadece herkesin değiştirebileceği bir grup yorumlanmış kaynak değildi. "Eval bir güvenlik deliği" ile karşılaştığında, bunun sadece sosyopatların elinde bir güvenlik deliği olduğunu varsayabilirsiniz. Sıradan programcılar sadece mevcut Python kaynağını değiştirir ve sorunlarına doğrudan neden olurlar. Dolaylı olarak evalsihir yoluyla değil .
S.Lott

14
Size tam olarak neden eval'in bir güvenlik açığı olduğunu söyleyebileceğimi söyleyebilirim ve bunun girdi olarak verdiği dizenin güvenilirliği ile ilgisi var. Bu dize kısmen veya tamamen dış dünyadan geliyorsa, dikkatli değilseniz programınıza bir komut dosyası saldırısı yapma olasılığı vardır. Ancak bu, kullanıcının veya yöneticinin değil, dışarıdaki bir saldırganın düzensizliğidir.
Owen S.6

6
@OwenS .: "Eğer bu dize kısmen ya da tamamen dış dünyadan geliyorsa" Genellikle yanlıştır. Bu "dikkatli" bir şey değil. Bu siyah ve beyaz. Metin bir kullanıcıdan geliyorsa, asla güvenilemez. Bakım gerçekten bunun bir parçası değil, kesinlikle güvenilmez. Aksi takdirde, metin bir geliştirici, yükleyici veya yöneticiden gelir ve güvenilir olabilir.
S.Lott

8
@OwenS .: Güvenilir olmayan bir Python kodu dizisi için kaçmak mümkün değil. "Dikkatli" kısmı dışında söylediklerinizin çoğuna katılıyorum. Çok net bir ayrım. Dış dünyadan gelen kod güvenilir değildir. AFAIK, hiçbir kaçış veya filtreleme onu temizleyemez. Kodu kabul edilebilir kılacak bir tür işleviniz varsa, lütfen paylaşın. Böyle bir şeyin mümkün olduğunu düşünmemiştim. Örneğin while True: pass, bir tür kaçışla temizlenmesi zor olurdu.
S.Lott

2
@OwenS .: "rastgele bir kod değil, bir dize olarak tasarlanmıştır". Bu ilgisiz. Bu sadece bir dize değeri, asla geçmeyeceğiniz eval()bir dize, çünkü bir dize. "Dış dünya" dan gelen yasalar sterilize edilemez. Dış dünyadan gelen teller sadece iplerdir. Neden bahsettiğin konusunda net değilim. Belki daha eksiksiz bir blog gönderisi sağlamalı ve buraya bağlantı vermelisiniz.
S.Lott

23

Bu durumda, evet. Onun yerine

exec 'self.Foo=val'

yerleşik işlevi kullanmalısınız setattr:

setattr(self, 'Foo', val)

16

Evet öyle:

Python kullanarak kesmek:

>>> eval(input())
"__import__('os').listdir('.')"
...........
...........   #dir listing
...........

Aşağıdaki kod, bir Windows makinesinde çalışan tüm görevleri listeler.

>>> eval(input())
"__import__('subprocess').Popen(['tasklist'],stdout=__import__('subprocess').PIPE).communicate()[0]"

Linux'ta:

>>> eval(input())
"__import__('subprocess').Popen(['ps', 'aux'],stdout=__import__('subprocess').PIPE).communicate()[0]"

7

Söz konusu sorun için, kullanmanın birkaç alternatifi olduğunu belirtmek gerekir eval:

En basit, belirtildiği gibi kullanıyor setattr:

def __init__(self):
    for name in attsToStore:
        setattr(self, name, None)

Daha az belirgin bir yaklaşım, nesnenin __dict__nesnesini doğrudan güncellemektir . Tüm yapmak istediğiniz nitelikleri başlatmaksa, Nonebu yukarıdakilerden daha az basittir. Ancak şunu düşünün:

def __init__(self, **kwargs):
    for name in self.attsToStore:
       self.__dict__[name] = kwargs.get(name, None)

Bu, yapıcıya anahtar kelime bağımsız değişkenleri iletmenize olanak tanır, örneğin:

s = Song(name='History', artist='The Verve')

Ayrıca locals()daha açık bir şekilde kullanmanıza izin verir , örneğin:

s = Song(**locals())

... ve Noneadları gerçekten bulunan özelliklere gerçekten atamak istiyorsanız locals():

s = Song(**dict([(k, None) for k in locals().keys()]))

Bir nesneye nitelikler listesi için varsayılan değerler sağlamanın başka bir yaklaşımı da sınıfın __getattr__yöntemini tanımlamaktır :

def __getattr__(self, name):
    if name in self.attsToStore:
        return None
    raise NameError, name

Bu yöntem, adlandırılan öznitelik normal şekilde bulunmadığında çağrılır. Bu yaklaşım, yalnızca yapıcıdaki öznitelikleri ayarlamaktan veya güncellemekten daha az basittir __dict__, ancak sınıfın bellek kullanımını oldukça önemli ölçüde azaltabilen özniteliği olmadıkça aslında özniteliği oluşturmamaktan yararlanır.

Tüm bunların amacı: Genel olarak, kaçınmak için birçok neden vardır eval- kontrol etmediğiniz kod yürütmenin güvenlik sorunu, hata ayıklayamadığınız pratik kod sorunu, vb. Ama daha da önemli bir neden genel olarak, onu kullanmanıza gerek yoktur. Python, iç mekanizmalarının çoğunu programcıya maruz bırakır ve nadiren gerçekten kod yazan bir kod yazmanız gerekir.


1
Tartışmasız daha fazla (veya daha az) Pythonic olan başka bir yol: Nesnenin __dict__doğrudan kullanmak yerine, nesneye miras yoluyla veya nitelik olarak gerçek bir sözlük nesnesi verin.
Josh Lee

1
"Daha az belirgin bir yaklaşım, nesnenin dikte nesnesini doğrudan güncelliyor " => Bunun, herhangi bir tanımlayıcıyı (özellik veya diğer) atlayacağını veya __setattr__beklenmedik sonuçlara neden olabileceğini geçersiz kılacağını unutmayın. setattr()bu problemi yok.
bruno desthuilliers

5

Diğer kullanıcılar, kodunuzun bağımlı olmayacak şekilde nasıl değiştirilebileceğine dikkat çekti eval; evalCPython: testte bile bulunan meşru bir kullanım örneği sunacağım .

İşte bir örnek test_unary.pynerede (+|-|~)b'a'yükseltir olup olmadığını bir test TypeError:

def test_bad_types(self):
    for op in '+', '-', '~':
        self.assertRaises(TypeError, eval, op + "b'a'")
        self.assertRaises(TypeError, eval, op + "'a'")

Burada kullanımı açıkça kötü bir uygulama değildir; girdiyi tanımlar ve yalnızca davranışı gözlemlersiniz. evaltest için kullanışlıdır.

Bu arama bir göz atın için evalCPython git depo üzerinde gerçekleştirilen; eval ile test çok kullanılır.


2

Ne zaman eval()işlem kullanıcı tarafından sağlanan girişi için kullanılır, sen kullanıcı etkinleştirmek Bırak-to-Repl böyle bir şey sağlayarak:

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

Bundan kurtulabilirsiniz, ancak normalde uygulamalarınızda rastgele kod yürütmek için vektör istemezsiniz .


1

@Nadia Alramli cevabına ek olarak, Python'da yeni olduğum evalve kullanımın zamanlamaları nasıl etkileyeceğini kontrol etmeye istekli olduğum için, küçük bir program denedim ve aşağıdaki gözlemler vardı:

#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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.