ฉันควรส่งผ่านชื่อไฟล์เพื่อเปิดหรือเปิดไฟล์หรือไม่


53

สมมติว่าฉันมีฟังก์ชั่นที่ทำงานกับไฟล์ข้อความตัวอย่างเช่นอ่านจากมันและลบคำว่า 'a' ฉันสามารถผ่านชื่อไฟล์และจัดการเปิด / ปิดในฟังก์ชั่นหรือฉันจะผ่านมันไฟล์ที่เปิดอยู่และคาดหวังว่าใครก็ตามที่เรียกมันจะจัดการกับการปิดมัน

วิธีแรกดูเหมือนจะเป็นวิธีที่ดีกว่าในการรับประกันว่าไม่มีไฟล์ใดถูกเปิดทิ้งไว้ แต่ป้องกันไม่ให้ฉันใช้สิ่งต่าง ๆ เช่นวัตถุ StringIO

วิธีที่สองอาจเป็นอันตรายเล็กน้อย - ไม่มีทางรู้ว่าไฟล์จะถูกปิดหรือไม่ แต่ฉันจะสามารถใช้วัตถุเหมือนไฟล์

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

อินเทอร์เฟซที่สะดวกสบายดีและบางครั้งก็เป็นไปได้ อย่างไรก็ตามความสามารถในการเรียงความที่ดีส่วนใหญ่มีความสำคัญมากกว่าความสะดวกสบายเนื่องจากการเขียนเรียงความที่เป็นนามธรรมช่วยให้เราสามารถใช้ฟังก์ชั่นอื่น ๆ (รวมถึง.

วิธีทั่วไปที่สุดสำหรับฟังก์ชั่นของคุณในการใช้ไฟล์คือการใช้ตัวจัดการไฟล์แบบเปิดเป็นพารามิเตอร์เนื่องจากมันอนุญาตให้ใช้ตัวจัดการไฟล์ที่ไม่ได้เป็นส่วนหนึ่งของระบบไฟล์ (เช่นไพพ์, ซ็อกเก็ต, ... ):

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)
    

    ฉันมักจะรับรู้ฟังก์ชั่นเช่น API bloat แต่ถ้าพวกเขามีฟังก์ชั่นที่ใช้กันทั่วไปความสะดวกที่ได้รับนั้นเป็นข้อโต้แย้งที่แข็งแกร่งพอสมควร

  • ห่อ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)" มากกว่า "ข้อผิดพลาดใน <stream @ 0x03fd2bb6> (12)" อาร์กิวเมนต์ "stream_name" ที่เป็นตัวเลือกซึ่ง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)

ชนิดของการทดสอบประเภทอย่างชัดเจนมักจะขมวดคิ้วโดยเฉพาะอย่างยิ่งในภาษาเช่น Java, จูเลียและไปที่ TYPE- หรือเยี่ยงอย่างอินเตอร์เฟซที่ใช้สนับสนุนโดยตรง อย่างไรก็ตามใน Python ไม่มีภาษาที่รองรับสำหรับการแจกจ่ายตามประเภท คุณอาจเห็นคำวิจารณ์ของการทดสอบโดยตรงใน Python เป็นครั้งคราว แต่ในทางปฏิบัติมันเป็นเรื่องธรรมดาและมีประสิทธิภาพมาก มันช่วยให้ฟังก์ชั่นมีระดับสูงโดยทั่วไปการจัดการประเภทข้อมูลใดก็ตามที่มีแนวโน้มว่าจะเกิดขึ้นหรือที่เรียกว่า "การพิมพ์เป็ด" สังเกตการขีดเส้นใต้นำบน_ver_file; นั่นเป็นวิธีดั้งเดิมในการกำหนดฟังก์ชั่น "ส่วนตัว" (หรือวิธีการ) แม้ว่าจะสามารถเรียกใช้เทคนิคได้โดยตรง แต่ก็แสดงให้เห็นว่าฟังก์ชั่นไม่ได้มีไว้สำหรับการบริโภคภายนอกโดยตรง


การอัปเดต 2019: จากการอัพเดทล่าสุดใน Python 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')และจับTypeErrorถ้าfileobjไม่ใช่สตริง
user2357112

