คำสั่ง python“ พร้อม” ออกแบบมาเพื่ออะไร?


418

ฉันเจอwithคำสั่งPython เป็นครั้งแรกในวันนี้ ฉันใช้ Python เบา ๆ เป็นเวลาหลายเดือนและไม่รู้ด้วยซ้ำว่ามันมีอยู่จริง! ด้วยสถานะที่ค่อนข้างคลุมเครือฉันคิดว่ามันคุ้มค่าที่จะถามว่า:

  1. withคำสั่งPython ที่ออกแบบมาเพื่อใช้คืออะไร?
  2. คุณใช้มันทำอะไร?
  3. มี gotchas ใด ๆ ที่ฉันต้องระวังหรือต่อต้านรูปแบบทั่วไปที่เกี่ยวข้องกับการใช้งานหรือไม่? ใด ๆ กรณีที่มีการใช้งานที่ดีขึ้นtry..finallyกว่าwith?
  4. ทำไมมันไม่ใช้กันอย่างแพร่หลายมากขึ้น?
  5. คลาสไลบรารีมาตรฐานใดที่เข้ากันได้กับมัน

5
สำหรับบันทึกนี่คือwithเอกสาร Python 3
Alexey

มาจากพื้นหลัง Java ช่วยให้ฉันจำได้ว่าเป็น "ลองกับทรัพยากร" ใน Java แม้ว่าจะไม่ถูกต้องทั้งหมด
vefthym

คำตอบ:


399
  1. ผมเชื่อว่านี้ได้รับการตอบจากผู้ใช้อื่น ๆ ก่อนที่ฉันดังนั้นฉันเพียงเพิ่มเพื่อความครบถ้วนสมบูรณ์นี้: withการจัดการข้อยกเว้นคำสั่งช่วยลดความยุ่งยากโดยการห่อหุ้มเซลล์แสงอาทิตย์ที่พบบ่อยและการเตรียมงานเก็บกวาดในสิ่งที่เรียกว่าผู้จัดการบริบท รายละเอียดเพิ่มเติมสามารถพบได้ในPEP 343 ตัวอย่างเช่นopenคำสั่งเป็นตัวจัดการบริบทในตัวเองซึ่งช่วยให้คุณเปิดไฟล์ให้เปิดไว้ตราบใดที่การดำเนินการอยู่ในบริบทของwithคำสั่งที่คุณใช้และปิดทันทีที่คุณออกจากบริบท ไม่ว่าคุณจะปล่อยทิ้งไว้เนื่องจากข้อยกเว้นหรือระหว่างการควบคุมการไหลปกติ withคำสั่งจึงจะสามารถใช้ในรูปแบบคล้ายกับรูปแบบ RAIIใน C ++: ทรัพยากรบางอย่างจะมาจากwithคำสั่งและปล่อยเมื่อคุณออกจากwithบริบท

  2. ตัวอย่างบางส่วน ได้แก่ : การเปิดไฟล์โดยใช้การwith open(filename) as fp:รับการล็อคโดยใช้with lock:(ซึ่งlockเป็นตัวอย่างของthreading.Lock) นอกจากนี้คุณยังสามารถสร้างผู้จัดการบริบทของคุณเองโดยใช้มัณฑนากรจากcontextmanager contextlibตัวอย่างเช่นฉันมักจะใช้สิ่งนี้เมื่อฉันต้องเปลี่ยนไดเรกทอรีปัจจุบันชั่วคราวแล้วกลับไปที่ที่ฉันเคย:

    from contextlib import contextmanager
    import os
    
    @contextmanager
    def working_directory(path):
        current_dir = os.getcwd()
        os.chdir(path)
        try:
            yield
        finally:
            os.chdir(current_dir)
    
    with working_directory("data/stuff"):
        # do something within data/stuff
    # here I am back again in the original working directory
    

    นี่คือตัวอย่างที่เปลี่ยนเส้นทางชั่วคราวอีกsys.stdin, sys.stdoutและsys.stderrบางส่วนที่จับไฟล์อื่น ๆ และเรียกคืนได้ในภายหลัง:

    from contextlib import contextmanager
    import sys
    
    @contextmanager
    def redirected(**kwds):
        stream_names = ["stdin", "stdout", "stderr"]
        old_streams = {}
        try:
            for sname in stream_names:
                stream = kwds.get(sname, None)
                if stream is not None and stream != getattr(sys, sname):
                    old_streams[sname] = getattr(sys, sname)
                    setattr(sys, sname, stream)
            yield
        finally:
            for sname, stream in old_streams.iteritems():
                setattr(sys, sname, stream)
    
    with redirected(stdout=open("/tmp/log.txt", "w")):
         # these print statements will go to /tmp/log.txt
         print "Test entry 1"
         print "Test entry 2"
    # back to the normal stdout
    print "Back to normal stdout again"
    

    และในที่สุดอีกตัวอย่างหนึ่งที่สร้างโฟลเดอร์ชั่วคราวและล้างมันเมื่อออกจากบริบท:

    from tempfile import mkdtemp
    from shutil import rmtree
    
    @contextmanager
    def temporary_dir(*args, **kwds):
        name = mkdtemp(*args, **kwds)
        try:
            yield name
        finally:
            shutil.rmtree(name)
    
    with temporary_dir() as dirname:
        # do whatever you want
    

