क्या मुझे खोलने के लिए फ़ाइलनाम में पास होना चाहिए, या फ़ाइलों को खोलना चाहिए?


53

मान लीजिए कि मेरे पास एक फ़ंक्शन है जो पाठ फ़ाइल के साथ चीजें करता है - उदाहरण के लिए इसे पढ़ता है और 'a' शब्द को हटाता है। मैं या तो इसे एक फ़ाइल नाम दे सकता हूं और फ़ंक्शन में उद्घाटन / समापन को संभाल सकता हूं, या मैं इसे खोली गई फ़ाइल को पास कर सकता हूं और उम्मीद करता हूं कि जो कोई भी इसे कॉल करेगा वह इसे बंद करने से निपटेगा।

पहला तरीका ऐसा लगता है कि गारंटी के लिए कोई बेहतर तरीका नहीं है कि कोई फाइल खुली न रह जाए, लेकिन मुझे स्ट्रिंगरियो जैसी चीजों के इस्तेमाल से रोकता है

दूसरा तरीका थोड़ा खतरनाक हो सकता है - यह जानने का कोई तरीका नहीं है कि फ़ाइल बंद हो जाएगी या नहीं, लेकिन मैं फ़ाइल जैसी वस्तुओं का उपयोग करने में सक्षम होऊंगा

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)

क्या इनमें से एक को आमतौर पर पसंद किया जाता है? क्या आमतौर पर यह उम्मीद की जाती है कि एक फ़ंक्शन इन दो तरीकों में से एक में व्यवहार करेगा? या यह सिर्फ अच्छी तरह से प्रलेखित किया जाना चाहिए कि प्रोग्रामर उपयुक्त रूप में फ़ंक्शन का उपयोग कर सकता है?

जवाबों:


39

सुविधाजनक इंटरफेस अच्छा है, और कभी-कभी जाने का रास्ता भी। हालांकि, अधिकांश समय अच्छी रचनाशीलता सुविधा की तुलना में अधिक महत्वपूर्ण होती है , क्योंकि एक संयोज्य अमूर्त हमें इसके ऊपर अन्य (incl। सुविधा आवरण) को लागू करने की अनुमति देता है।

फ़ाइलों का उपयोग करने के लिए आपके फ़ंक्शन का सबसे सामान्य तरीका एक खुली फ़ाइल हैंडल को पैरामीटर के रूप में लेना है, क्योंकि यह फ़ाइल हैंडल का उपयोग करने की अनुमति देता है जो कि फाइलसिस्टम का हिस्सा नहीं हैं (जैसे पाइप, सॉकेट,…):

def your_function(open_file):
    return do_stuff(open_file)

यदि with open(filename, 'r') as f: result = your_function(f)आपके उपयोगकर्ताओं से पूछना वर्तनी बहुत अधिक है, तो आप निम्न में से एक समाधान चुन सकते हैं:

  • your_functionपैरामीटर के रूप में एक खुली फ़ाइल या एक फ़ाइल नाम लेता है। यदि यह एक फ़ाइल नाम है, तो फ़ाइल को खोला और बंद किया जाता है, और अपवाद प्रचारित किया जाता है। यहाँ अस्पष्टता के साथ एक मुद्दा है जिसे नामांकित तर्कों का उपयोग करके काम किया जा सकता है।
  • एक साधारण आवरण प्रदान करें जो फ़ाइल को खोलने का ध्यान रखता है, जैसे

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

    मैं आमतौर पर एपीआई ब्लोट जैसे कार्यों का अनुभव करता हूं, लेकिन यदि वे आमतौर पर उपयोग की जाने वाली कार्यक्षमता प्रदान करते हैं, तो प्राप्त सुविधा एक पर्याप्त मजबूत तर्क है।

  • with openकार्यक्षमता को किसी अन्य कंपोज़ेबल फ़ंक्शन में लपेटें :

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

    के रूप में with_file(name, your_function)या अधिक जटिल मामलों में उपयोग किया जाता हैwith_file(name, lambda f: some_function(1, 2, f, named=4))


