Açılacak dosya adlarını mı girmeliyim yoksa dosyaları açmalı mıyım?


53

Diyelim ki bir metin dosyasıyla işleri yapan bir fonksiyonum var - örneğin ondan okuyor ve 'a' kelimesini kaldırıyor. Bir dosya adı iletebilir ve işlevdeki açma / kapama işlemini gerçekleştirebilir ya da açılan dosyayı iletebilir ve çağıran kişinin onu kapatmakla ilgileneceğini tahmin edebilirim.

İlk yöntem, hiçbir dosyanın açık bırakılmadığını garanti etmenin daha iyi bir yolu gibi görünüyor, ancak StringIO nesneleri gibi şeyler kullanmamı engelliyor

İkinci yol biraz tehlikeli olabilir - dosyanın kapanıp kapanmayacağını bilmenin bir yolu yok, ama dosyaya benzer nesneleri kullanabileceğim

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)

Bunlardan biri genellikle tercih edilir mi? Genelde bir fonksiyonun bu iki yoldan biriyle davranması bekleniyor mu? Yoksa programcının işlevi uygun şekilde kullanabilmesi için iyi bir şekilde belgelenmesi gerekir mi?

Yanıtlar:


39

Kullanışlı arayüzler güzeldir ve bazen de gider. Bununla birlikte, çoğu zaman iyi bir beste edilebilirlik kolaylıktan daha önemlidir , çünkü bir beste edilebilir soyutlama başka fonksiyonellik (kolaylık sarmalayıcılar dahil) üzerine uygulamamıza izin verir.

İşlevinizin dosyaları kullanmasının en genel yolu, açık dosya tanıtıcısını parametre olarak kullanmaktır, çünkü dosya sisteminin bir parçası olmayan dosya tanıtıcılarını da kullanmasına izin verir (örneğin, borular, soketler,…):

def your_function(open_file):
    return do_stuff(open_file)

Heceleme with open(filename, 'r') as f: result = your_function(f), kullanıcılarınızdan sormayacak kadar fazlaysa, aşağıdaki çözümlerden birini seçebilirsiniz:

  • your_functionparametre olarak açık bir dosyayı veya bir dosya adını alır. Bir dosya adıysa, dosya açılır ve kapanır ve istisnalar yayılır. Burada, isimlendirilmiş argümanlar kullanılarak çözülebilecek belirsizliği olan bir sorun var.
  • Örneğin dosyayı açmayı önemseyen basit bir sarıcı kullanın.

    def your_function_filename(file):
        with open(file, 'r') as f:
            return your_function(f)

    Genelde API şişirme gibi işlevleri algılıyorum, ancak yaygın olarak kullanılan işlevler sağlarlarsa, kazanılan kolaylık yeterince güçlü bir argümandır.

  • with openİşlevselliği başka bir düzenlenebilir işleve sarın :

    def with_file(filename, callback):
        with open(filename, 'r') as f:
            return callback(f)

    olarak with_file(name, your_function)veya daha karmaşık durumlarda kullanılırwith_file(name, lambda f: some_function(1, 2, f, named=4))


6
Bu yaklaşımın tek dezavantajı, bazen dosyaya benzeyen nesnenin adına ihtiyaç duyulmasıdır, örneğin hata bildirimi için: son kullanıcılar "<< (12)". your_functionBu konuda kullanılabilecek isteğe bağlı bir "stream_name" argümanı .

22

Asıl soru, bir bütünlüktür. Dosya işleme fonksiyonunuz dosyanın tam olarak işlenmesi mi yoksa işlem adımlarının zincirinde sadece bir parça mı? Tamamen kendi başına tamamlanmışsa, bir işlev içindeki tüm dosya erişimini almaktan çekinmeyin.

def ver(filepath):
    with open(filepath, "r") as f:
        # do processing steps on f
        return result

Bu, withifadenin sonunda kaynağı sonlandırma (dosyayı kapatma) konusunda çok hoş bir özelliğe sahiptir .

Bununla birlikte, halihazırda açık olan bir dosyayı işlemeye ihtiyaç duyulması muhtemelse, o zaman sizden ayrılın ver_1ve ver_2daha anlamlı olun. Örneğin:

def _ver_file(f):
    # do processing steps on f
    return result

def ver(fileobj):
    if isinstance(fileobj, str):
        with open(fileobj, 'r') as f:
            return _ver_file(f)
    else:
        return _ver_file(fileobj)