20
ขอขอบคุณที่เพิ่มการเปรียบเทียบกับ RAII ในฐานะโปรแกรมเมอร์ C ++ ที่บอกทุกอย่างที่ฉันจำเป็นต้องรู้
Fred Thomsen

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

เพราะฉันใช้มันเพื่อเปิดสคริปต์ py with open('myScript.py', 'r') as f: pass. ผมคาดว่าจะสามารถเรียกตัวแปรfเพื่อดูเนื้อหาข้อความของเอกสารเช่นนี้เป็นสิ่งที่จะปรากฏขึ้นหากเอกสารที่ได้รับมอบหมายให้fผ่านทางปกติคำสั่ง:open แต่แทนที่จะผมได้ดังต่อไปนี้:f = open('myScript.py').read() <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>มันหมายความว่าอะไร?
Musixauce3000

3
@ Musixauce3000 - การใช้withไม่จำเป็นต้องลบreadไฟล์จริง การwithโทรopen- ไม่รู้ว่าคุณต้องทำอะไร - คุณอาจต้องการค้นหาตัวอย่าง
Tony Suffolk 66

@ Musixauce3000 withคำสั่งสามารถเติมข้อมูลด้วยข้อมูลหรือทำการเปลี่ยนแปลงอื่น ๆ กับสภาพแวดล้อมจนกว่าคำแนะนำจะเสร็จสมบูรณ์จากนั้นทำการล้างข้อมูลประเภทใด ๆ ที่จำเป็น ประเภทของการล้างข้อมูลที่สามารถทำได้คือสิ่งต่าง ๆ เช่นการปิดไฟล์ที่เปิดอยู่หรือตามที่ @Tamas มีอยู่ในตัวอย่างนี้การเปลี่ยนไดเรกทอรีกลับไปยังที่ที่คุณเคยอยู่มาก่อน ฯลฯ เนื่องจาก Python มีการรวบรวมขยะการทำให้ตัวแปรไม่สำคัญ ใช้กรณี withโดยทั่วไปใช้สำหรับการล้างข้อมูลชนิดอื่น
Bob Steinke

89

ฉันขอแนะนำการบรรยายที่น่าสนใจสองรายการ:

  • PEP 343ข้อความ "กับ"
  • Effbotเข้าใจคำว่า "กับ" ของงูใหญ่

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

2. คุณสามารถทำสิ่งที่ชอบ:

with open("foo.txt") as foo_file:
    data = foo_file.read()

หรือ

from contextlib import nested
with nested(A(), B(), C()) as (X, Y, Z):
   do_something()

OR (Python 3.1)

with open('data') as input_file, open('result', 'w') as output_file:
   for line in input_file:
     output_file.write(parse(line))

หรือ

lock = threading.Lock()
with lock:
    # Critical section of code

3. ฉันไม่เห็น Antipattern ใด ๆ ที่นี่
การดำน้ำเป็น Python :

ลอง .. ในที่สุดก็เป็นสิ่งที่ดี ด้วยจะดีกว่า

4. ฉันเดาว่ามันเกี่ยวข้องกับนิสัยของโปรแกรมเมอร์ในการใช้try..catch..finallyคำสั่งจากภาษาอื่น


4
มันเข้ามาเป็นของตัวเองจริงๆเมื่อคุณต้องจัดการกับวัตถุการทำข้อมูลให้ตรงกัน ค่อนข้างหายากในหลาม withแต่เมื่อคุณต้องการพวกเขาที่คุณต้องการจริงๆ
det

1
diveintopython.org ไม่ทำงาน (ถาวรหรือไม่) มิร์เรอร์ที่diveintopython.net
snuggles

ตัวอย่างของคำตอบที่ดี open file เป็นตัวอย่างสำคัญที่แสดงเบื้องหลังการเปิด io การปิดการทำงานของไฟล์จะถูกซ่อนไว้อย่างสะอาดหมดจดพร้อมชื่ออ้างอิงที่กำหนดเอง
Angry 84

40

withคำสั่งPython นั้นรองรับภาษาที่มีอยู่ในResource Acquisition Is Initializationสำนวนที่นิยมใช้ใน C ++ มันมีวัตถุประสงค์เพื่อให้ได้มาซึ่งความปลอดภัยและการปล่อยทรัพยากรระบบปฏิบัติการ

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