คุณกำลังเถียงสำหรับการพิมพ์เป็ดในการใช้งาน ตัวอย่างให้ผลการพิมพ์เป็ด - นั่นคือผู้ใช้จะได้รับการverดำเนินงานที่เป็นอิสระจากประเภท อาจเป็นไปได้ที่จะนำไปใช้verผ่านการพิมพ์เป็ดอย่างที่คุณพูด แต่การสร้างข้อยกเว้นนั้นช้ากว่าการตรวจสอบแบบง่าย ๆ และ IMO ไม่ได้ผลประโยชน์ใด ๆ โดยเฉพาะ (ความคมชัดทั่วไป ฯลฯ ) จากประสบการณ์ของฉันการพิมพ์เป็ดนั้นยอดเยี่ยมมาก "ในตัวใหญ่" ."
Jonathan Eunice

3
ไม่สิ่งที่คุณกำลังทำอยู่ไม่ใช่การพิมพ์เป็ด การhasattr(fileobj, 'read')ทดสอบจะเป็นการพิมพ์เป็ด การisinstance(fileobj, str)ทดสอบไม่ได้ นี่คือตัวอย่างของความแตกต่างด้วยการisinstanceทดสอบล้มเหลวด้วยชื่อไฟล์ Unicode เนื่องจากไม่ได้เป็นu'adsf.txt' strคุณได้ทดสอบประเภทที่เฉพาะเจาะจงเกินไป การทดสอบการพิมพ์เป็ดไม่ว่าจะเป็นการโทรopenหรือdoes_this_object_represent_a_filenameฟังก์ชั่นสมมุติจะไม่มีปัญหานั้น
user2357112

1
หากรหัสนั้นเป็นรหัสการใช้งานจริงแทนที่จะเป็นตัวอย่างที่อธิบายได้ฉันก็จะไม่มีปัญหาเช่นนั้นเพราะฉันจะไม่ใช้is_instance(x, str)แต่จะเป็นแบบที่คล้ายis_instance(x, string_types)ๆ กับการstring_typesตั้งค่าที่เหมาะสมสำหรับการทำงานที่เหมาะสมใน PY2 และ PY3 รับบางสิ่งที่ quacks เช่นสตริงverจะตอบสนองอย่างถูกต้อง ให้สิ่งที่ quacks เช่นไฟล์เหมือนกัน ไปยังผู้ใช้ของverก็จะไม่แตกต่างกัน - ยกเว้นว่าการดำเนินการตรวจสอบชนิดจะทำงานได้เร็วขึ้น นักชิมเป็ด: ไม่เห็นด้วย
Jonathan Eunice

5

หากคุณส่งชื่อไฟล์ไปรอบ ๆ แทนที่จะเป็นตัวจัดการไฟล์ดังนั้นจึงไม่รับประกันว่าไฟล์ที่สองจะเป็นไฟล์เดียวกับไฟล์แรกเมื่อเปิดขึ้นมา สิ่งนี้สามารถนำไปสู่ข้อผิดพลาดที่ถูกต้องและช่องโหว่ความปลอดภัย


1
จริง แต่นั่นจะต้องมีการถ่วงเวลาด้วยการแลกเปลี่ยนอื่น: หากคุณผ่านการจัดการไฟล์ผู้อ่านทุกคนจะต้องประสานการเข้าถึงไฟล์ของพวกเขาเพราะแต่ละคนมีแนวโน้มที่จะย้าย "ตำแหน่งไฟล์ปัจจุบัน"
Jonathan Eunice

@JonathanEunice: ประสานงานในความหมายอะไร สิ่งที่พวกเขาต้องทำคือกำหนดตำแหน่งไฟล์ให้เป็นทุกที่ที่ต้องการ
Mehrdad

1
หากมีหลายหน่วยงานที่อ่านไฟล์อาจมีการอ้างอิง หนึ่งอาจต้องเริ่มต้นจากที่อื่น ๆ ที่เหลือ (หรือในสถานที่ที่กำหนดโดยข้อมูลที่อ่านโดยการอ่านก่อนหน้า) นอกจากนี้ผู้อ่านอาจทำงานในหัวข้อที่แตกต่างกันเปิดขึ้นกระป๋องการประสานงานอื่น ๆ ของเวิร์ม วัตถุไฟล์ที่ผ่านไปแล้วจะถูกเปิดเผยสถานะโกลบอลพร้อมกับปัญหาทั้งหมด (รวมถึงผลประโยชน์) ที่เกี่ยวข้อง
Jonathan Eunice

