Załóżmy, że mam funkcję, która robi rzeczy z plikiem tekstowym - na przykład czyta z niego i usuwa słowo „a”. Mógłbym albo przekazać nazwę pliku i obsłużyć otwieranie / zamykanie w funkcji, albo przekazywać otwarty plik i oczekiwać, że ktokolwiek go wywoła, zajmie się jego zamknięciem.
Pierwszy sposób wydaje się lepszym sposobem na zagwarantowanie, że żadne pliki nie zostaną otwarte, ale uniemożliwia mi używanie takich rzeczy jak obiekty StringIO
Drugi sposób może być trochę niebezpieczny - nie ma możliwości dowiedzenia się, czy plik zostanie zamknięty, czy nie, ale mógłbym używać obiektów podobnych do plików
def ver_1(filename):
with open(filename, 'r') as f:
return do_stuff(f)
def ver_2(open_file):
return do_stuff(open_file)
print ver_1('my_file.txt')
with open('my_file.txt', 'r') as f:
print ver_2(f)
Czy jeden z nich jest ogólnie preferowany? Czy ogólnie oczekuje się, że funkcja będzie zachowywać się na jeden z tych dwóch sposobów? A może powinien być po prostu dobrze udokumentowany, aby programista mógł odpowiednio wykorzystać tę funkcję?
źródło
your_function
tym względzie można zastosować opcjonalny argument „nazwa_strumienia” .Prawdziwe pytanie dotyczy kompletności. Czy twoja funkcja przetwarzania plików jest kompletnym przetwarzaniem pliku, czy jest to tylko jeden element w szeregu etapów przetwarzania? Jeśli jest on sam w sobie kompletny, możesz obudować cały dostęp do pliku w ramach funkcji.
Ma to bardzo przyjemną właściwość finalizowania zasobu (zamykania pliku) na końcu
with
instrukcji.Jeśli jednak istnieje potrzeba przetworzenia już otwartego pliku, wówczas rozróżnienie twojego
ver_1
iver_2
ma większy sens. Na przykład:Tego rodzaju jawne testowanie typów jest często lekceważone , szczególnie w takich językach, jak Java, Julia i Go, gdzie bezpośrednie wysyłanie oparte na typie lub interfejsie jest obsługiwane. Jednak w Pythonie nie ma obsługi języka dla wysyłania opartego na typach. Czasami możesz spotkać się z krytyką bezpośrednich testów typu w Pythonie, ale w praktyce jest to zarówno bardzo powszechne, jak i dość skuteczne. Umożliwia dużej ogólności funkcji, obsługując wszystkie typy danych, które mogą się pojawić, czyli „pisanie kaczek”. Zwróć uwagę na wiodący znak podkreślenia na
_ver_file
; jest to konwencjonalny sposób oznaczania funkcji „prywatnej” (lub metody). Chociaż technicznie można go wywołać bezpośrednio, sugeruje, że funkcja nie jest przeznaczona do bezpośredniego zużycia zewnętrznego.Aktualizacja 2019: Biorąc pod uwagę najnowsze aktualizacje w Pythonie 3, na przykład ścieżki są teraz potencjalnie przechowywane jako
pathlib.Path
obiekty nie tylkostr
lubbytes
(3.4+), a podpowiedzi typu zmieniły się z ezoterycznych na główny nurt (około 3.6+, choć wciąż aktywnie się rozwija), oto: zaktualizowany kod, który uwzględnia te postępy:źródło
read
czegoś, co może być podobne do pliku lub wywołanieopen(fileobj, 'r')
i przechwycenieTypeError
iffileobj
nie jest łańcuchem.ver
niezależne funkcjonowanie typu.ver
Jak można powiedzieć, może być również możliwe wdrożenie za pomocą pisania kaczego. Ale generowanie następnie wychwytywania wyjątków jest wolniejsze niż prosta kontrola typu, a IMO nie przynosi żadnej szczególnej korzyści (jasności, ogólności itp.) Z mojego doświadczenia, pisanie kaczek jest niesamowite „na dużą skalę”, ale neutralne na przeciwne do zamierzonego na małych . ”hasattr(fileobj, 'read')
Badanie byłoby kaczka wpisywanie;isinstance(fileobj, str)
test nie jest. Oto przykład różnicy:isinstance
test kończy się niepowodzeniem z nazwami plików Unicode, ponieważu'adsf.txt'
nie jest tostr
. Testowałeś na zbyt konkretny typ. Test pisania na kaczce, bez względu na to, czy jest wywołany,open
czy jakąś hipotetycznądoes_this_object_represent_a_filename
funkcję, nie miałby tego problemu.is_instance(x, str)
raczej czegoś podobnegois_instance(x, string_types)
, zstring_types
odpowiednio ustawionym działaniem w PY2 i PY3. Biorąc pod uwagę coś, co trzeszczy jak struna,ver
zareaguje prawidłowo; biorąc pod uwagę coś, co kwakuje jak plik, to samo. Do użytkownika over
, nie byłoby żadnej różnicy - poza tym, że realizacja inspekcja typ będzie działać szybciej. Kaczki purystów: nie krępuj się.Jeśli podasz nazwę pliku zamiast uchwytu pliku, nie ma gwarancji, że drugi plik jest tym samym plikiem, co pierwszy plik po otwarciu; może to prowadzić do błędów poprawności i dziur bezpieczeństwa.
źródło
Chodzi o własność i odpowiedzialność za zamknięcie pliku. Możesz przekazać uchwyt strumienia lub pliku lub cokolwiek innego, co powinno zostać w pewnym momencie zamknięte / zbywane, na inną metodę, o ile upewnisz się, że jest jasne, kto jest jego właścicielem, i że po zakończeniu zostanie ono zamknięte przez właściciela . Zazwyczaj wiąże się to z konstrukcją typu wypróbowanie na końcu lub wzorem jednorazowym.
źródło
Jeśli zdecydujesz się przekazać otwarte pliki, możesz zrobić coś takiego, ale NIE masz dostępu do nazwy pliku w funkcji, która zapisuje do pliku.
Zrobiłbym to, gdybym chciał mieć klasę, która byłaby w 100% odpowiedzialna za operacje na plikach / strumieniach oraz inne klasy lub funkcje, które byłyby naiwne i nie spodziewałyby się otwierania lub zamykania wspomnianych plików / strumieni.
Pamiętaj, że menedżerowie kontekstu działają jak klauzula „wreszcie”. Jeśli więc w funkcji zapisującej zostanie zgłoszony wyjątek, plik zostanie zamknięty bez względu na wszystko.
źródło
with open
? Jak to rozwiązuje kwestię używania nazw plików w porównaniu z obiektami podobnymi do plików?with open
, prawda? A to, za czym skutecznie opowiadasz się, to funkcja, która używa tylko obiektów podobnych do plików i nie dba o to, skąd pochodzi?