ฉันจะเปิดหลายไฟล์โดยใช้“ with open” ใน Python ได้อย่างไร


672

ฉันต้องการเปลี่ยนไฟล์สองสามไฟล์ในครั้งเดียวถ้าฉันสามารถเขียนไฟล์ทั้งหมดได้ ฉันสงสัยว่าฉันจะสามารถรวมการโทรแบบเปิดหลายสายกับwithคำสั่งได้หรือไม่:

try:
  with open('a', 'w') as a and open('b', 'w') as b:
    do_something()
except IOError as e:
  print 'Operation failed: %s' % e.strerror

หากเป็นไปไม่ได้โซลูชันที่สง่างามของปัญหานี้จะเป็นอย่างไร

คำตอบ:


1051

ตั้งแต่ Python 2.7 (หรือ 3.1 ตามลำดับ) คุณสามารถเขียนได้

with open('a', 'w') as a, open('b', 'w') as b:
    do_something()

ใน Python เวอร์ชันก่อนหน้าบางครั้งคุณสามารถใช้ contextlib.nested()เพื่อซ้อนตัวจัดการบริบท สิ่งนี้จะไม่ทำงานอย่างที่คาดไว้สำหรับการเปิดไฟล์หลายรายการ - ดูเอกสารประกอบที่เชื่อมโยงเพื่อดูรายละเอียด


ในกรณีที่ไม่ค่อยเกิดขึ้นที่คุณต้องการเปิดจำนวนตัวแปรของไฟล์ทั้งหมดในเวลาเดียวกันคุณสามารถใช้contextlib.ExitStackโดยเริ่มจาก Python เวอร์ชั่น 3.3:

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # Do something with "files"

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


5
น่าเสียดายที่ documentlib.nested docs คุณไม่ควรใช้สำหรับการเปิดไฟล์: "using nested () เพื่อเปิดสองไฟล์เป็นข้อผิดพลาดในการเขียนโปรแกรมเนื่องจากไฟล์แรกจะไม่ถูกปิดทันทีหากมีข้อยกเว้นเกิดขึ้นเมื่อเปิด ไฟล์ที่สอง "
weronika

41
มีวิธีใช้withในการเปิดรายการตัวแปรหรือไม่?
monkut

23
@monkut: คำถามที่ดีมาก (คุณอาจถามคำถามนี้เป็นคำถามแยกต่างหาก) คำตอบสั้น ๆ : ใช่มีExitStackตั้งแต่ Python 3.3 ไม่มีวิธีง่ายๆในการทำเช่นนี้กับ Python รุ่นก่อนหน้า
Sven Marnach

12
เป็นไปได้ไหมที่จะมีไวยากรณ์นี้ขยายหลายบรรทัด?
tommy.carstensen

9
@ tommy.carstensen: คุณสามารถใช้ปกติกลไกต่อเนื่องบรรทัด คุณควรใช้เครื่องหมายทับขวาของบรรทัดแบ็กสแลชเพื่อหยุดที่เครื่องหมายจุลภาคตามที่ PEP 9แนะนำ
Sven Marnach

99

เพียงแทนที่andด้วย,และคุณทำเสร็จแล้ว:

try:
    with open('a', 'w') as a, open('b', 'w') as b:
        do_something()
except IOError as e:
    print 'Operation failed: %s' % e.strerror

3
คุณควรระบุเวอร์ชันของ Python ที่สนับสนุนไวยากรณ์นี้
Craig McQueen

58

สำหรับการเปิดไฟล์หลายไฟล์พร้อมกันหรือสำหรับเส้นทางไฟล์ที่ยาวอาจเป็นประโยชน์ในการแยกสิ่งต่าง ๆ ออกเป็นหลายบรรทัด จากPython Style Guideตามที่ @Sven Marnach แนะนำในความคิดเห็นไปยังคำตอบอื่น:

with open('/path/to/InFile.ext', 'r') as file_1, \
     open('/path/to/OutFile.ext', 'w') as file_2:
    file_2.write(file_1.read())

1
ด้วยการเยื้องนี้ฉันจะได้รับ: "flake8: สายต่อเนื่องเยื้องสำหรับเยื้องภาพ"
Louis M

@LouisM ฟังดูเหมือนว่ามีบางอย่างมาจากโปรแกรมแก้ไขหรือสภาพแวดล้อมของคุณมากกว่าฐานหลาม หากยังคงเป็นปัญหาสำหรับคุณฉันขอแนะนำให้สร้างคำถามใหม่เกี่ยวกับคำถามนี้และให้รายละเอียดเพิ่มเติมเกี่ยวกับเครื่องมือแก้ไขและสภาพแวดล้อมของคุณ
Michael Ohlrogge

3
ใช่มันเป็นตัวแก้ไขของฉันและเป็นเพียงคำเตือน สิ่งที่ฉันต้องการเน้นคือการเยื้องของคุณไม่สอดคล้องกับ PEP8 คุณควรเยื้องการเปิดครั้งที่สอง () พร้อมช่องว่าง 8 ช่องแทนที่จะเป็นการจัดแนวช่องว่างด้วยช่องว่างอันแรก
Louis M

