Как правильно завершить/очистить объекты CLR pythonnet?

Я использую python для связи с определенным программным обеспечением через предоставленный API. Поскольку API-код написан на C#, я использую pythonnet для импорта DLL и последующего ее использования. Это выгодно сделать, используя, например. Jupyter Lab или Jupyter Notebook при оптимизации кода, так как вы можете легко сравнить результаты в своем коде и в программном обеспечении. Тем не менее, я столкнулся с проблемой очистки. API требует, чтобы вы установили соединение, выполнив следующий код

import clr
clr.AddReference('API')
api = __import__('API', globals(), locals(), [], 0)
connection = api.connection()
app = connection.connect()

Теперь вы можете общаться с программой, используя app. Основная причина моей проблемы в том, что вам разрешено иметь только одно приложение в CLR. Если вы хотите создать новый, вы должны позвонить app.close(), а затем newapp = connection.connect(). Что происходит, когда вы создаете newapp без вызова app.close(), четко не определено. Я не уверен, как C# справится с этим, перезапишет ли он app в памяти, будет ли app теперь также указывать на newapp или что-то еще? При этом я еще больше не уверен, как с этим справляется python + CLR.

Чтобы убедиться, что вы всегда будете работать с правильно подключенным приложением, я создал класс, который позволяет присутствовать только одному экземпляру app. Это ограничение реализовано путем оценки connection.Alive через API, что является истинным, когда приложение создано и еще не закрыто должным образом. Класс похож на:

class APIWrapper:
    def __init__(self, api):
        self.API = api
        self.Connection = api.connection()
   
    def connect():
        if self.Connection.Alive:
            raise RunTimeError('Only one live application is allowed at runtime')

        app = self.Connection.connect()
        return app

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

wrap = APIWrapper()
wrap.connect()

При этом приложение становится активным, а значение свойства wrap.Connection.Alive оценивается как True. Однако, поскольку я не присваиваю возврат wrap.connect() переменной, я не могу закрыть его с помощью app.close(). Например, если я делаю:

wrap = APIWrapper()
print(wrap.Connection.Alive)  # -> False
app = wrap.connect()
print(wrap.Connection.Alive)  # -> True
app.close()
print(wrap.Connection.Alive)  # -> False
wrap.connect()
print(wrap.Connection.Alive)  # -> True

Я больше не могу закрыть соединение. Я подумал об изменении класса, чтобы просто привязать wrap.connect() к wrap.App и разрешить доступ через атрибут. Это решило бы проблему потери приложения, но я предпочитаю не вызывать Wrap.App постоянно для удобочитаемости кода. Кроме того, мне просто интересно, есть ли правильный способ решения этих проблем с завершением?


person Lucvv    schedule 25.11.2020    source источник


Ответы (1)


Прежде всего, если проблема заключается в вызове wrap.connect() без сохранения возвращаемого значения в любом месте, то для нее есть простое решение: не делайте этого! Похоже, что соединение — это ресурс, поэтому вы должны следить за ним, чтобы освободить его должным образом, когда придет время.

В вашем примере, что должно произойти с ранее созданным соединением, когда кто-то снова позвонит connect()?

Во-вторых, в Python есть два способа явного отслеживания ресурсов:

  1. with операторов + менеджеры контекста (настоятельно рекомендуется). В этом случае вам нужно будет реализовать контекстный менеджер на ваших оболочках.
  2. __del__ функцию, которую вы можете определить, которая будет вызываться, когда объект больше не нужен. Этого вам следует избегать, потому что оно будет выполняться в произвольное время, а это означает, что когда вы пытаетесь создать новое соединение, старое все еще может быть рядом, потому что Python еще не понял, что он должен вызывать __del__.

Другой вариант — сделать одноэлементным.

person LOST    schedule 03.12.2020
comment
Я согласен, что мне не следует этого делать, основная причина, по которой я задал этот вопрос, заключается в том, что я пишу обертку вокруг API и хочу, чтобы она была максимально надежной. Таким образом, он позволяет динамически взаимодействовать с API и гарантирует, что вам не придется перезапускать все, если вы случайно вызовете wrap.connect. Я также смотрел на __del__, а также на реализацию weakref.finalize. __del__ не работало, так как соединение каким-то образом все еще присутствовало в clr, а weakref.finalize() также не работало. Поэтому я реализовал это по-другому. (Но посмотрю на синглтоны!) - person Lucvv; 04.12.2020