Обертки функций внутри класса с использованием декораторов
У меня есть класс, который взаимодействует с базой данных, и поэтому есть повторяющиеся действия (установить сеанс, зафиксировать, закрыть сеанс) до и после каждого метода-члена класса.
Следующее:
class UserDatabaseManager(object):
DEFAULT_DB_PATH = 'test.db'
def __init__(self, dbpath=DEFAULT_DB_PATH):
dbpath = 'sqlite:///' + dbpath
self.engine = create_engine(dbpath, echo=True)
def add_user(self, username, password):
Session = sessionmaker(bind=self.engine)
session = Session()
# <============================== To be wrapped
user = User(username, password)
session.add(user)
# ==============================>
session.commit()
session.close()
def delete_user(self, user):
Session = sessionmaker(bind=self.engine)
session = Session()
# <============================== To be wrapped
# Delete user here
# ==============================>
session.commit()
session.close()
Что такое идиоматический способ абстрагирования повторных вызовов сеанса с помощью функции-оболочки?
Я бы предпочел сделать это с декораторами, объявив частный _Decorators
класс внутри UserDatabaseManager
и реализации функции оболочки внутри, но тогда такой класс не сможет получить доступ к self.engine
Атрибут экземпляра внешнего класса.
2 ответа
Простой (и, на мой взгляд, самый идиоматичный) способ сделать это - обернуть шаблонный код настройки / демонтажа в диспетчере контекста, используя contextlib.contextmanager
, Вы тогда просто используете with
утверждение в функциях, которые выполняют работу (вместо того, чтобы пытаться обернуть эту функцию самой).
Например:
from contextlib import contextmanager
class UserDatabaseManager(object):
DEFAULT_DB_PATH = 'test.db'
def __init__(self, dbpath=DEFAULT_DB_PATH):
dbpath = 'sqlite:///' + dbpath
self.engine = create_engine(dbpath, echo=True)
@contextmanager
def session(self):
try:
Session = sessionmaker(bind=self.engine)
session = Session()
yield session
session.commit()
except:
session.rollback()
finally:
session.close()
def add_user(self, username, password):
with self.session() as session:
user = User(username, password)
session.add(user)
def delete_user(self, user):
with self.session() as session:
session.delete(user)
Вы можете создать простую функцию вне класса, чтобы обернуть каждый метод:
def create_session(**kwargs):
def outer(f):
def wrapper(cls, *args):
Session = sessionmaker(bind=getattr(cls, 'engine'))
session = Session()
getattr(session, kwargs.get('action', 'add'))(f(cls, *args))
session.commit()
session.close()
return wrapper
return outer
class UserDatabaseManager(object):
DEFAULT_DB_PATH = 'test.db'
def __init__(self, dbpath=DEFAULT_DB_PATH):
dbpath = 'sqlite:///' + dbpath
self.engine = create_engine(dbpath, echo=True)
@create_session(action = 'add')
def add_user(self, username, password):
return User(username, password)
@create_session(action = 'delete')
def delete_user(self, user):
return User(username, password)
Как правило, операции настройки и удаления, подобные описанным выше, лучше всего помещать в контекстный менеджер:
class UserDatabaseManager(object):
DEFAULT_DB_PATH = 'test.db'
def __init__(self, dbpath=DEFAULT_DB_PATH):
dbpath = 'sqlite:///' + dbpath
self.engine = create_engine(dbpath, echo=True)
class UserAction(UserDatabaseManager):
def __init__(self, path):
UserDatabaseManager.__init__(self, path)
def __enter__(self):
self.session = sessionmaker(bind=self.engine)()
return self.session
def __exit__(self, *args):
self.session.commit()
self.session.close()
with UserAction('/the/path') as action:
action.add(User(username, password))
with UserAction('/the/path') as action:
action.remove(User(username, password))