2
@LouisM PEP8 เป็นแนวทางไม่ใช่กฎและในกรณีนี้ฉันจะเพิกเฉยต่อสิ่งนี้อย่างแน่นอน
Nick

2
ใช่ปัญหากับว่าไม่มีก็อาจจะมีประโยชน์สำหรับคนอื่น ๆ ที่มี linters อัตโนมัติแม้ว่า :)
หลุยส์ M

15

การซ้อนคำพูดจะทำหน้าที่เดียวกันและในความคิดของฉันตรงไปตรงมากว่าที่จะจัดการกับ

สมมติว่าคุณมี inFile.txt และต้องการเขียนลงในไฟล์ outFile สองไฟล์พร้อมกัน

with open("inFile.txt", 'r') as fr:
    with open("outFile1.txt", 'w') as fw1:
        with open("outFile2.txt", 'w') as fw2:
            for line in fr.readlines():
                fw1.writelines(line)
                fw2.writelines(line)

แก้ไข:

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


1
ฉันไม่ทราบว่ามีคนอื่นที่ทำให้คุณผิดหวัง แต่ฉันอัปเดตคุณเพราะนี่เป็นเพียงตัวอย่างเดียวที่มีสามไฟล์ (หนึ่งอินพุตสองเอาต์พุต) ซึ่งเกิดขึ้นเป็นสิ่งที่ฉันต้องการ
Adam Michael Wood

2
บางทีคุณอาจถูกลดระดับ bcoz ใน Python> 2.6 คุณสามารถเขียนรหัส pythonic เพิ่มเติม - gist.github.com/IaroslavR/3d8692e2a11e1ef902d2d8277eb88cb8 (ทำไมฉันไม่สามารถแทรกส่วนของรหัสในความคิดเห็นได้?!) เราอยู่ในปี 2018; อดีต
El Ruso

2
การแจ้งเตือนที่เป็นมิตรกับงูหลาม poo-poohing 2.6: CentOS 6 (ซึ่งไม่ใช่ EOL จนถึง พ.ย. 2563) ยังคงใช้ py2.6 ตามค่าเริ่มต้น ดังนั้นคำตอบนี้ (ณ ตอนนี้) ยังคงเป็น IMO โดยรวมที่ดีที่สุด
BJ Black

11

ตั้งแต่ Python 3.3 คุณสามารถใช้ในชั้นเรียนExitStackจากcontextlibโมดูลได้อย่างปลอดภัยเปิดจำนวนข้อของไฟล์

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

ในความเป็นจริงกรณีการใช้งานแบบบัญญัติซึ่งถูกกล่าวถึงในเอกสารประกอบคือการจัดการจำนวนไฟล์แบบไดนามิก

with ExitStack() as stack:
    files = [stack.enter_context(open(fname)) for fname in filenames]
    # All opened files will automatically be closed at the end of
    # the with statement, even if attempts to open files later
    # in the list raise an exception

หากคุณสนใจรายละเอียดต่อไปนี้เป็นตัวอย่างทั่วไปเพื่ออธิบายวิธีExitStackการทำงาน:

from contextlib import ExitStack

class X:
    num = 1

    def __init__(self):
        self.num = X.num
        X.num += 1

    def __repr__(self):
        cls = type(self)
        return '{cls.__name__}{self.num}'.format(cls=cls, self=self)

    def __enter__(self):
        print('enter {!r}'.format(self))
        return self.num

    def __exit__(self, exc_type, exc_value, traceback):
        print('exit {!r}'.format(self))
        return True

xs = [X() for _ in range(3)]

with ExitStack() as stack:
    print(len(stack._exit_callbacks)) # number of callbacks called on exit
    nums = [stack.enter_context(x) for x in xs]
    print(len(stack._exit_callbacks))

print(len(stack._exit_callbacks))
print(nums)

เอาท์พุท:

0
enter X1
enter X2
enter X3
3
exit X3
exit X2
exit X1
0
[1, 2, 3]

3

ด้วย python 2.6 มันใช้งานไม่ได้เราต้องใช้วิธีด้านล่างเพื่อเปิดหลายไฟล์:

with open('a', 'w') as a:
    with open('b', 'w') as b:

1

ตอบช้า (8 ปี) แต่สำหรับคนที่ต้องการเข้าร่วมหลายไฟล์เป็นหนึ่งเดียวฟังก์ชั่นต่อไปนี้อาจช่วยได้

def multi_open(_list):
    out=""
    for x in _list:
        try:
            with open(x) as f:
                out+=f.read()
        except:
            pass
            # print(f"Cannot open file {x}")
    return(out)

fl = ["C:/bdlog.txt", "C:/Jts/tws.vmoptions", "C:/not.exist"]
print(multi_open(fl))

2018-10-23 19:18:11.361 PROFILE  [Stop Drivers] [1ms]
2018-10-23 19:18:11.361 PROFILE  [Parental uninit] [0ms]
...
# This file contains VM parameters for Trader Workstation.
# Each parameter should be defined in a separate line and the
...
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.