6
इस दृष्टिकोण का एकमात्र दोष यह है कि कभी-कभी फ़ाइल जैसी वस्तु के नाम की आवश्यकता होती है, उदाहरण के लिए त्रुटि रिपोर्टिंग के लिए: अंतिम उपयोगकर्ता "त्रुटि में foo.cfg (12)" के बजाय "त्रुटि में <स्ट्रीम @ 0x03fn2bb6" त्रुटि पसंद करते हैं (12) "। your_functionइस संबंध में एक वैकल्पिक "स्ट्रीम_नाम" तर्क का उपयोग किया जा सकता है।

22

वास्तविक प्रश्न संपूर्णता में से एक है। क्या आपकी फाइल प्रोसेसिंग फाइल की पूरी प्रोसेसिंग का काम करती है, या यह प्रोसेसिंग स्टेप्स की श्रृंखला में सिर्फ एक टुकड़ा है? यदि यह अपने आप में पूर्ण है, तो किसी फ़ंक्शन के भीतर सभी फ़ाइल एक्सेस को एनकैप्सुलेट करने के लिए स्वतंत्र महसूस करें।

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

यह withकथन के अंत में संसाधन (फ़ाइल को बंद करना) को अंतिम रूप देने की बहुत अच्छी संपत्ति है ।

यदि फिर भी पहले से खुली हुई फ़ाइल को संसाधित करने की आवश्यकता है, तो आपका भेद ver_1और ver_2अधिक समझ में आता है। उदाहरण के लिए:

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)

इस तरह के स्पष्ट प्रकार के परीक्षण अक्सर , विशेष रूप से जावा, जूलिया, और गो जैसी भाषाओं में किए जाते हैं, जहां टाइप- या इंटरफ़ेस-आधारित प्रेषण सीधे समर्थित है। हालांकि, पायथन में, टाइप-आधारित प्रेषण के लिए कोई भाषा समर्थन नहीं है। आप कभी-कभी पाइथन में प्रत्यक्ष प्रकार-परीक्षण की आलोचना देख सकते हैं, लेकिन व्यवहार में यह अत्यंत सामान्य और काफी प्रभावी दोनों है। यह एक फ़ंक्शन को सामान्यता का एक उच्च स्तर रखने में सक्षम बनाता है, जो कुछ डेटाटाइप्स को इसके रास्ते में आने की संभावना है, संभालती है, उर्फ ​​"टाइपिंग"। प्रमुख अंडरस्कोर पर ध्यान दें _ver_file; यह एक "निजी" फ़ंक्शन (या विधि) को नामित करने का एक पारंपरिक तरीका है। जबकि इसे तकनीकी रूप से सीधे कहा जा सकता है, यह सुझाव देता है कि प्रत्यक्ष बाहरी खपत के लिए फ़ंक्शन का इरादा नहीं है।


2019 अद्यतन: यह देखते हुए अजगर 3 में हाल ही में अद्यतन, उदाहरण के लिए पथ अब संभावित जमा हो जाती है कि के रूप में pathlib.Pathन केवल वस्तुओं strया bytes(3.4+), और उस प्रकार हिंट (, लगभग 3.6+ हालांकि अभी भी सक्रिय रूप से विकसित हो रहा है) मुख्य धारा में गूढ़ से चला गया है, यहाँ है अद्यतन कोड जो इन अग्रिमों को ध्यान में रखते हैं:

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
बतख टाइपिंग इस बात पर आधारित होगी कि आप वस्तु के साथ क्या कर सकते हैं, इसके बजाय कि इसका प्रकार क्या है। उदाहरण के लिए, readकिसी ऐसी चीज़ पर कॉल करने की कोशिश करना जो फ़ाइल की तरह हो सकती है, या यदि स्ट्रिंग नहीं है तो कॉल करना open(fileobj, 'r')और पकड़ना । TypeErrorfileobj
user2357112

