มีตัวแปรหลายตัวในข้อความ 'with' หรือไม่?


391

เป็นไปได้ไหมที่จะประกาศมากกว่าหนึ่งตัวแปรโดยใช้withคำสั่งใน Python

สิ่งที่ต้องการ:

from __future__ import with_statement

with open("out.txt","wt"), open("in.txt") as file_out, file_in:
    for line in file_in:
        file_out.write(line)

... หรือกำลังล้างทรัพยากรสองรายการพร้อมกันหรือไม่


อาจเป็นเช่นนี้: กับ [expr1, expr2] เป็น f: แล้วใช้ f [0] และ f [1]
jbasko

น่าจะดีเพราะไม่ต้องนำเข้าบางสิ่ง .... แต่มันไม่ทำงาน AttributeError: 'รายการ' วัตถุไม่มีแอตทริบิวต์ ' exit '
pufferfish

ถ้างูใหญ่เพิ่งปิดคุณไม่จำเป็นต้องใช้คำสั่ง
BT

คุณไม่จำเป็นต้องใช้คำสั่ง a พร้อมใช่ไหม คุณสามารถตั้งค่า file_out และ file_in เป็น None จากนั้นลอง / ยกเว้น / ในที่สุดเมื่อคุณเปิดไฟล์เหล่านั้นและดำเนินการในการลองแล้วในที่สุดก็ปิดพวกเขาหากพวกเขาไม่ใช่ ไม่จำเป็นต้องมีการเยื้องสองครั้ง
M Katz

1
คำตอบเหล่านี้จำนวนมากไม่ได้ตอบสนองความต้องการมากกว่าสองคำตอบ ในทางทฤษฎีอาจมีแอปพลิเคชั่นที่จำเป็นต้องเปิดหลายสิบบริบทการซ้อนกันอย่างรวดเร็วคือข้อ จำกัด ของความยาวบรรทัดใด ๆ
ThorSummoner

คำตอบ:


666

มันเป็นไปได้ในหลาม 3 ตั้งแต่ v3.1และ งูหลาม 2.7 withไวยากรณ์ใหม่สนับสนุนตัวจัดการบริบทหลายรายการ:

with A() as a, B() as b, C() as c:
    doSomething(a,b,c)

ซึ่งแตกต่างจากcontextlib.nestedนี้รับประกันได้ว่าaและbจะมีการ__exit__()เรียกของพวกเขาแม้ว่าC()หรือเป็น__enter__()วิธีการยกข้อยกเว้น

คุณยังสามารถใช้ตัวแปรก่อนหน้านี้ในคำจำกัดความในภายหลัง (h / t Ahmadด้านล่าง):

with A() as a, B(a) as b, C(a, b) as c:
    doSomething(a, c)

1
เป็นไปได้ที่จะตั้งค่าฟิลด์ให้เท่ากับบางสิ่งในพร้อมกับข้อความสั่งเหมือนwith open('./file') as arg.x = file:หรือไม่
Charlie Parker

13
นอกจากนี้ยังเป็นไปได้: ด้วย A () เป็น a, B (a) เป็น b, C (a, b) เป็น c:
Ahmad Yoosofan

คลาส test2: x = 1; t2 = test2 () พร้อม open ('f2.txt') เป็น t2.x: สำหรับ l1 ใน t2.x.readlines (): print (l1); # Charlie Parker # ทดสอบใน python 3.6
Ahmad Yoosofan

1
โปรดทราบว่าasเป็นตัวเลือก
Sławomir Lenart

เพื่อชี้แจงสิ่งที่ @ SlawomirLenart พูดว่า: asจำเป็นต้องใช้หากคุณต้องการวัตถุaหรือbแต่ทั้งหมดas aหรือas bไม่จำเป็นต้องใช้
Ciprian Tomoiagă

56

contextlib.nested รองรับสิ่งนี้:

import contextlib

with contextlib.nested(open("out.txt","wt"), open("in.txt")) as (file_out, file_in):

   ...

อัปเดต:
เพื่ออ้างถึงเอกสารเกี่ยวกับcontextlib.nested:

เลิกใช้แล้วตั้งแต่รุ่น 2.7 : ขณะนี้คำสั่ง with สนับสนุนการใช้งานนี้โดยตรง

ดูคำตอบของRafał Dowgirdสำหรับข้อมูลเพิ่มเติม


34
ฉันเสียใจที่จะบอกว่า แต่ฉันคิดว่าnestedผู้จัดการบริบทเป็นความผิดพลาดและไม่ควรใช้ ในตัวอย่างนี้หากการเปิดไฟล์ที่สองทำให้เกิดข้อยกเว้นไฟล์แรกจะไม่ถูกปิดเลยทำให้การทำลายวัตถุประสงค์ของการใช้ตัวจัดการบริบททั้งหมด
Rafał Dowgird

ทำไมคุณพูดแบบนั้น? เอกสารกล่าวว่าการใช้ซ้อนกันจะเทียบเท่ากับการซ้อนกัน 'กับ
เจมส์ Hopkin

@Rafal: ภาพรวมของคู่มือดูเหมือนว่าบ่งบอกว่างูหลามนั้นทำรังกับคำสั่งอย่างถูกต้อง ปัญหาที่แท้จริงคือถ้าไฟล์ที่สองส่งข้อยกเว้นเมื่อปิด
ไม่ทราบ

10
@James: ไม่รหัสเทียบเท่าในเอกสารที่docs.python.org/library/contextlib.html#contextlib.nestedแตกต่างจากwithบล็อกซ้อนมาตรฐาน ผู้จัดการถูกสร้างขึ้นตามลำดับก่อนที่จะเข้าสู่บล็อกด้วย: m1, m2, m3 = A (), B (), C () ถ้า B () หรือ C () ล้มเหลวโดยมีข้อยกเว้น ) เป็นตัวเก็บขยะ
Rafał Dowgird

