Как мне украсить метод класса аргументами? Текущий код

def establish_con(func):

    con, meta = db.connect(config.user, config.password, config.db)
    meta.reflect(bind=con)

    def inner(self, query, con, *args, **kwargs):
        return func(self, query, con, *args, **kwargs)

    con.close()
    return inner

class DataReader:
    def __init__(self):
        self.data = {}

    @establish_con
    def execQuery(self, query, con):
        # con, meta = db.connect(config.user, config.password, config.db)
        # meta.reflect(bind=con)

        result = pd.read_sql(query, con)

        # con.close()
        return result

test = DataReader()
df = test.execQuery("Select * from backtest limit 10")
print(df)

В настоящее время первым аргументом является экземпляр класса. Я пробовал разные варианты кода, но всегда сталкивался с слишком большим / недостаточным / неопределенным количеством аргументов.

Я читал другие сообщения, как

Как передать дополнительные аргументы декоратору Python?

Декораторы с параметрами?

И другие, но все еще не могут понять это.

Изменить: не дубликат Python-декораторов в классах, так как в этом ответе не нужно передавать аргументы к функции.

0
Biarys 7 Июл 2019 в 02:25

3 ответа

Лучший ответ

Адаптация ответа @madjardi из этой публикации для использования аргументов.

import functools

class Example:

    con = "Connection"

    def wrapper(func):
        @functools.wraps(func)
        def wrap(self, *args, **kwargs):
            print("inside wrap")
            return func(self, Example.con, *args, **kwargs) # use *args to pass objects down
        return wrap

    @wrapper
    def method(self, con, arg): 
        print("METHOD {0} {1}".format(con, arg))

    wrapper = staticmethod(wrapper)


e = Example()
e.method(1) # METHOD Connection 1
1
fizzybear 6 Июл 2019 в 23:56

Вам следует заменить:

def inner(self, query, con, *args, **kwargs):
        return func(self, query, con, *args, **kwargs)

С участием:

def inner(self, query, *args, **kwargs):   # no 'con' here
        return func(self, query, con, *args, **kwargs) 

Минимальный рабочий пример, использующий вашу стратегию: (ничего не достигая своей цели):

def add_arg(method):

    def decorated_method(self, *args):
        return method(self, 10, *args)

    return decorated_method

class Data:
    @add_arg
    def summation(self, *args):
        return sum(args)


d = Data()
print(d.summation(1, 2))  # prints 13, not 3
1
cglacet 7 Июл 2019 в 00:36

Вот как я думаю, вам нужно это сделать. Я разделил код на два отдельных блока, чтобы сделать его более понятным.

Первая часть просто устанавливает минимальный каркас, чтобы позволить запускать (и следить за выполнением) код во втором блоке и держать его как можно ближе к тому, что в вашем вопросе. Это не очень важно для себя.

Декораторы с аргументами фактически становятся фабриками декораторов - в том смысле, что они должны создать возвращаемую функцию декоратора, которая затем будет применена к целевой функции или методу.

# Scaffolding
class Pandas:
    @staticmethod
    def read_sql(query, con):
        print(f'in Pandas read_sql({query!r}, {con})')
        return 'pandas_dataframe'


class Connection:
    def close(self):
        print('in Connection.close()')

    def __repr__(self):
        return '<Connection object>'

con = Connection()


class Meta:
    def reflect(self, bind=con):
        print(f'in Meta.reflect(bind={bind}')


class Database:
    def connect(self, user, password, db):
        print(f'in Database.connect({user}, {password}, {db})')
        return Connection(), Meta()

    def __repr__(self):
        return '<Database object>'

class Config:
    def __init__(self, user, password, db):
        self.user = user
        self.password = password
        self.db = db

# Set up a framework for testing.
pd = Pandas()
meta = Meta()
db = Database()
config = Config('username', 'secret', db)

С этой установленной средой, вот как можно написать декоратор.

# Decorator
def establish_con(config):

    def wrapper(method):

        def wrapped(*args, **kwargs):
            con, meta = db.connect(config.user, config.password, config.db)
            meta.reflect(bind=con)

            args = args + (con,)
            result = method(*args, **kwargs)

            con.close()

            return result

        return wrapped

    return wrapper


class DataReader:
    def __init__(self):
        self.data = {}

    @establish_con(config)
    def execQuery(self, query, con):
        return pd.read_sql(query, con)


test = DataReader()
df = test.execQuery("Select * from backtest limit 10")
print(df)

Выход:

in Database.connect(username, secret, <Database object>)
in Meta.reflect(bind=<Connection object>
in Pandas read_sql('Select * from backtest limit 10', <Connection object>)
in Connection.close()
pandas_dataframe
1
martineau 7 Июл 2019 в 01:18