使用装饰器在类中的函数包装器

时间:2018-05-05 22:06:11

标签: python decorator wrapper python-decorators

我有一个与数据库交互的类,因此在类的每个成员方法之前和之后都有重复的操作(建立会话,提交,关闭会话)。

如下:

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类并在其中实现包装函数来对装饰器执行此操作,但是这样的类无法访问{ {1}}外部类的实例属性。

2 个答案:

答案 0 :(得分:1)

您可以在类外创建一个简单的函数来包装每个方法:

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))

答案 1 :(得分:1)

一个简单的(在我看来,最常用的)方法是使用context manager将设置/拆卸样板代码包装在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)