Trăn trăn với câu lệnh được thiết kế để làm gì?


419

Tôi đã bắt gặp withcâu lệnh Python lần đầu tiên ngày hôm nay. Tôi đã sử dụng Python nhẹ trong vài tháng và thậm chí không biết đến sự tồn tại của nó! Với tình trạng hơi mơ hồ của nó, tôi nghĩ rằng nó sẽ đáng để hỏi:

  1. Câu withlệnh Python được thiết kế để sử dụng là gì?
  2. Bạn sử dụng chúng để làm gì?
  3. Có bất kỳ vấn đề nào tôi cần phải biết, hoặc các mô hình chống phổ biến liên quan đến việc sử dụng nó không? Bất kỳ trường hợp nào nó được sử dụng tốt try..finallyhơn with?
  4. Tại sao nó không được sử dụng rộng rãi hơn?
  5. Những lớp thư viện tiêu chuẩn tương thích với nó?

5
Chỉ để ghi lại, đây làwith trong tài liệu Python 3.
Alexey

đến từ một nền tảng Java, nó giúp tôi ghi nhớ nó như là "thử với tài nguyên" tương ứng trong Java, ngay cả khi điều đó có thể không hoàn toàn chính xác.
vefthym

Câu trả lời:


399
  1. Tôi tin rằng điều này đã được trả lời bởi những người dùng khác trước tôi, vì vậy tôi chỉ thêm nó để hoàn thiện: withcâu lệnh đơn giản hóa việc xử lý ngoại lệ bằng cách gói gọn các nhiệm vụ chuẩn bị và dọn dẹp chung trong cái gọi là trình quản lý bối cảnh . Thông tin chi tiết có thể được tìm thấy trong PEP 343 . Chẳng hạn, bản thân opencâu lệnh là một trình quản lý bối cảnh, cho phép bạn mở một tệp, giữ cho nó mở miễn là việc thực thi nằm trong ngữ cảnh của withcâu lệnh mà bạn đã sử dụng và đóng nó ngay khi bạn rời khỏi ngữ cảnh, bất kể bạn đã rời khỏi nó vì một ngoại lệ hoặc trong quá trình kiểm soát thường xuyên. Do đó, withcâu lệnh có thể được sử dụng theo những cách tương tự như mẫu RAII trong C ++: một số tài nguyên được mua lại bởiwithtuyên bố và phát hành khi bạn rời khỏi withbối cảnh.

  2. Một số ví dụ là: mở tệp bằng cách sử dụng with open(filename) as fp:, lấy khóa bằng cách sử dụng with lock:( lockví dụ là trường hợp threading.Lock). Bạn cũng có thể xây dựng các trình quản lý bối cảnh của riêng mình bằng cách sử dụng trình contextmanagertrang trí từ contextlib. Chẳng hạn, tôi thường sử dụng cái này khi tôi phải thay đổi thư mục hiện tại tạm thời và sau đó quay lại nơi tôi đang ở:

    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
    

    Dưới đây là một ví dụ khác mà tạm thời chuyển hướng sys.stdin, sys.stdoutsys.stderrmột số xử lý tập tin khác và khôi phục chúng sau:

    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"
    

    Và cuối cùng, một ví dụ khác tạo thư mục tạm thời và dọn sạch nó khi rời khỏi bối cảnh:

    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
Cảm ơn đã thêm so sánh với RAII. Là một lập trình viên C ++ đã nói với tôi mọi thứ tôi cần biết.
Fred Thomsen

Được rồi để tôi làm rõ điều này. Bạn đang nói rằng withcâu lệnh được thiết kế để điền vào một biến với dữ liệu cho đến khi các hướng dẫn bên dưới hoàn thành và sau đó giải phóng biến đó?
Musixauce3000