Bu tür açık tür testler , özellikle tür veya arabirim tabanlı gönderimin doğrudan desteklendiği yerlerde, özellikle Java, Julia ve Go gibi dillerde kaşlarını çattı . Ancak Python'da tür tabanlı gönderme için dil desteği yoktur. Python'da bazen doğrudan tip testi eleştirisini görebilirsiniz, ancak pratikte hem oldukça yaygın hem de oldukça etkili. Bir fonksiyonun yüksek derecede bir genelliğe sahip olmasını sağlar, veri tiplerinin yoluna girme olasılığı ne olursa olsun, yani "ördek yazma". Önde gelen alt çizgiyi not edin _ver_file; bu, "özel" bir işlev (veya yöntem) belirlemenin geleneksel bir yoludur. Teknik olarak doğrudan çağrılabilse de, işlevin doğrudan dış tüketim için tasarlanmadığını düşündürmektedir.


2019 güncellemesi: Python 3'teki son güncellemeler göz önüne alındığında, örneğin, yolların şimdi potansiyel olarak pathlib.Pathsadece strveya bytes(3.4+) değil de nesne olarak depolandığı ve bu tür ipuçlarının ezoterikten ana akıma (yaklaşık 3.6+, ancak hala aktif olarak gelişmekte olan) gittiği belirtildi. Bu gelişmeleri hesaba katan güncellenmiş kod:

from pathlib import Path
from typing import IO, Any, AnyStr, Union

Pathish = Union[AnyStr, Path]  # in lieu of yet-unimplemented PEP 519
FileSpec = Union[IO, Pathish]

def _ver_file(f: IO) -> Any:
    "Process file f"
    ...
    return result

def ver(fileobj: FileSpec) -> Any:
    "Process file (or file path) f"
    if isinstance(fileobj, (str, bytes, Path)):
        with open(fileobj, 'r') as f:
            return _ver_file(f)
    else:
        return _ver_file(fileobj)

1
Ördek yazımı, türü ile değil, nesneyle neler yapabileceğinizi temel alarak test eder. Örneğin, readdosyaya benzer bir şeyi aramaya çalışmak ya da bir dize değilse arama open(fileobj, 'r')ve yakalama . TypeErrorfileobj
user2357112

Ördek kullanımında kullanılıyor . Örnek ördek yazarak sağlayan etkisi , kullanıcıların olsun -Öyle vertip çalışma bağımsız. verDediğiniz gibi, ördek yazarak uygulamak da mümkün olabilir . Ancak, istisnaları yakalamak ve sonra istisnaları yakalamak basit tip muayenesinden daha yavaştır ve IMO özel bir fayda sağlamaz (netlik, genellik, vb.) ."
Jonathan Eunice

3
Hayır, yaptığın şey hala ördek yazmıyor. Bir hasattr(fileobj, 'read')test ördek yazmaya; bir isinstance(fileobj, str)test değil. İşte farkın bir örneği: isinstancetest, unicode dosya adlarıyla başarısız oluyor, çünkü u'adsf.txt'bir str. Çok spesifik bir tip için test yaptınız. Bir ördek yazma testi, ister arama openister bir varsayımsal does_this_object_represent_a_filenamefonksiyona dayalı olsun , bu sorunu yaşamazdı.
user2357112

1
Kod açıklayıcı örnek ziyade üretim kodu olsaydım kullanmak ister çünkü, ben de, böyle bir sorun olmazdı is_instance(x, str)gibi oldukça bir şey ama is_instance(x, string_types)birlikte, string_typesdüzgün PY2 ve PY3 genelinde düzgün çalışması için ayarlayın. Bir dize gibi sersemleten bir şey göz önüne alındığında, verdoğru tepki verirdi; Aynı dosya gibi quacks bir şey verildi. Bir kullanıcı için ver, hiçbir fark olmaz - tür denetim uygulamasının daha hızlı çalışması dışında. Ördek temizleyicileri: katılmamakta serbestsin.
Jonathan Eunice

5

Dosya adını, dosya tanıtıcısı yerine etrafa iletirseniz, ikinci dosyanın açıldığında ilk dosyayla aynı dosya olduğuna dair hiçbir garanti yoktur; bu doğruluk hataları ve güvenlik delikleri yol açabilir.


1
Doğru. Ancak bunun başka bir takasla dengelenmesi gerekir: Bir dosya tanıtıcısını dolaştırırsanız, tüm okuyucular dosyaya erişimlerini koordine etmelidir, çünkü her birinin "geçerli dosya konumu" nu taşıması muhtemeldir.
Jonathan Eunice

@JonathanEunice: Hangi anlamda koordineli? Tek yapmaları gereken, dosya konumunu istedikleri yerde olacak şekilde ayarlamak.
Mehrdad