1
มันไม่ได้ผ่านเส้นทางไฟล์ที่เป็นกุญแจสำคัญ มันมีฟังก์ชั่นเดียว (หรือคลาส, เมธอดหรือสถานที่ควบคุมอื่น ๆ ) รับผิดชอบในการ "ประมวลผลไฟล์ทั้งหมด" หากการเข้าถึงไฟล์ถูกห่อหุ้มอยู่ที่ใดที่หนึ่งแล้วคุณไม่จำเป็นต้องส่งผ่านสถานะโกลบอลที่ไม่แน่นอนเช่นการจัดการเปิดไฟล์
Jonathan Eunice

1
ทีนี้เราก็เห็นด้วยที่จะไม่เห็นด้วย ฉันกำลังพูดว่ามีข้อเสียในการตัดสินใจในการออกแบบที่ผ่านไปทั่วโลกที่ไม่แน่นอน มีข้อดีด้วยเช่นกัน ดังนั้น "การค้าขาย" การออกแบบที่ส่งผ่านพา ธ ไฟล์มักจะทำ I / O ในคราวเดียวในลักษณะที่ห่อหุ้ม ฉันเห็นว่าเป็นข้อต่อที่ได้เปรียบ YMMV
Jonathan Eunice

1

นี่เป็นเรื่องเกี่ยวกับความเป็นเจ้าของและความรับผิดชอบในการปิดไฟล์ คุณสามารถส่งต่อกระแสข้อมูลหรือจัดการไฟล์หรือสิ่งที่ควรปิด / กำจัดในบางจุดไปยังวิธีอื่นตราบใดที่คุณแน่ใจว่าใครเป็นเจ้าของชัดเจนและมั่นใจว่าเจ้าของจะถูกปิดเมื่อคุณทำเสร็จ . โดยทั่วไปแล้วจะเกี่ยวข้องกับการสร้างลองในที่สุดหรือรูปแบบที่ใช้แล้วทิ้ง


-1

หากคุณเลือกที่จะส่งไฟล์ที่เปิดอยู่คุณสามารถทำสิ่งต่อไปนี้ แต่คุณไม่สามารถเข้าถึงชื่อไฟล์ในฟังก์ชั่นที่เขียนลงในไฟล์

ฉันจะทำเช่นนี้หากฉันต้องการมีคลาสที่รับผิดชอบการดำเนินงานไฟล์ / สตรีมและคลาสหรือฟังก์ชั่นอื่น ๆ ที่จะไร้เดียงสาและไม่คาดว่าจะเปิดหรือปิดไฟล์ / สตรีมที่กล่าวมา

จำไว้ว่าผู้จัดการบริบททำงานเหมือนมีประโยคสุดท้าย ดังนั้นหากมีข้อผิดพลาดเกิดขึ้นในฟังก์ชั่นเขียนไฟล์จะถูกปิดไม่ว่าจะเกิดอะไรขึ้น

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อย่างไร คำถามนี้เกี่ยวกับการใช้ชื่อไฟล์กับวัตถุคล้ายไฟล์อย่างไร
Dannnno

นี่แสดงวิธีซ่อนไฟล์ / สตรีมการเปิด / ปิดการทำงาน ในขณะที่คุณสามารถเห็นได้อย่างชัดเจนในความคิดเห็นมันช่วยให้คุณเพิ่มตรรกะก่อนที่จะเปิดกระแส / ไฟล์ที่โปร่งใสกับ "นักเขียน" "นักเขียน" อาจเป็นวิธีการเรียนของแพคเกจอื่น ในสาระสำคัญมันเป็นเสื้อคลุมของการเปิด นอกจากนี้ขอขอบคุณสำหรับการตอบกลับและการลงคะแนน
Vls

พฤติกรรมนั้นได้รับการจัดการโดยwith openใช่มั้ย และสิ่งที่คุณสนับสนุนอย่างมีประสิทธิภาพคือฟังก์ชั่นที่ใช้เฉพาะวัตถุที่เหมือนไฟล์และไม่สนใจว่ามันมาจากไหน?
Dannnno
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.