Узнайте, как использовать обертки классов для более чистых API плагинов, основанных на объектно-ориентированном программировании, в Python.
Классы в Plugify предоставляют объектно-ориентированный способ работы с ресурсами плагинов, обеспечивая автоматическое управление жизненным циклом и более чистые API. Вместо ручного управления дескрипторами и вызова функций с явными указателями, вы можете использовать интуитивные интерфейсы на основе классов.
Работа с API на основе дескрипторов в традиционном стиле C может быть многословной и подверженной ошибкам:
Без классов (стиль C)
from plugify.pps import s2sdk
# Создать дескриптор KeyValues вручную
kv_handle = s2sdk.Kv1Create("MyConfig")
# Установить свойства, используя дескриптор
s2sdk.Kv1SetName(kv_handle, "ServerSettings")
# Найти подключ
subkey_handle = s2sdk.Kv1FindKey(kv_handle, "Players")
# Легко забыть очистку!
s2sdk.Kv1Destroy(subkey_handle)
s2sdk.Kv1Destroy(kv_handle)
С классами тот же код становится намного чище и безопаснее:
С классами (стиль ООП)
from plugify.pps import s2sdk
# Создать с использованием конструктора класса
with s2sdk.KeyValues("MyConfig") as kv:
# Использовать интуитивные методы
kv.SetName("ServerSettings")
# Найти подключ - возвращает экземпляр KeyValues
subkey = kv.FindKey("Players")
# Автоматическая очистка при выходе из контекста!
Преимущества:
Более чистый синтаксис - Методы вместо функций с явными дескрипторами
Автоматическое управление ресурсами - Нет необходимости вручную вызывать функции уничтожения
Безопасность типов - Лучшее автодополнение в IDE и проверка типов
Меньше ошибок - Труднее забыть очистку или перепутать дескрипторы
Питонический API - Ощущается естественным для разработчиков на Python
Возвращает True, если дескриптор действителен (не равен invalidValue):
Использование valid()
from plugify.pps import s2sdk
kv = s2sdk.KeyValues("Config")
if kv.valid():
kv.SetName("ServerConfig")
print("Handle is valid")
else:
print("Handle is invalid")
# После release дескриптор становится недействительным
handle = kv.release()
print(kv.valid()) # False
Возвращает значение базового дескриптора. Используйте это, когда вам нужно передать необработанный дескриптор в функции в стиле C:
Использование get()
from plugify.pps import s2sdk
kv = s2sdk.KeyValues("Config")
# Получить необработанный дескриптор
raw_handle = kv.get()
print(f"Handle value: {raw_handle}")
# Передать в функцию, ожидающую необработанный дескриптор
some_c_function(kv.get())
Предупреждение: Будьте осторожны при использовании get(). Возвращаемый дескриптор все еще принадлежит экземпляру класса и будет уничтожен при очистке экземпляра.
Освобождает владение дескриптором и возвращает его. После вызова release() экземпляр класса становится недействительным и не будет вызывать деструктор:
Использование release()
from plugify.pps import s2sdk
def create_and_release():
kv = s2sdk.KeyValues("Config")
kv.SetName("ServerConfig")
# Передать владение наружу
handle = kv.release()
# kv теперь недействителен, не будет очищать
print(kv.valid()) # False
return handle
# Теперь мы владеем дескриптором и должны очистить его вручную
raw_handle = create_and_release()
# ... использовать raw_handle ...
s2sdk.Kv1Destroy(raw_handle) # Требуется ручная очистка!
Используйте release(), когда вам нужно:
Передать владение другой системе
Сохранить дескриптор в долгоживущей структуре данных
Взаимодействовать с кодом в стиле C, который принимает владение
Когда определен деструктор, классы автоматически поддерживают оператор with через __enter__ и __exit__:
Менеджер контекста
from plugify.pps import s2sdk
with s2sdk.KeyValues("Config") as kv:
kv.SetName("ServerConfig")
kv.SetString("hostname", "My Server")
# ... использовать kv ...
# Автоматически очищается при выходе из блока 'with'
Это рекомендуемый паттерн, потому что:
Гарантированная очистка даже при возникновении исключений
Деструктор (__del__) вызывается автоматически при сборке мусора объекта:
Автоматическая очистка
from plugify.pps import s2sdk
def process_config():
kv = s2sdk.KeyValues("Config")
kv.SetName("ServerConfig")
# ... использовать kv ...
# kv автоматически уничтожается при возврате из функции
process_config() # kv очищается здесь
Важно - Жизненный цикл плагина: Финализаторы CPython (__del__) детерминированы и вызываются, когда счетчик ссылок достигает нуля. Однако вы должны избегать ситуаций, когда финализаторы вызываются после выгрузки вашего плагина. Если вы храните объекты глобально, вы несете ответственность за их очистку в pluginEnd(), иначе поведение будет неопределенным и может привести к сбоям.
Для немедленной очистки ресурсов используйте close() или удалите объект:
Ручная очистка
from plugify.pps import s2sdk
kv = s2sdk.KeyValues("Config")
kv.SetName("ServerConfig")
# ... использовать kv ...
# Вариант 1: Явно закрыть
kv.close()
# Вариант 2: Удалить объект
kv2 = s2sdk.KeyValues("Config2")
del kv2
# Оба приводят к немедленной очистке
Если в манифесте класс не имеет определенного деструктора, он действует как простая обертка без автоматической очистки:
Обертка без деструктора
from plugify.pps import s2sdk
# Класс без деструктора - просто удобная обертка
wrapper = s2sdk.SomeWrapper()
# Все еще имеет утилитарные методы
if wrapper.valid():
handle = wrapper.get()
# Нет автоматической очистки - дескриптор сохраняется
# Полезно для оберток без состояния или глобальных ресурсов
Когда метод принимает владение ресурсом, вы должны передать его и не использовать после этого:
Передача владения
from plugify.pps import s2sdk
# Создать родителя и ребенка
parent = s2sdk.KeyValues("Parent")
child = s2sdk.KeyValues("Child")
# AddSubKey принимает владение child
parent.AddSubKey(child)
# child теперь принадлежит parent
# child.valid() может все еще быть True, но не используйте его!
# parent обработает очистку
В манифесте это определяется как:
Владение в манифесте
{
"name": "AddSubKey",
"method": "Kv1AddSubKey",
"bindSelf": true,
"paramAliases": [
{
"name": "subKey",
"owner": true // Этот параметр принимает владение
}
]
}
Лучшая практика: После передачи владения избегайте использования объекта:
Правильная обработка владения
from plugify.pps import s2sdk
parent = s2sdk.KeyValues("Parent")
child = s2sdk.KeyValues("Child")
# Передать владение
parent.AddSubKey(child)
# Освободить нашу ссылку, чтобы предотвратить случайное использование
child_handle = child.release() # Теперь child.valid() == False
# Использовать только через parent
found = parent.FindKey("Child")
from plugify.pps import s2sdk
parent = s2sdk.KeyValues("Parent")
# FindKey возвращает НОВЫЙ KeyValues, которым мы владеем
child = parent.FindKey("Settings")
if child and child.valid():
child.SetName("UpdatedSettings")
# Мы ответственны за жизненный цикл child
# Он будет автоматически очищен, когда выйдет из области видимости
Когда owner: false, метод возвращает ссылку без передачи владения:
Невладеющая ссылка
from plugify.pps import s2sdk
parent = s2sdk.KeyValues("Parent")
# GetFirstSubKey возвращает ссылку, parent все еще владеет ею
child_ref = parent.GetFirstSubKey()
if child_ref and child_ref.valid():
# Используйте ссылку, но НЕ удаляйте ее
name = child_ref.GetName()
# Не вызывайте del child_ref или release()
# child_ref будет очищен parent
Важно: С невладеющими ссылками возвращаемый объект все еще действителен (имеет дескриптор), но вы не владеете им. Удаление родителя сделает эти ссылки недействительными.
При использовании классов в плагинах вы должны быть осторожны с очисткой объектов во время выгрузки плагина. CPython использует подсчет ссылок, что означает, что финализаторы (__del__) детерминированы и вызываются немедленно, когда счетчик ссылок достигает нуля. Однако, если объекты все еще живы, когда ваш плагин выгружается, их финализаторы могут быть вызваны после того, как код плагина больше не находится в памяти, что приводит к неопределенному поведению или сбоям.
Если вы храните экземпляры классов в глобальных переменных или атрибутах уровня модуля, вы должны явно очистить их в функции pluginEnd() вашего плагина:
plugin.py
from plugify import PluginEntryPoint
from plugify.pps import s2sdk
# Глобальный объект - опасен, если не очищен!
g_config = None
class MyPlugin(PluginEntryPoint):
def pluginStart(self):
global g_config
# Создать глобальную конфигурацию
g_config = s2sdk.KeyValues("GlobalConfig")
g_config.SetName("ServerSettings")
print("Plugin started with global config")
def pluginEnd(self):
global g_config
# КРИТИЧНО: Очистить глобальные объекты перед выгрузкой плагина
if g_config:
g_config.close() # или del g_config
g_config = None
print("Plugin ended, global config cleaned up")
Критично: Неспособность очистить глобальные объекты в pluginEnd() может привести к тому, что финализаторы запустятся после выгрузки вашего плагина, что приведет к сбоям или неопределенному поведению. Всегда явно очищайте глобальные ресурсы!
✅ Безопасно: Локальная область видимости с автоматической очисткой
Безопасный паттерн
class MyPlugin(PluginEntryPoint):
def onCommand(self, args):
# Локальный объект - автоматически очищается при возврате из функции
kv = s2sdk.KeyValues("TempConfig")
kv.SetName("CommandConfig")
# ... использовать kv ...
# Автоматически уничтожается здесь - безопасно!
✅ Безопасно: Менеджер контекста
Безопасный паттерн
class MyPlugin(PluginEntryPoint):
def onCommand(self, args):
# Менеджер контекста обеспечивает очистку перед возвратом из функции
with s2sdk.KeyValues("TempConfig") as kv:
kv.SetName("CommandConfig")
# ... использовать kv ...
# Определенно очищается здесь - безопасно!
✅ Безопасно: Переменная экземпляра с очисткой
Безопасный паттерн
class MyPlugin(PluginEntryPoint):
def pluginStart(self):
self.config = s2sdk.KeyValues("PluginConfig")
self.config.SetName("Settings")
def pluginEnd(self):
# Очистить переменные экземпляра
if hasattr(self, 'config') and self.config:
self.config.close()
self.config = None
❌ Небезопасно: Глобальная без очистки
Небезопасный паттерн
from plugify.pps import s2sdk
# ОПАСНО: Глобальный объект
g_config = s2sdk.KeyValues("GlobalConfig")
class MyPlugin(PluginEntryPoint):
def pluginStart(self):
g_config.SetName("ServerSettings")
def pluginEnd(self):
# ОТСУТСТВУЕТ: Нет очистки!
# Финализатор g_config запустится после выгрузки плагина - СБОЙ!
pass
❌ Небезопасно: Кэш уровня модуля без очистки
Небезопасный паттерн
from plugify.pps import s2sdk
# ОПАСНО: Кэш уровня модуля
_kv_cache = {}
class MyPlugin(PluginEntryPoint):
def cacheConfig(self, name):
_kv_cache[name] = s2sdk.KeyValues(name)
def pluginEnd(self):
# ОТСУТСТВУЕТ: Нет очистки кэша!
# Кэшированные объекты завершатся после выгрузки плагина - СБОЙ!
pass
Иногда вам нужно работать с необработанными дескрипторами вместе с экземплярами классов:
mixed_usage.py
from plugify.pps import s2sdk
def process_with_mixed_api():
# Создать с использованием класса
kv = s2sdk.KeyValues("Config")
# Использовать методы класса
kv.SetName("ProcessedConfig")
# Получить необработанный дескриптор для функции в стиле C
raw_handle = kv.get()
# Вызвать устаревшую функцию в стиле C
result = s2sdk.SomeLegacyFunction(raw_handle)
# Продолжить использование методов класса
if kv.valid():
kv.SaveToFile("output.kv")
# Автоматическая очистка все еще работает
# kv будет уничтожен при возврате из функции
def transfer_ownership_example():
# Создать и настроить
kv = s2sdk.KeyValues("Config")
kv.SetName("TransferTest")
# Освободить владение
handle = kv.release()
# kv теперь недействителен
assert not kv.valid()
# Мы должны вручную уничтожить
s2sdk.Kv1Destroy(handle)
# Или обернуть снова
kv2 = s2sdk.KeyValues("NewConfig")
# ... использовать kv2 ...
Всегда используйте менеджеры контекста - Используйте операторы with для гарантированной очистки
Используйте close() для немедленной очистки - Более явно, чем полагаться на del или сборку мусора
Очищайте глобальные объекты в pluginEnd() - Критично: Явно очищайте все глобальные или уровня модуля экземпляры классов перед выгрузкой плагина, чтобы избежать сбоев
Предпочитайте локальную область видимости - Держите экземпляры классов в локальной области видимости, когда возможно, для автоматической очистки
Проверяйте valid() для безопасности - Особенно после операций, которые могут завершиться неудачей
Уважайте владение - Не используйте объекты после передачи владения
Используйте release() экономно - Только когда вам нужен ручной контроль
Избегайте смешивания стилей - Предпочитайте API классов необработанным функциям в стиле C
Доверяйте автоматической очистке для локальных объектов - Пусть __del__ обрабатывает очистку для локальных объектов, но всегда очищайте глобальные вручную
Обрабатывайте RuntimeError - Будьте готовы перехватывать исключения от методов, вызванных на закрытых дескрипторах
Проверяйте возвращаемые объекты - Методы могут возвращать None при неудаче
Если конструктор вызывает исключение, базовая функция создания вернула недопустимый дескриптор:
Сбой конструктора дескриптора
try:
kv = s2sdk.KeyValues("Config")
except RuntimeError as e:
print(f"Failed to create KeyValues: {e}")
# Обработать ошибку - возможно повторить или использовать конфигурацию по умолчанию
Попытка использовать объект после вызова close(), release() или после уничтожения вызовет RuntimeError:
Использование закрытого дескриптора
from plugify.pps import s2sdk
kv = s2sdk.KeyValues("Config")
kv.close()
# Это вызовет RuntimeError
try:
kv.SetName("Test")
except RuntimeError as e:
print(e) # "KeyValues handle is closed"
# Сначала проверьте valid(), чтобы избежать исключений
if kv.valid():
kv.SetName("Test")
else:
print("Handle is closed!")
Все связанные методы автоматически проверяют дескриптор перед вызовом базовой функции C:
Автоматическая проверка
from plugify.pps import s2sdk
kv = s2sdk.KeyValues("Config")
handle = kv.release()
# Любой вызов метода завершится неудачей
try:
name = kv.GetName()
except RuntimeError as e:
print(e) # "KeyValues handle is closed"