1
Dosyayı okuyan birden fazla varlık varsa, bağımlılıklar olabilir. Birinin bir başkasının kaldığı yerden başlaması gerekebilir (veya önceki bir okuma tarafından okunan verilerle tanımlanan bir yerde). Ayrıca, okuyucular farklı ipliklerle çalışarak solucanların diğer koordinasyon kutularını açıyor olabilir. Etrafından dolaştırılan dosya nesneleri, gerektirdiği tüm sorunların (yanı sıra faydaların) yanı sıra küresel duruma maruz kalır.
Jonathan Eunice

1
Anahtar olan dosya yolundan geçmiyor. Bir işleve (veya sınıf, yöntem veya diğer kontrol odağına) sahip olması "dosyanın tümüyle işlenmesi" sorumluluğunu üstlenir. Dosya erişimleri bir yerde saklanıyorsa , açık dosya tanıtıcıları gibi değişken global durumun etrafından geçmeniz gerekmez.
Jonathan Eunice

1
Öyleyse aynı fikirde değiliz. Diyelim ki değişken küresel devlet etrafından kayganca geçen tasarımların bir dezavantajı var. Bazı avantajlar da var. Böylece, bir "tradeoff". Dosya yollarını geçen tasarımlar genellikle G / Ç'yi bir kapsüllenmiş biçimde baskın düştü. Bunu avantajlı bir bağlantı olarak görüyorum. YMMV.
Jonathan Eunice

1

Bu, sahiplik ve dosyayı kapatma sorumluluğu ile ilgilidir. Sen bunu sahibi ve belirli kim açıktır sürece emin olarak, başka bir yönteme bir noktada tanzim kapatılmalıdır bir dere veya dosya kolu ya da her türlü zımbırtı / aktarabilir olacak işiniz bittiğinde sahibi tarafından kapatılacak . Bu genellikle bir try-finally yapısını veya tek kullanımlık paterni içerir.


-1

Açık dosyaları geçmeyi seçerseniz, aşağıdaki ANCAK gibi bir şey yapabilirsiniz, ancak dosyada yazan işlevdeki dosya adına erişiminiz yoktur.

Bunu, dosya / akış işlemlerinden ve% 25 saflıktan sorumlu olacak ve söz konusu dosyaları / akışları açması veya kapatması beklenmeyen diğer sınıflar veya işlevlerden sorumlu bir sınıfa sahip olmak istersem yapardım.

İçerik yöneticilerinin nihayet bir cümle kuralı gibi çalıştığını unutmayın. Bu nedenle, yazar işlevinde bir istisna atılırsa, dosya ne olursa olsun kapatılacak.

import contextlib

class FileOpener:

    def __init__(self, path_to_file):
        self.path_to_file = path_to_file

    @contextlib.contextmanager
    def open_write(self):
        # ...
        # Here you can add code to create the directory that will accept the file.
        # ...
        # And you can add code that will check that the file does not exist 
        # already and maybe raise FileExistsError
        # ...
        try:            
            with open(self.path_to_file, "w") as file:
                print(f"open_write: has opened the file with id:{id(file)}")            
                yield file                
        except IOError:
            raise
        finally:
            # The try/catch/finally is not mandatory (except if you want to manage Exceptions in an other way, as file objects have predefined cleanup actions 
            # and when used with a 'with' ie. a context manager (not the decorator in this example) 
            # are closed even if an error occurs. Finally here is just used to demonstrate that the 
            # file was really closed.
            print(f"open_write: has closed the file with id:{id(file)} - {file.closed}")        


def writer(file_open, data, raise_exc):
    with file_open() as file:
        print("writer: started writing data.")
        file.write(data)
        if raise_exc:
            raise IOError("I am a broken data cable in your server!")
        print("writer: wrote data.")
    print("writer: finished.")

if __name__ == "__main__":
    fo = FileOpener('./my_test_file.txt')    
    data = "Hello!"  
    raise_exc = False  # change me to True and see that the file is closed even if an Exception is raised.
    writer(fo.open_write, data, raise_exc)

Bu sadece kullanmaktan daha iyi / farklı nasıl with open? Bu, dosya benzeri nesnelere karşı dosya isimlerinin kullanılması sorununu nasıl ele almaktadır?
Dannnno

Bu, dosya / akış açma / kapama davranışını gizlemenin bir yolunu gösterir. Yorumlarda açıkça görebileceğiniz gibi, size "yazar" için şeffaf olan akışı / dosyayı açmadan önce mantık eklemenin yolunu sunar. "Yazar", başka bir paketin sınıfının bir yöntemi olabilir. Özünde açık bir sarıcıdır. Ayrıca yanıtladığınız ve oy verdiğiniz için teşekkür ederiz.
Vls

Bu davranış zaten with openolsa da ele alınır , değil mi? Ve etkili bir şekilde savunuculuğunu yaptığınız şey sadece dosya benzeri nesneler kullanan ve nereden geldiği ile ilgilenmeyen bir işlevdir?
Dannnno
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.