Для чого розроблений оператор 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.