7

Aby wygenerować wynik, funkcja zwykle używa tylko wartości swoich argumentów. Istnieją jednak również przypadki, w których funkcja generuje swoje wyniki, czyta coś z systemu plików lub z bazy danych lub z Internetu. Chciałbym mieć prosty i niezawodny sposób, aby coś takiego się nie stało.Jak zapewnić, że funkcja Pythona generuje swoje dane wyjściowe tylko w oparciu o dane wejściowe?

Jednym ze sposobów, jaki widzę, jest utworzenie białej listy bibliotek Pythona, które mogą być używane do odczytu z systemu plików, bazy danych lub sieci. Ale jeśli to jest droga, gdzie mogę dostać tę (potencjalnie ogromną) listę. Co więcej, nie chcę wyłączać całej biblioteki tylko dlatego, że można jej użyć do odczytu z systemu plików. Na przykład chcę, aby użytkownicy mogli korzystać z biblioteki pand (do przechowywania i manipulowania danymi tabelarycznymi). Po prostu nie chcę, aby mogły korzystać z tej biblioteki do odczytu danych z systemu plików.

Czy istnieje rozwiązanie tego problemu?

+1

Wykonaj kopię zapasową kroku. * Dlaczego * chcesz uniemożliwić komuś czytanie z zewnętrznego źródła? – chepner

+0

Istnieje wiele powodów. Przede wszystkim chcę mieć pewność, że w przyszłości funkcja wygeneruje taki sam wynik jak dziś. Po drugie, generalnie uważam, że jest to "brzydkie" rozwiązanie, gdy funkcja gdzieś coś czyta. Powinien widzieć tylko to, co wyraźnie otrzymuje jako dane wejściowe. Jeśli coś powinno zostać odczytane z pliku lub bazy danych, należy go odczytać poza funkcją i przekazać do funkcji jako jedno z jego wejść. – Roman

+0

Więc chcesz użyć kodu, któremu nie ufasz? –

Odpowiedz

8

Odpowiedź na to jest nr. To, czego szukasz, to funkcja testująca dla functional purity. Ale, jak wykazano w tym kodzie, nie ma sposobu, aby zagwarantować, że nie wywołuje się żadnych skutków ubocznych.

class Foo(object): 
    def __init__(self, x): 
     self.x = x 
    def __add__(self, y): 
     print("HAHAHA evil side effects here...") 
     # proceed to read a file and do stuff 
     return self 

# this looks pure... 
def f(x): return x + 1 

# but really... 
>>> f(Foo(1)) 
HAHAHA evil side effects here... 

Ze względu na kompleksowy sposób obiekty mogą dostosować swoje zachowanie (dostęp do pola, powołanie, operator przeciążenia itp), zawsze można przejść wejście sprawia, że ​​funkcję czystego nieczyste. Dlatego jedynymi czystymi funkcjami są te, które dosłownie nie robią nic z ich argumentami ... klasą funkcji, które są generalnie mniej użyteczne.

Oczywiście, jeśli można określić inne ograniczenia, staje się to łatwiejsze.

+0

W twoim przykładzie "złe efekty" zdarzają się, ponieważ użytkownik funkcji zrobił coś "złego" (użytkownik nazwał "dobrą" funkcję z "złym" argumentem). W moim przypadku jestem użytkownikiem tej funkcji. Tak więc nie będę wywoływał funkcji w "zły" sposób. Po prostu muszę mieć pewność, że funkcje, które ja użytkownik nie są "złe". – Roman

+0

To jest ważne, aby wiedzieć z wyprzedzeniem ... – PythonNut

+2

@Roman: Twoje wymagania były znacznie silniejsze pierwotnie. Napisałeś: "Chcę, aby użytkownicy mogli ... Po prostu nie chcę, aby mogli korzystać z tej biblioteki do odczytu danych z systemu plików." a teraz piszesz "Nie będę wywoływał funkcji w zły sposób". Wydaje się to niezwykłe. Czy wierzysz użytkownikowi, ale nie wierzysz w zainstalowane oprogramowanie? – hynekcer

4

Twoje wymagane ograniczenia mogą zostać złamane nawet po usunięciu wszystkich modułów i wszystkich funkcji. Kod może uzyskać dostęp do plików, jeśli może korzystać z atrybutów dowolnego prostego obiektu, np. liczby zero.