8
เลิกใช้ตั้งแต่รุ่น 2.7 หมายเหตุ: ขณะนี้คำสั่ง with สนับสนุนการทำงานนี้โดยตรง (โดยไม่มีข้อผิดพลาดที่ทำให้เกิดข้อผิดพลาดได้ง่าย)
miku

36

โปรดทราบว่าถ้าคุณแบ่งตัวแปรออกเป็นบรรทัดคุณต้องใช้แบ็กสแลชเพื่อตัดบรรทัดใหม่

with A() as a, \
     B() as b, \
     C() as c:
    doSomething(a,b,c)

วงเล็บไม่ทำงานเนื่องจาก Python สร้าง tuple แทน

with (A(),
      B(),
      C()):
    doSomething(a,b,c)

เนื่องจากสิ่งอันดับขาด__enter__คุณสมบัติคุณจะได้รับข้อผิดพลาด (ไม่ได้อธิบายและไม่ได้ระบุประเภทของคลาส):

AttributeError: __enter__

ถ้าคุณพยายามที่จะใช้asภายในวงเล็บ, Python จับข้อผิดพลาดในเวลาในการแยกวิเคราะห์:

with (A() as a,
      B() as b,
      C() as c):
    doSomething(a,b,c)

SyntaxError: ไวยากรณ์ไม่ถูกต้อง

https://bugs.python.org/issue12782ดูเหมือนว่าจะเกี่ยวข้องกับปัญหานี้


16

ฉันคิดว่าคุณต้องการทำสิ่งนี้แทน:

from __future__ import with_statement

with open("out.txt","wt") as file_out:
    with open("in.txt") as file_in:
        for line in file_in:
            file_out.write(line)

5
นั่นเป็นวิธีที่ฉันกำลังทำมัน แต่แล้วการทำรังเป็นสองเท่าให้ลึกที่สุดเท่าที่ฉันต้องการ (ค่าเฉลี่ย) ว่ามันจะ ...
ปลาปักเป้า

ฉันคิดว่านี่เป็นวิธีที่สะอาดที่สุด แต่วิธีอื่น ๆ นั้นยากที่จะอ่าน คำตอบของ Alex Martelli นั้นดูเหมือนจะใกล้เคียงกับสิ่งที่คุณต้องการ แต่อ่านได้น้อยกว่ามาก เหตุใดการทำรังข้อกังวลดังกล่าว
Andrew Hare

7
ไม่ใช่เรื่องใหญ่เป็นที่ยอมรับ แต่ต่อ "นำเข้าสิ่งนี้" (หรือที่รู้จัก "Zen of Python") "flat is better than nested" - นั่นคือเหตุผลที่เราเพิ่ม contextlib.nested ไปยังไลบรารีมาตรฐาน BTW, 3.1 อาจมีไวยากรณ์ใหม่ "ที่มี A () เป็น a, B () เป็น b:" (แพตช์อยู่ในไม่มีการประกาศ BDFL เกี่ยวกับเรื่องนี้จนถึง) สำหรับการสนับสนุนโดยตรงมากขึ้น (ชัดเจนว่าโซลูชั่นห้องสมุดไม่ได้ ' ถือว่าไม่สมบูรณ์แบบ ... แต่การหลีกเลี่ยงการซ้อนที่ไม่ต้องการนั้นเป็นเป้าหมายที่ใช้ร่วมกันอย่างกว้างขวางในหมู่นักพัฒนา Python หลัก)
Alex Martelli

2
@Alex: จริงมาก แต่เราต้องพิจารณาด้วยว่า "จำนวนการอ่านได้"
Andrew Hare

4
@ แอนดรูว์: ฉันคิดว่าการเยื้องระดับหนึ่งแสดงถึงตรรกะที่ตั้งใจไว้ของโปรแกรมได้ดียิ่งขึ้นซึ่งก็คือ "อะตอมมิก" สร้างตัวแปรสองตัวและทำความสะอาดพวกมันในภายหลังด้วยกัน (ฉันรู้ว่านี่ไม่ใช่สิ่งที่เกิดขึ้นจริง) คิดว่าปัญหาข้อยกเว้นเป็น breaker จัดการแม้ว่า
ปลาปักเป้า

12

ตั้งแต่ Python 3.3 คุณสามารถใช้คลาสExitStackจากcontextlibโมดูลได้

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

Canonical use-case ที่ถูกกล่าวถึงในเอกสารคู่มือกำลังจัดการจำนวนไฟล์แบบไดนามิก

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

นี่คือตัวอย่างทั่วไป:

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(stack._exit_callbacks)
    nums = [stack.enter_context(x) for x in xs]
    print(stack._exit_callbacks)
print(stack._exit_callbacks)
print(nums)

เอาท์พุท:

deque([])
enter X1
enter X2
enter X3
deque([<function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86158>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f861e0>, <function ExitStack._push_cm_exit.<locals>._exit_wrapper at 0x7f5c95f86268>])
exit X3
exit X2
exit X1
deque([])
[1, 2, 3]

0

ใน Python 3.1+ คุณสามารถระบุหลายบริบทของการแสดงออกและพวกเขาจะถูกประมวลผลราวกับว่าหลายwithงบซ้อนกัน:

with A() as a, B() as b:
    suite

เทียบเท่ากับ

with A() as a:
    with B() as b:
        suite

นอกจากนี้ยังหมายความว่าคุณสามารถใช้นามแฝงจากนิพจน์แรกในวินาที (มีประโยชน์เมื่อทำงานกับการเชื่อมต่อ / เคอร์เซอร์ db):

with get_conn() as conn, conn.cursor() as cursor:
    cursor.execute(sql)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.