Bởi vì tôi đã sử dụng nó để mở một kịch bản py. with open('myScript.py', 'r') as f: pass. Tôi dự kiến ​​có thể gọi biến fđể xem nội dung văn bản của tài liệu, vì đây là nội dung sẽ xuất hiện nếu tài liệu được gán fthông qua một opencâu lệnh thông thường : f = open('myScript.py').read(). Nhưng thay vào đó tôi đã nhận được như sau : <_io.TextIOWrapper name='myScript.py' mode='r' encoding='cp1252'>. Nó có nghĩa là gì?
Musixauce3000

3
@ Musixauce3000 - sử dụng withkhông loại bỏ sự cần thiết đối readvới tệp thực tế. Các withcuộc gọi open- không biết bạn cần phải làm gì với nó - bạn có thể muốn thực hiện tìm kiếm chẳng hạn.
Tony Suffolk 66

@ Musixauce3000 Câu withlệnh có thể điền vào một biến với dữ liệu hoặc thực hiện một số thay đổi khác đối với môi trường cho đến khi các hướng dẫn bên dưới hoàn thành, và sau đó thực hiện bất kỳ loại dọn dẹp nào cần thiết. Các loại dọn dẹp có thể được thực hiện là những việc như đóng tệp đang mở hoặc như @Tamas có trong ví dụ này, thay đổi thư mục trở lại nơi bạn đã ở trước đây, v.v. Vì Python có bộ sưu tập rác, việc giải phóng một biến không phải là một điều quan trọng trường hợp sử dụng. withthường được sử dụng cho các loại dọn dẹp khác.
Bob Steinke

89

Tôi muốn đề nghị hai bài giảng thú vị:

  • PEP 343 Tuyên bố "với"
  • Effbot Hiểu câu lệnh "với" của Python

1. Câu withlệnh được sử dụng để bao bọc việc thực thi một khối bằng các phương thức được xác định bởi trình quản lý bối cảnh. Điều này cho phép try...except...finallycác mẫu sử dụng phổ biến được đóng gói để tái sử dụng thuận tiện.

2. Bạn có thể làm một cái gì đó như:

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

HOẶC LÀ

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

HOẶC (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))

HOẶC LÀ

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

3. Tôi không thấy bất kỳ Antipotype nào ở đây.
Trích dẫn Lặn vào Python :

cố gắng .. cuối cùng là tốt với là tốt hơn.

4. Tôi đoán nó liên quan đến thói quen của lập trình viên khi sử dụng try..catch..finallycâu lệnh từ các ngôn ngữ khác.


4
Nó thực sự xuất hiện khi bạn xử lý các đối tượng đồng bộ hóa luồng. Tương đối hiếm trong Python, nhưng khi bạn cần chúng, bạn thực sự cần with.
gièm pha

1
diveintopython.org không hoạt động (vĩnh viễn?). Nhân đôi tại diveintopython.net
snuggles 11/2/2015

Ví dụ về một câu trả lời hay, tệp mở là một ví dụ điển hình cho thấy đằng sau hậu trường mở, io, đóng các thao tác tệp được ẩn sạch với một tên tham chiếu tùy chỉnh
Angry 84

40

Câu withlệnh Python được hỗ trợ ngôn ngữ tích hợp của Resource Acquisition Is Initializationthành ngữ thường được sử dụng trong C ++. Nó được dự định để cho phép mua lại và giải phóng tài nguyên hệ điều hành một cách an toàn.

Câu withlệnh tạo tài nguyên trong một phạm vi / khối. Bạn viết mã của bạn bằng cách sử dụng các tài nguyên trong khối. Khi khối thoát, các tài nguyên được giải phóng sạch bất kể kết quả của mã trong khối (đó là liệu khối có thoát bình thường hay do ngoại lệ).

Nhiều tài nguyên trong thư viện Python tuân theo giao thức được yêu cầu bởi withcâu lệnh và do đó có thể được sử dụng với nó bên ngoài hộp. Tuy nhiên, bất kỳ ai cũng có thể tạo tài nguyên có thể được sử dụng trong câu lệnh with bằng cách triển khai giao thức được ghi chép tốt: PEP 0343

Sử dụng nó bất cứ khi nào bạn có được tài nguyên trong ứng dụng của mình phải bị từ bỏ rõ ràng như các tệp, kết nối mạng, khóa và những thứ tương tự.


