RAII в Python - автоматическое уничтожение при выходе из области видимости - PullRequest
29 голосов
/ 21 февраля 2011

Я пытался найти RAII в Python.Распределение ресурсов - Инициализация - это шаблон в C ++, посредством которого объект инициализируется при его создании.Если это терпит неудачу, то это вызывает исключение.Таким образом, программист знает, что объект никогда не будет оставлен в полусозданном состоянии.Python может многое сделать.

Но RAII также работает с правилами области видимости C ++, чтобы обеспечить быстрое уничтожение объекта.Как только переменная выскакивает из стека, она уничтожается.Это может произойти в Python, но только если нет внешних или циклических ссылок.

Что еще более важно, имя объекта все еще существует до тех пор, пока не завершится функция, в которой он находится (а иногда и дольше).Переменные на уровне модуля будут сохраняться в течение всего срока службы модуля.

Я хотел бы получить ошибку, если я сделаю что-то вроде этого:

for x in some_list:
    ...

... 100 lines later ...

for i in x:
    # Oops! Forgot to define x first, but... where's my error?
    ...

Я мог бы вручную удалитьимена после того, как я его использовал, но это было бы довольно уродливо и потребовало бы от меня усилий.

И я бы хотел, чтобы в данном случае это было «Что я имею в виду»:

for x in some_list:
    surface = x.getSurface()
    new_points = []
    for x,y,z in surface.points:
        ...     # Do something with the points
        new_points.append( (x,y,z) )
    surface.points = new_points
    x.setSurface(surface)

Python делает некоторую область видимости, но не на уровне отступа, просто на функциональном уровне.Кажется глупым требовать, чтобы я сделал новую функцию только для того, чтобы охватить переменные, чтобы я мог повторно использовать имя.

В Python 2.5 есть оператор "with" , но для этого необходимо явно указатьв функциях __enter__ и __exit__ и в целом кажется более ориентированным на очистку ресурсов, таких как файлы и блокировки мьютекса, независимо от вектора выхода.Это не помогает с ограничением.Или я что-то упустил?

Я искал "Python RAII" и "Область Python" и не смог найти ничего, что решало бы проблему напрямую и авторитетно.Я просмотрел все ПКП.Кажется, эта концепция не рассматривается в Python.

Я плохой человек, потому что хочу иметь области видимости переменных в Python?Разве это не слишком пифонично?

Разве я не ворчу?

Возможно, я пытаюсь отобрать преимущества динамических аспектов языка.Эгоистично ли иногда требовать принудительного прицеливания?

Я ленив, что хочу, чтобы компилятор / интерпретатор ловил мои небрежные ошибки повторного использования переменных?Ну да, конечно, я ленивый, но я ленив в плохом смысле?

Ответы [ 5 ]

29 голосов
/ 22 февраля 2011

tl; dr RAII невозможен, вы смешиваете его с областью видимости в целом, и когда вы пропускаете эти дополнительные области, вы, вероятно, пишете плохой код.

Возможно, я нене получите ваш вопрос (ы), или вы не получите некоторых очень важных вещей о Python ... Во-первых, детерминированное уничтожение объектов, привязанное к области видимости, невозможно на языке сборки мусора.Переменные в Python являются просто ссылками.Вы бы не хотели, чтобы кусок памяти malloc был free, как только указатель, указывающий на него, выходит из области видимости, не так ли?Практическое исключение в некоторых обстоятельствах, если вам случается использовать подсчет ссылок - но ни один язык не является настолько безумным, чтобы установить точную реализацию в камне.

И , даже если у вас естьподсчет ссылок, как и в CPython, это детали реализации.Как правило, в том числе в Python, который имеет различные реализации не с использованием подсчета ссылок, вы должны кодировать, как если бы каждый объект зависал до тех пор, пока не закончилась память.

