Для чого розроблений оператор python “with”?


418

withСьогодні я вперше натрапив на заяву Python . Я кілька місяців злегка використовую Python і навіть не знаю про його існування! Враховуючи його дещо незрозумілий статус, я подумав, що варто було б запитати:

  1. Для чого withстворений оператор Python ?
  2. Для чого ви його використовуєте?
  3. Чи є якісь проблеми, про які мені потрібно знати, або поширені анти-шаблони, пов'язані з його використанням? Будь-які випадки, коли це краще використовувати try..finallyніж with?
  4. Чому його не використовують ширше?
  5. Які стандартні класи бібліотеки сумісні з ним?

5
Тільки для запису, осьwith у документації Python 3.
Олексій

Виходячи з фону Java, це допомагає мені пам'ятати, як відповідний «спроби з ресурсами» в Java, навіть якщо це може бути не зовсім правильно.
вефтим

Відповіді:


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 ++, який розповів мені все, що потрібно знати.
Фред Томсен

Гаразд, дозвольте мені зрозуміти це. Ви говорите, що 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- він не знає , що вам потрібно зробити з ним - ви можете зробити шукати, наприклад.
Тоні Суффолк 66

@ Musixauce3000 Оператор withможе заповнити змінну даними або внести якісь інші зміни в середовище до тих пір, поки інструкції під нею не будуть завершені, а потім буде зроблено будь-який вид очищення, який потрібен. Види очищення, які можна зробити, - це такі речі, як закриття відкритого файлу, або, як у цьому прикладі @Tamas, повернення каталогів туди, де ви були раніше, тощо. Оскільки Python має збір сміття, звільнення змінної не є важливим використання випадку. withзазвичай використовується для інших видів очищення.
Боб

89

Я б запропонував дві цікаві лекції:

  • PEP 343 Заява "з"
  • Effbot Розуміння твердження "з" Python

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()

АБО (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. Я не бачу тут жодного Антипаттера.
Цитуючи занурення в Python :

спробуйте .. нарешті це добре. з кращим.

4. Я думаю, це пов’язано із звичкою програмістів використовувати try..catch..finallyвисловлювання з інших мов.


4
Це дійсно стає своїм, коли ви маєте справу з потоками об'єктів синхронізації. Відносно рідкісні в Python, але коли вони вам потрібні, вам це справді потрібно with.
детлет

1
diveintopython.org не працює (постійно?). Дзеркальні в diveintopython.net
притискається

Приклад хорошої відповіді, відкритий файл - це яскравий приклад, який показує за лаштунками відкриття, io, закриття файлових операцій приховано чисто з користувацьким довідковим іменем
Angry 84,

40

Оператор Python with- це вбудована мовна підтримка Resource Acquisition Is Initializationідіоми, яка зазвичай використовується в C ++. Він призначений для забезпечення безпечного придбання та звільнення ресурсів операційної системи.

withОператор створює ресурси в межах області / блоку. Ви пишете свій код, використовуючи ресурси в блоці. Коли блок виходить, ресурси видаються чисто незалежно від результату коду в блоці (тобто блокування виходить нормально або через виняток).

Багато ресурсів у бібліотеці Python, які підкоряються протоколу, необхідному для withоператора, і тому можуть використовуватись із ним поза вікном. Однак будь-хто може створити ресурси, які можуть бути використані в операторі із заявою, застосовуючи добре задокументований протокол: 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

Я дуже використовую це з тестом на гіпергеометрику, який вимагає поділу великої кількості в результаті факторних даних. Виконуючи розрахунки геномної шкали, ви повинні бути обережними щодо помилок округлення та переповнення.


26

Прикладом антипатерні може бути використання withвнутрішньої петлі, коли було б більш ефективно мати withзовнішню петлю

наприклад

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

проти

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

Перший спосіб - це відкриття та закриття файлу для кожного, rowщо може спричинити проблеми з продуктивністю порівняно з другим способом із відкриттям та закриттям файлу лише один раз.


10

Див. PEP 343 - Заява "з" , в кінці є приклад розділу.

... новий вислів "з" на мову Python, щоб зробити можливим виділити стандартне використання операцій try / нарешті.


5

пункти 1, 2 і 3 досить добре охоплені:

4: він відносно новий, доступний лише в python2.6 + (або python2.5 за допомогою from __future__ import with_statement)


4

Оператор з операцією працює з так званими контекстними менеджерами:

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

Ідея полягає в тому, щоб спростити обробку винятків, виконавши необхідне очищення після виходу з блоку "з". Деякі з вбудованих пітон уже працюють як менеджери контексту.


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

У python звичайно оператор " з " використовується для відкриття файлу, обробки даних, присутніх у файлі, а також для закриття файлу без виклику методу close (). Оператор "з" спрощує обробку винятків шляхом надання очищення.

Загальна форма з:

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

Примітка: не потрібно закривати файл, зателефонувавши close () на файл-var.close ()

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.