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

Рейтинг: 0Ответов: 2Опубликовано: 19.01.2023

Я ещё нуб в питоне, если вопрос покажется смешным, но разобраться не могу и ответа ни здесь ни вообще так и не нашел

Пишу класс ActivkaBackup() в котором есть несколько методов обращающихся к FTP серверам (получение списка файлов, списка файлов по фильтру, содержимого файла и т.п.) использую ftplib.FTP для подключения требуется host,user,passwd,acct но у меня 2 сервера, основной и резервный, разные хосты разные учетки. получаю их из ini файла при инициализации объекта класса

self.main_backup_server['ftp_user'] = myini.main_backup_server['user']
self.second_backup_server['ftp_user'] = myini.second_backup_server['user']

если методы в общем случае имеют вид someMetods(self, *args, **kwargs) как сделать декоратор, который бы проверял, существует ли kwargs['second'] = True в зависимости от этого добавлял в kwargs необходимые элементы и вызывал бы someMetods(self, *args, **kwargs) с уже обновленными kwargs. Написал декоратор:

def _set_ftp_var(func):
     def wrapper(*args, **kwargs):
        if kwargs.get('second'):
            kwargs['host'] = self.second_backup_server['name']
            kwargs['user'] = self.second_backup_server['ftp_user']
            kwargs['passwd'] = self.second_backup_server['ftp_password']
            kwargs['ftp_root'] = self.second_backup_server['ftp_root']
        else:
            kwargs['host'] = self.main_backup_server['name']
            kwargs['user'] = self.main_backup_server['ftp_user']
            kwargs['passwd'] = self.main_backup_server['ftp_password']
            kwargs['ftp_root'] = self.main_backup_server['ftp_root']
        return func(self, *args, **kwargs)
    return wrapper()

но это неправильный код, в умных книгах примеры создания декоратора с передачей ему каких то параметров, а я хочу создать декоратор, который бы "копался" в параметрах оборачиваемой им функции и еще и дополнял их. А еще где его разместить, внутри класса или снаружи? Если снаружи, как ему self передать?

Буду признателен за любую мысль или ссылку на нее.

Ответы

▲ 1Принят

В вашей задаче декораторы не нужны, но если вы просто изучаете декораторы...

Нужно понимать что декоратор - просто функция (на деле Callable, поэтому это может быть и __call__ у класса). Тонкость в том, что когда выполняется код модуля сверху вниз и встречается вызов декоратора @... то питон вызывает эту функцию, передает ей декорируемый объект и замещает декорируемый объект результатом работы этой функции.

То есть обычно это "принял класс/функцию и вернул класс/функцию", но на деле можно вернуть хоть 42

А значит нет никакой магии - функции ведут себя точно так же в плане передачи параметров и доступности self

В вашем коде вы и так "копаетесь в передаваемых параметрах и дополняете их", разве что вернуть должны wrapper (функция), а не wrapper() (результат вызова этой функции)

И если вам нужен декоратор внутри класса (свой self) - то это просто функция внутри класса. Меняется только способ вызова этого декоратора - вам нужен инстанс класса чтобы внутри декоратора был доступен self. При этом он не смешивается с self декорируемой функции, который передается внутри *args

class FtpParams:

    def __init__(self, primary, second):
        self.primary = primary
        self.second = second

    def set_ftp_vars(self, func):
        def wrapper(*args, **kwargs):
            if kwargs.get('second'):
                params = self.second
                del kwargs['second']
            else:
                params = self.primary
            kwargs.update(**params)
            return func(*args, **kwargs)

        return wrapper


ftp = FtpParams(
    {'host': 'primary.com', 'user': 'user'},
    {'host': 'second.com', 'user': 'user'}
)


@ftp.set_ftp_vars
def foo(host, user):
    print(f'{host}:{user}')


class Bar():

    @ftp.set_ftp_vars
    def bar(self, host, user):
        print(f'{host}:{user}')


foo()
foo(second=True)

bar = Bar()
bar.bar()
bar.bar(second=True)
▲ 0

@vitidev Спасибо большое!!! я конечно изучаю декораторы, но всему свое место, и ваше

В вашей задаче декораторы не нужны

поставило все на место :-) "если какой то код повторяется в 3 местах просто вынеси его в отдельную функцию"

переписал

def _set_ftp_var(self, second):
    if second:
        ftp_params = {
            'host': self.second_backup_server['name'],           
            'user': self.second_backup_server['ftp_user'],       
            'passwd': self.second_backup_server['ftp_password'],
            'acct' : self.second_backup_server['ftp_user'],
            } 
        ftp_root  = self.second_backup_server['ftp_root']
    else:
        ftp_params = {
            'host': self.main_backup_server['name'],           
            'user': self.main_backup_server['ftp_user'],       
            'passwd': self.main_backup_server['ftp_password'],
            'acct' : self.main_backup_server['ftp_user'],
            } 
        ftp_root  = self.main_backup_server['ftp_root']
    return (ftp_root, ftp_params)

second = False прописываю везде как именованный параметр методов (так и было раньше пока не задумал улучшать и стало красивее везде:

ftp_root, ftp_params = self._set_ftp_var(second)
with FTP(**ftp_params) as con:
        con.cwd(ftp_root + segment)
        ........