Что касается имен, существующих для остальной части функциивызов: вы можете удалить имя из текущей или глобальной области с помощью оператора del.Однако это не имеет никакого отношения к ручному управлению памятью.Это просто удаляет ссылку.Это может или не может привести к тому, что указанный объект будет GC'd и не является целью упражнения.

  • Если ваш код достаточно длинный, чтобы вызвать конфликт имен, вы должны написатьменьшие функции.И используйте более наглядные и менее вероятные имена.То же самое для вложенных циклов, перезаписывающих переменную итерации цикла out: я еще не столкнулся с этой проблемой, поэтому, возможно, ваши имена не достаточно описательны, или вам следует разделить эти циклы?

Вы правы,with не имеет ничего общего с областью видимости, только с детерминированной очисткой (так что она пересекается с RAII в концах, но не в средствах).

Возможно, я пытаюсь отобрать преимуществадинамических аспектов языка.Это эгоистично - иногда требовать принудительного прицеливания?

Нет.Достойный лексический охват - это заслуга, не зависящая от динамичности / статичности.По общему признанию, Python (2 - 3 в значительной степени исправил это) имеет слабые стороны в этом отношении, хотя они в большей степени связаны с замыканиями.

Но для объяснения "почему": Python должен будьте осторожны с тем, где начинается новая область, потому что без объявления, говорящего иначе, присвоение имени делает его локальным для самой внутренней / текущей области.Так, например, если бы цикл for имел собственную область видимости, вы не могли бы легко изменять переменные вне цикла.

Я ленив, что хочу, чтобы компилятор / интерпретатор ловил мои небрежные ошибки повторного использования переменных?Ну да, конечно, я ленивый, но я ленив в плохом смысле?

Опять же, я полагаю, что случайное повторное использование имени (таким образом, что приводит к ошибкам или ловушкам)в любом случае редкий и маленький.

Редактировать: Чтобы сформулировать это как можно более четко:

  • В языке, использующем GC, не может быть очистки на основе стека. По определению это просто невозможно: переменная является одной из потенциально многих ссылок на объекты в куче, которые не знают и не заботятся о том, когда переменные выходят из области видимости, а все управление памятью находится в руках GC, которыйработает, когда ему нравится, а не когда кадр стека выталкивается.Очистка ресурса решается по-другому, см. Ниже.
  • Детерминированная очистка происходит с помощью оператора with. Да, она не вводит новую область (см. Ниже), потому что это не то, чтоэто для.Неважно, что оно удаляет имя, с которым связан управляемый объект, не удаляется - тем не менее, очистка произошла, остался объект «не трогай меня, я непригодный для использования» (например, закрытый файловый поток).
  • Python имеет область действия для функции, класса и модуля.Точка. Так работает язык, нравится вам это или нет.Если вы хотите / «нуждаетесь» в более детальной области видимости, разбейте код на более детальные функции.Возможно, вы захотите более детальную область видимости, но это не так - и по причинам, указанным ранее в этом ответе (три абзаца над «Изменить:»), есть причины для этого.Как ни крути, но так работает язык.
13 голосов
/ 22 февраля 2011
  1. Вы правы насчет with - это совершенно не связано с переменной областью видимости.

  2. Избегайте глобальных переменных, если вы думаете, что они являются проблемой. Это включает в себя переменные уровня модуля.

  3. Основным инструментом для скрытия состояния в Python являются классы.

  4. Выражения генератора (и в Python 3 также есть списки) имеют свою область видимости.

  5. Если ваши функции достаточно длинные, чтобы вы не могли отслеживать локальные переменные, вам, вероятно, следует провести рефакторинг своего кода.

8 голосов
/ 22 февраля 2011

Но RAII также работает с правилами области видимости C ++ для обеспечения быстрого уничтожения объекта.

Это считается неважным в языках GC, которые основаны на идее, что память заменим .Не нужно срочно восстанавливать память объекта, если в другом месте достаточно памяти для размещения новых объектов.Не заменимые ресурсы, такие как файловые дескрипторы, сокеты и мьютексы, считаются особым случаем, к которому нужно обращаться специально (например, with).Это отличается от модели C ++, которая обрабатывает все ресурсы одинаково.