ทรัพยากรจำนวนมากในไลบรารี Python ที่ปฏิบัติตามโปรโตคอลที่จำเป็นโดยwithคำสั่งและสามารถใช้กับมันได้นอกกรอบ อย่างไรก็ตามทุกคนสามารถสร้างทรัพยากรที่สามารถใช้ในคำสั่ง a ด้วยการใช้โปรโตคอลที่มีเอกสารที่ดี: PEP 0343

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


27

อีกครั้งเพื่อความสมบูรณ์ฉันจะเพิ่มกรณีการใช้งานที่มีประโยชน์ที่สุดสำหรับwithงบ

ฉันทำการคำนวณทางวิทยาศาสตร์จำนวนมากและสำหรับบางกิจกรรมฉันต้องการDecimalห้องสมุดสำหรับการคำนวณความแม่นยำโดยพลการ ส่วนหนึ่งของรหัสของฉันฉันต้องการความแม่นยำสูงและส่วนอื่น ๆ ที่ฉันต้องการความแม่นยำน้อย

ฉันตั้งค่าความแม่นยำเริ่มต้นของฉันให้เป็นตัวเลขต่ำแล้วใช้withเพื่อให้ได้คำตอบที่แม่นยำยิ่งขึ้นสำหรับบางส่วน:

from decimal import localcontext

with localcontext() as ctx:
    ctx.prec = 42   # Perform a high precision calculation
    s = calculate_something()
s = +s  # Round the final result back to the default precision

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


26

ตัวอย่างของ antipattern อาจจะใช้withวงในเมื่อมันจะมีประสิทธิภาพมากกว่าที่จะมีwithวงนอก

ตัวอย่างเช่น

for row in lines:
    with open("outfile","a") as f:
        f.write(row)

VS

with open("outfile","a") as f:
    for row in lines:
        f.write(row)

วิธีแรกคือการเปิดและปิดไฟล์สำหรับแต่ละไฟล์rowซึ่งอาจทำให้เกิดปัญหาประสิทธิภาพเทียบกับวิธีที่สองเมื่อเปิดและปิดไฟล์เพียงครั้งเดียว



5

คะแนน 1, 2 และ 3 ถูกกล่าวถึงอย่างดีพอสมควร:

4:มันค่อนข้างใหม่ใช้ได้เฉพาะใน python2.6 + (หรือใช้ python2.5 from __future__ import with_statement)


4

คำสั่ง with ทำงานกับผู้จัดการบริบทที่เรียกว่า:

http://docs.python.org/release/2.5.2/lib/typecontextmanager.html

แนวคิดคือทำให้การจัดการข้อยกเว้นง่ายขึ้นโดยทำการล้างข้อมูลที่จำเป็นหลังจากออกจากบล็อก 'with' ไพ ธ อนอินของไพ ธ อนบางตัวทำงานเป็นผู้จัดการบริบทแล้ว


3

อีกตัวอย่างหนึ่งสำหรับการสนับสนุนนอกกรอบและอีกอย่างหนึ่งที่อาจทำให้งุนงงเล็กน้อยในตอนแรกเมื่อคุณคุ้นเคยกับopen()พฤติกรรมในตัวเป็นconnectionวัตถุของโมดูลฐานข้อมูลยอดนิยมเช่น:

connectionวัตถุเป็นผู้จัดการบริบทและเป็นเช่นนี้สามารถนำมาใช้ออกจากกล่องในwith-statementแต่เมื่อใช้ทราบข้างต้นว่า

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

ซึ่งหมายความว่าโปรแกรมเมอร์จะต้องระมัดระวังในการปิดการเชื่อมต่อด้วยตนเอง แต่อนุญาตให้รับการเชื่อมต่อและใช้ในหลาย ๆwith-statementsดังที่แสดงในเอกสาร psycopg2 :

conn = psycopg2.connect(DSN)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL1)

with conn:
    with conn.cursor() as curs:
        curs.execute(SQL2)

conn.close()

ในตัวอย่างข้างต้นคุณจะทราบว่าcursorวัตถุของpsycopg2ยังเป็นผู้จัดการบริบท จากเอกสารที่เกี่ยวข้องกับพฤติกรรม:

เมื่อcursorออกจากwith-blockมันปิดให้ปล่อยทรัพยากรใด ๆ ที่เกี่ยวข้องกับมันในที่สุด สถานะของธุรกรรมไม่ได้รับผลกระทบ


3

ในภาษาไพ ธ อนคำสั่ง“ with ” ใช้เพื่อเปิดไฟล์ประมวลผลข้อมูลที่มีอยู่ในไฟล์และเพื่อปิดไฟล์โดยไม่เรียกเมธอด close () คำสั่ง“ with” ทำให้การจัดการข้อยกเว้นง่ายขึ้นโดยจัดให้มีกิจกรรมการล้างข้อมูล

รูปแบบทั่วไปของด้วย:

with open(“file name”, mode”) as file-var:
    processing statements

หมายเหตุ:ไม่จำเป็นต้องปิดไฟล์โดยการเรียก close () เมื่อ file-var.close ()

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