python 使上下文管理器类成为装饰器

bogh5gae  于 2023-08-02  发布在  Python
关注(0)|答案(1)|浏览(134)

我有一个用于数据库连接的类作为上下文管理器:

class Database:
    def __init__(self):
        self._conn = psycopg2.connect(host=os.environ['DB_SERVER'],
                            database=os.environ['DB_NAME'],
                            user=os.environ['DB_USER'],
                            password=os.environ['DB_PASSWORD'])
        self._cursor = self._conn.cursor()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.close()

    @property
    def connection(self):
        return self._conn

    @property
    def cursor(self):
        return self._cursor

    def commit(self):
        self.connection.commit()

    def close(self, commit=True):
        if commit:
            self.commit()
        self.cursor.close()
        self.connection.close()

    def execute(self, sql, params=None):
        self.cursor.execute(sql, params or ())

    def fetchall(self):
        return self.cursor.fetchall()

    def fetchone(self):
        return self.cursor.fetchone()

    def query(self, sql, params=None):
        self.cursor.execute(sql, params or ())
        return self.fetchall()

字符串
我想用它来装饰。我把decorator写为function with_connection,它工作得很好。

def with_connection(func):
    def wrapper(*args, **kwargs):
        with Database() as db:
            return func(db, *args, **kwargs)
    return wrapper


我使用它与其他功能如下:

@with_connection
def selectfunc(conn, *args, **kwargs):
...


现在我想在Database类中实现 call magic方法,并使用类作为装饰器,而不是使用单独的函数,我想我需要这样的东西:

def __call__(self, f):
    @functools.wraps(f)
    def decorated(*args, **kwds):
        with self as db:
            return f(db, *args, **kwds)
    return decorated


首先我得到了TypeError:init()接受1个位置参数,但给出了2个。因此,我在init方法中添加了 *args和**kwargs,但随后我得到了TypeError:call()缺少1个必需的位置参数:“f”
我希望能得到一些关于如何正确实现调用方法以及如何使用这个类作为装饰器的帮助。

bttbmeg0

bttbmeg01#

您的类是正确的,实现__call__将类转换为装饰器是正常的。为了完整起见,这里有Database类。我添加了一个connect方法来分离初始化和实际连接到数据库,这确保了它发生在更接近你的函数使用数据库的时候。

class Database:
    def __init__(self):
        pass
    
    def __enter__(self):
        self.connect()
        return self
    
    def __exit__(self, *args, **kwargs):
        self.close()
        
    def connect(self):
        print("Connecting")
        
    def close(self):
        print("Closing connection")
    
    def __call__(self, func):
        def wrapper(*args, **kwargs):
            with self as db:
                return func(db, *args, **kwargs)
        return wrapper

字符串
你得到的异常是因为你是这样使用它的:

@Database
def echo(conn, x):
    return x


而不是像这样:

@Database()
def echo(conn, x):
    return x


你需要传递一个示例,而不是类本身。在第一个例子中,__init__被调用,Python试图将self和修饰函数传递给它,这是一个太多的参数。
如果你喜欢旧的函数名,你也可以这样做:

with_connection = Database()

@with_connection
def echo(conn, x):
    return x


如果你遵循这种方法,一定要分离初始化和连接,否则你会遇到问题。

相关问题