27

Một lần nữa để hoàn thiện tôi sẽ thêm trường hợp sử dụng hữu ích nhất cho các withcâu lệnh.

Tôi làm rất nhiều tính toán khoa học và đối với một số hoạt động tôi cần Decimalthư viện để tính toán chính xác tùy ý. Một số phần của mã tôi cần độ chính xác cao và đối với hầu hết các phần khác tôi cần độ chính xác thấp hơn.

Tôi đặt độ chính xác mặc định của mình thành một số thấp và sau đó sử dụng withđể có câu trả lời chính xác hơn cho một số phần:

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

Tôi sử dụng điều này rất nhiều với Kiểm tra siêu âm yêu cầu phân chia số lượng lớn dẫn đến các yếu tố hình thức. Khi bạn thực hiện tính toán tỷ lệ bộ gen, bạn phải cẩn thận với các lỗi làm tròn và lỗi tràn.


26

Một ví dụ về một antipotype có thể là sử dụng withbên trong một vòng lặp khi nó có hiệu quả hơn để có withbên ngoài vòng lặp

ví dụ

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

đấu với

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

Cách đầu tiên là mở và đóng tệp cho mỗi tệp rowcó thể gây ra sự cố về hiệu suất so với cách thứ hai với việc mở và đóng tệp chỉ một lần.


10

Xem PEP 343 - Câu lệnh 'with' , có phần ví dụ ở cuối.

... tuyên bố mới "với" với ngôn ngữ Python để có thể xác định các cách sử dụng tiêu chuẩn của các câu lệnh try / cuối cùng.


5

các điểm 1, 2 và 3 được bảo vệ hợp lý:

4: nó tương đối mới, chỉ có sẵn trong python2.6 + (hoặc sử dụng python2.5 from __future__ import with_statement)


4

Câu lệnh with hoạt động với cái gọi là trình quản lý bối cảnh:

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

Ý tưởng là để đơn giản hóa việc xử lý ngoại lệ bằng cách thực hiện dọn dẹp cần thiết sau khi rời khỏi khối 'với'. Một số python dựng sẵn đã hoạt động như các trình quản lý bối cảnh.


3

Một ví dụ khác về hỗ trợ ngoài luồng lúc đầu và một hỗ trợ có thể hơi khó hiểu khi bạn đã quen với cách open()ứng xử tích hợp sẵn, là connectioncác đối tượng của các mô-đun cơ sở dữ liệu phổ biến như:

Các connectionđối tượng là các trình quản lý bối cảnh và như vậy có thể được sử dụng ngoài hộp trong một with-statement, tuy nhiên khi sử dụng lưu ý ở trên rằng:

Khi with-blockkết thúc, có ngoại lệ hoặc không có, kết nối không được đóng . Trong trường hợp with-blockkết thúc với một ngoại lệ, giao dịch được khôi phục, nếu không giao dịch được cam kết.

Điều này có nghĩa là lập trình viên phải cẩn thận tự đóng kết nối, nhưng cho phép có được kết nối và sử dụng nhiều kết nối with-statements, như được hiển thị trong tài liệu 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()

Trong ví dụ trên, bạn sẽ lưu ý rằng các cursorđối tượng psycopg2cũng là người quản lý bối cảnh. Từ các tài liệu liên quan về hành vi:

Khi cursorthoát ra, with-blocknó được đóng lại, giải phóng bất kỳ tài nguyên nào cuối cùng được liên kết với nó. Tình trạng của giao dịch không bị ảnh hưởng.


3

Nói chung, python nói chung với câu lệnh sử dụng để mở tệp, xử lý dữ liệu có trong tệp và cũng để đóng tệp mà không cần gọi phương thức close (). Tuyên bố của cải thiện làm cho việc xử lý ngoại lệ đơn giản hơn bằng cách cung cấp các hoạt động dọn dẹp.

Hình thức chung với:

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

lưu ý: không cần phải đóng tệp bằng cách gọi close () trên tệp-var.close ()

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.