Как только переменная выскакивает из стека, она уничтожается.

Python не имеет переменных стека,В терминах C ++, все является shared_ptr.

Python делает некоторую область видимости, но не на уровне отступа, просто на функциональном уровне.Кажется глупым требовать, чтобы я сделал новую функцию только для того, чтобы охватить переменные, чтобы я мог повторно использовать имя.

Он также выполняет поиск на уровне понимания генератора (и в3.x, в все понимания).

Если вы не хотите стучать по переменным цикла for, не используйте столько циклов for.В частности, не-Pythonic использовать append в цикле.Вместо:

new_points = []
for x,y,z in surface.points:
    ...     # Do something with the points
    new_points.append( (x,y,z) )

напишите:

new_points = [do_something_with(x, y, z) for (x, y, z) in surface.points]

или

# Can be used in Python 2.4-2.7 to reduce scope of variables.
new_points = list(do_something_with(x, y, z) for (x, y, z) in surface.points)
2 голосов
/ 08 февраля 2018

При переходе на Python после нескольких лет C ++ я обнаружил соблазн полагаться на __del__ для имитации поведения RAII-типа, например закрыть файлы или соединения. Однако существуют ситуации (например, шаблон наблюдателя, реализованный в Rx), когда наблюдаемая вещь поддерживает ссылку на ваш объект, поддерживая его в живых! Так что, если вы хотите закрыть соединение до того, как оно будет прервано источником, вы ничего не добьетесь, если попытаетесь сделать это в __del__.

В программировании пользовательского интерфейса возникает следующая ситуация:

class MyComponent(UiComponent):

    def add_view(self, model):
        view = TheView(model) # observes model
        self.children.append(view)

    def remove_view(self, index):
        del self.children[index] # model keeps the child alive

Итак, вот способ получить поведение RAII-типа: создать контейнер с хуками add и remove:

import collections

class ScopedList(collections.abc.MutableSequence):

    def __init__(self, iterable=list(), add_hook=lambda i: None, del_hook=lambda i: None):
        self._items = list()
        self._add_hook = add_hook
        self._del_hook = del_hook
        self += iterable

    def __del__(self):
        del self[:]

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, item):
        self._del_hook(self._items[index])
        self._add_hook(item)
        self._items[index] = item

    def __delitem__(self, index):
        if isinstance(index, slice):
            for item in self._items[index]:
                self._del_hook(item)
        else:
            self._del_hook(self._items[index])
        del self._items[index]

    def __len__(self):
        return len(self._items)

    def __repr__(self):
        return "ScopedList({})".format(self._items)

    def insert(self, index, item):
        self._add_hook(item)
        self._items.insert(index, item)

Если UiComponent.children - это ScopedList, который вызывает методы acquire и dispose для дочерних элементов, вы получаете ту же гарантию получения и удаления детерминированных ресурсов, что и в C ++.

2 голосов
/ 25 августа 2011

В основном вы используете не тот язык. Если вам нужны разумные правила определения границ и надежное уничтожение, используйте C ++ или попробуйте Perl. GC дебаты о том, когда память выпущена, кажется, упускают суть. Речь идет о выпуске других ресурсов, таких как мьютексы и файловые дескрипторы. Я считаю, что C # делает различие между деструктором, который вызывается, когда счетчик ссылок падает до нуля, и когда он решает перезапустить память. Люди не так озабочены переработкой памяти, но хотят знать, как только на нее больше не ссылаются. Жаль, что у Python был реальный потенциал как языка. Но это нетрадиционная область видимости и ненадежные деструкторы (или, по крайней мере, зависящие от реализации) означают, что человеку отказано в силе, которую вы получаете с C ++ и Perl.

Интересен комментарий, сделанный только об использовании новой памяти, если она доступна, а не об утилизации старой в GC. Разве это не просто причудливый способ сказать, что это утечка памяти: -)

...