आप उपयोग में बतख टाइपिंग के लिए बहस कर रहे हैं । उदाहरण प्रभाव में बतख टाइपिंग प्रदान करता है - यह है, उपयोगकर्ताओं को verऑपरेशन के प्रकार से स्वतंत्र मिलता है । verजैसा कि आप कहते हैं, बतख टाइपिंग के माध्यम से इसे लागू करना भी संभव हो सकता है । लेकिन फिर साधारण अपवादों की तुलना में अपवादों को पकड़ना धीमा होता है, और IMO से कोई विशेष लाभ (स्पष्टता, व्यापकता, आदि) प्राप्त नहीं होता है। मेरे अनुभव में, बतख टाइपिंग बहुत बड़ी है, "लेकिन छोटे में" उल्टी करने के लिए तटस्थ " । "
जोनाथन यूनिस

3
नहीं, आप जो कर रहे हैं वह अभी भी बतख टाइपिंग नहीं है। एक hasattr(fileobj, 'read')परीक्षण बतख टाइपिंग होगा; एक isinstance(fileobj, str)परीक्षण नहीं है। यहाँ अंतर का एक उदाहरण है: isinstanceयूनिकोड फ़ाइलनाम के साथ परीक्षण विफल हो जाता है, क्योंकि u'adsf.txt'ए नहीं है str। आपने बहुत विशिष्ट प्रकार के लिए परीक्षण किया है। एक बतख टाइपिंग टेस्ट, चाहे कॉलिंग openया कुछ काल्पनिक does_this_object_represent_a_filenameफ़ंक्शन के आधार पर , उस समस्या को नहीं होगा।
user2357112

1
यदि कोड एक व्याख्यात्मक उदाहरण के बजाय उत्पादन कोड थे, तो मुझे भी यह समस्या नहीं होगी, क्योंकि मैं PY2 और PY3 में उचित संचालन के लिए ठीक से सेट होने के साथ, is_instance(x, str)बल्कि कुछ का उपयोग नहीं करूंगा। कुछ ऐसा है जो एक स्ट्रिंग की तरह होता है , ठीक से प्रतिक्रिया करेगा; कुछ है कि एक फ़ाइल की तरह quacks दिया, वही। के उपयोगकर्ता के लिए , कोई अंतर नहीं होगा - सिवाय इसके कि टाइप निरीक्षण कार्यान्वयन तेजी से चलेगा। बतख शुद्धतावादी: असहमत महसूस करते हैं। is_instance(x, string_types)string_typesverver
जोनाथन यूनिस

5

यदि आप फ़ाइल के हैंडल के बजाय फ़ाइल का नाम पास करते हैं, तो इस बात की कोई गारंटी नहीं है कि दूसरी फ़ाइल उसी फ़ाइल के समान है, जिसे खोला जाता है; इससे कीड़े और सुरक्षा छिद्र सही हो सकते हैं।


1
सच। लेकिन इसे किसी अन्य ट्रेडऑफ़ के साथ काउंटरबेल्ट किया जाना चाहिए: यदि आप फ़ाइल हैंडल के आसपास से गुजरते हैं, तो सभी पाठकों को फ़ाइल तक अपनी पहुंच का समन्वय करना होगा, क्योंकि प्रत्येक में "वर्तमान फ़ाइल स्थिति" को स्थानांतरित करने की संभावना है।
जोनाथन यूनिस

@JonathanEunice: किस अर्थ में समन्वय करें? उन्हें बस इतना करना होगा कि वे जहां चाहें वहां फ़ाइल की स्थिति निर्धारित करें।
मेहरदाद

1
यदि फ़ाइल को पढ़ने वाली कई संस्थाएँ हैं, तो निर्भरताएँ हो सकती हैं। एक को शुरू करने की आवश्यकता हो सकती है जहां एक और एक छोड़ दिया (या पूर्ववर्ती रीडिंग द्वारा पढ़े गए डेटा द्वारा परिभाषित जगह में)। इसके अलावा, पाठक विभिन्न थ्रेड्स में चल रहे हैं, जो कि कीड़े के अन्य समन्वय के डिब्बे खोल सकते हैं। पास-पास फ़ाइल ऑब्जेक्ट वैश्विक स्थिति से अवगत हो जाते हैं, जिसमें सभी मुद्दे (साथ ही साथ लाभ) होते हैं।
जोनाथन यूनिस