(0).__class__.__base__.__subclasses__()[40]('/etc/pas'+'swd') 

Indeks 40 jest indywidualny i bardzo typowe dla Pythona 2.7, ale indeks podklasy <type 'file'> mogą być łatwo znalezione:

[x for x in (1).__class__.__base__.__subclasses__()if'fi'+'le'in'%s'%x][0](
'/etc/pas'+'swd') 

Dowolna kombinacja białej liście i czarnej jest albo niebezpieczne i/lub zbyt restrykcyjne. pypy sandbox jest odporny na zasadzie bez kompromisów:

... To podproces można uruchomić dowolny niezaufane kodu Pythona, ale wszystko jego wejścia/wyjścia jest szeregowane do potoku stdin/stdout zamiast bezpośrednio wykonywane. Proces czyta zewnętrzna rury i decyduje, które polecenia są dozwolone, czy nie (Piaskownica), a nawet na nowo interpretuje je inaczej ...

także rozwiązanie oparte na seccomp funkcji jądra może być wystarczająco bezpieczne. (blog)


Chcę mieć pewność, że w przyszłości funkcja wygeneruje taką samą wyjścia jak dzisiaj.

Łatwo jest napisać funkcję, która ma twardych powtarzalne wyniki i nie można łatwo zapobiec:

class A(object): 
    "This can be any very simple class" 
    def __init__(self, x): 
     self.x = x 
    def __repr__(self): 
     return repr(self.x) 

def strange_function(): 
    # You get a different result probably everytimes. 
    return list(set(A(i) for i in range(20))) 

>>> strange_function() 
[1, 18, 12, 5, 16, 15, 8, 2, 14, 0, 6, 19, 13, 11, 10, 9, 17, 3, 7, 4] 
>>> strange_function() 
[0, 9, 14, 3, 17, 5, 6, 11, 8, 1, 15, 7, 12, 13, 2, 10, 16, 4, 19, 18] 

... nawet jeśli usuniesz everythng który zależy od czasu, liczba losowa generator, zlecenie oparte na funkcji skrótu itd., można też łatwo napisać funkcję, która czasami przekracza dostępną pamięć lub limit czasu, a czasami daje wynik.


EDIT:
Roman, napisał niedawno, że jesteś pewien, że można wierzyć użytkownikowi. Wtedy istnieje realistyczne rozwiązanie. Jest to weryfikacja danych wejściowych i wyjściowych z funkcji poprzez zapisanie ich w pliku i weryfikacja na maszynie wirtualnej z uruchomionym zdalnym IPython notebook (niezły krótki film instruktażowy, obsługa zdalnego komputera po wyjęciu z pudełka, ponowne uruchomienie usługi backendu przez sieć WWW menu dokumentu z przeglądarki w ciągu jednej sekundy, bez utraty danych (wejścia/wyjścia) w notatniku (dokument HTML), ponieważ jest tworzony dynamicznie krok po kroku przez naszą aktywność wyzwalającą javascript, który wywołuje zdalny backend).

Nie musisz być zainteresowany połączeniami wewnętrznymi, a jedynie globalnym wejściem i wyjściem, dopóki nie znajdziesz różnic. Maszyna wirtualna powinna być w stanie zweryfikować wyniki niezależnie i odtwarzalne. Skonfiguruj zaporę, aby maszyna akceptowała połączenia od ciebie, ale nie może inicjować połączenia wychodzącego. Skonfiguruj system plików, aby żaden bieżący użytkownik nie mógł zapisać danych, a zatem nie są one obecne, z wyjątkiem komponentów oprogramowania. Wyłącz usługi baz danych. Sprawdź wprowadzanie/wyprowadzanie wyników w losowej kolejności lub uruchom dwie usługi IPython dla notebooków na różnych portach i wybierz losowy backend dla każdej linii poleceń w notebooku lub zrestartuj proces backendu często zanim cokolwiek ważnego. Jeśli znajdziesz różnicę, rozwiąż swój kod i napraw go.

Można go zautomatyzować bez "notatnika" tylko w przypadku zdalnej obsługi IPython, gdy nie jest wymagana interaktywność.

+1

Należy zauważyć, że można również uzyskać losowe liczby z losowego adresu pamięci obiektu. 'klasa A: pass; str (A)' – PythonNut

Powiązane problemy