【问题标题】:Use a class decorator to implement late-initialization使用类装饰器实现后期初始化
【发布时间】:2013-03-13 17:19:36
【问题描述】:

我正在使用一些需要连接到数据库的类。只有在执行实际操作时才真正需要连接。我想延迟连接阶段,直到真正需要它。为此,我想做类似的事情:

class MyClass

    def __init__(self):
        self.conn = None

    def connect(self):
        if self.conn : return
        self.conn = ConnectToDatabase()

    @connect
    def do_something1(self):
        self.conn.do_something1()

    @connect
    def do_something2(self):
        self.conn.do_something2()

但我不知道如何为类定义 connect 装饰器。

我当然可以这样做:

    def do_something1(self):
        self.connect()
        self.conn.do_something1()

但使用装饰器似乎是一种更易读的解决方案。有可能吗?

【问题讨论】:

    标签: python decorator lazy-initialization


    【解决方案1】:

    与其尝试装饰需要连接的函数,不如使用属性来获取连接本身。

    class MyClass(object):
    
        def __init__(self):
            self._conn = None
    
        @property
        def conn(self):
            if self._conn is None:
                self._conn = ConnectToDatabase()
            return self._conn
    
        def do_something1(self):
            self.conn.do_something1()
    
        def do_something2(self):
            self.conn.do_something2()
    

    至于直接的装饰器示例,玩弄 F.J 的答案:

    def prerequisite(prerequisite_function, *pre_args, **pre_kwargs):
        def wrapper(func):
            def wrapped(self, *args, **kwargs):
                prerequisite_function(self, *pre_args, **pre_kwargs)
                return func(self, *args, **kwargs)
            return wrapped
        return wrapper
    
     class MyClass(object):
    
         def __init__(self):
             self.conn = None
    
         def connect(self):
             if self.conn is None:
                 self.conn = ConnectToDatabase()
    
         @prerequisite(connect)
         def do_something(self):
             self.conn.do_something()
    

    您还可以使prerequisite 更健壮,方法是让它创建描述符,以便它可以正确地处理函数和静态方法以及类和实例方法。

    【讨论】:

    • 如果你停止学习,你就已经过时了,将被更新的模型取代。
    【解决方案2】:

    我确实喜欢 sr2222 使用属性来获取连接的方法,但是这里有一种带有装饰器的方法,它可能有用或至少提供信息(functools.wraps() 的使用是可选的):

    import functools
    
    def require_connection(f):
        @functools.wraps(f)
        def wrapped(self, *args, **kwargs):
            self.connect()
            return f(self, *args, **kwargs)
        return wrapped
    
    class MyClass(object):
        def __init__(self):
            self.conn = None
    
        def connect(self):
            if self.conn : return
            self.conn = ConnectToDatabase()
    
        @require_connection
        def do_something1(self):
            self.conn.do_something1()
    
        @require_connection
        def do_something2(self):
            self.conn.do_something2()
    

    【讨论】:

    • 这个我没试过,但看起来很有趣。尽管如此,sr2222 回复以更简单的方式完成了我想做的事情,虽然这直接回答了我的问题,但我更喜欢其他解决方案。
    • 为什么还要在类上创建一个方法? require_connection 在创建类后的任何时候都将可用但令人困惑地无用/损坏。要么让它成为一个直接的辅助函数,要么让它在元类中更有意义。
    • @sr2222 好点,我把require_connection移出了课堂。
    【解决方案3】:

    类似于 sr2222 的解决方案,但称其为:cached_property

    代码更紧凑,使用可重用的构建块,在我看来更具可读性。

    class MyClass(object):
    
        @cached_property
        def conn(self):
            return ConnectToDatabase()
    
        def do_something1(self):
            self.conn.do_something1()
    
        def do_something2(self):
            self.conn.do_something2()
    

    cached_property的定义见here

    【讨论】:

    • property 装饰器是内置的,而 cached_property 不是。
    • 正确。它仍然是一个非常有用的构建块。抓一次,多用。
    猜你喜欢
    • 2023-04-08
    • 2014-03-15
    • 1970-01-01
    • 2018-05-04
    • 2020-02-13
    • 2013-03-17
    • 2019-01-24
    • 1970-01-01
    • 2020-12-01
    相关资源
    最近更新 更多