1
यह फ़ाइल पथ के चारों ओर नहीं है जो कुंजी है। यह एक फ़ंक्शन (या वर्ग, विधि, या नियंत्रण के अन्य नियंत्रण) है जो "फ़ाइल के पूर्ण प्रसंस्करण" के लिए जिम्मेदारी लेता है। यदि फ़ाइल एक्सेस कहीं से एनकैप्सुलेटेड हैं , तो आपको ओपन फाइल हैंडल जैसे म्यूटेबल ग्लोबल स्टेट के आसपास से गुजरने की जरूरत नहीं है।
जोनाथन यूनिस

1
खैर, हम तब असहमत हो सकते हैं। मैं कह रहा हूं कि डिजाइनों के लिए एक निश्चित नकारात्मक पहलू है जो कि चमकता हुआ वैश्विक राज्य है। कुछ फायदे भी हैं। इस प्रकार, एक "ट्रेडऑफ़।" फ़ाइल पथों को पास करने वाले डिज़ाइन अक्सर I / O को एक ध्वस्त तरीके से गिरते हुए झपटते हैं। मैं इसे एक लाभप्रद युग्मन के रूप में देखता हूं। YMMV।
जोनाथन यूनिस

1

यह स्वामित्व और फ़ाइल को बंद करने की जिम्मेदारी के बारे में है। आप एक स्ट्रीम या फ़ाइल हैंडल या किसी भी चीज़ को बंद कर सकते हैं, जिसे किसी बिंदु पर किसी अन्य विधि पर बंद / निपटाया जाना चाहिए, जब तक आप यह सुनिश्चित कर लेते हैं कि यह स्पष्ट है कि इसका मालिक कौन है और निश्चित रूप से यह स्वामी द्वारा बंद किया जाएगा जब आप कर रहे हैं । इसमें आमतौर पर एक कोशिश अंत में निर्माण या डिस्पोजेबल पैटर्न शामिल है।


-1

यदि आप खुली फ़ाइलों को पास करना चुनते हैं, तो आप निम्न BUT की तरह कुछ कर सकते हैं, फ़ाइल में लिखने वाले फ़ंक्शन में फ़ाइल नाम तक आपकी कोई पहुंच नहीं है।

मैं ऐसा करूंगा यदि मैं एक ऐसा वर्ग रखना चाहता था जो फ़ाइल / स्ट्रीम संचालन और अन्य वर्गों या फ़ंक्शन के लिए 100% जिम्मेदार था जो अनुभवहीन होगा और उक्त फ़ाइलों / धाराओं को खोलने या बंद करने की उम्मीद नहीं करेगा।

याद रखें कि संदर्भ प्रबंधक अंत में क्लॉज होने की तरह काम करते हैं। इसलिए यदि लेखक फ़ंक्शन में एक अपवाद को फेंक दिया जाता है, तो फ़ाइल बंद होने वाली है, चाहे जो भी हो।

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)

यह कैसे बेहतर है / केवल उपयोग करने से अलग है with open? यह फ़ाइल नाम जैसी वस्तुओं के फ़ाइल नाम का उपयोग करने के प्रश्न को कैसे संबोधित करता है?
दान्नो

यह आपको फ़ाइल / स्ट्रीम ओपन / क्लोज व्यवहार को छिपाने का एक तरीका दिखाता है। जैसा कि आप टिप्पणियों में स्पष्ट रूप से देख सकते हैं कि यह आपको "लेखक" के लिए पारदर्शी स्ट्रीम / फ़ाइल खोलने से पहले तर्क जोड़ने का तरीका देता है। "लेखक" दूसरे पैकेज के वर्ग का एक तरीका हो सकता है। संक्षेप में यह खुले का एक आवरण है। इसके अलावा, उत्तर देने और मतदान करने के लिए धन्यवाद।
वील्स

यह व्यवहार with openहालांकि, पहले से ही संभाला हुआ है, है ना? और जिस चीज की आप प्रभावी रूप से वकालत कर रहे हैं, वह एक ऐसा फंक्शन है जो केवल फाइल जैसी वस्तुओं का उपयोग करता है, और परवाह नहीं करता है कि यह कहां से आया है?
दन्नन्नो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.