装饰器-带参数的装饰器动态传值

带参数的装饰器动态传值


​ 有一个函数,返回字符串类型, 现在需要在这个 字符串上添加链接。或者添加一个 p 标签 , 这里很容易想到可以用一个装饰器来完成这个任务。

装饰器实现的简单实现

相信很多的同学,都可以想到,这个还不简单 直接使用带参数的装饰器 不就可以搞定了。

# -*- coding: utf-8 -*-
from functools import wraps
from typing import Dict

"""
pip install pysimple-log==0.0.4
"""
from simplelog import logger


def add_custom_tag(tagname='', attrs: Dict = None):
    """
    目前支持 添加  a 标签, p 标签
    :param tagname:
    :param attrs:
    :return:
    """
    if attrs is None:
        attrs = {}

    def _add_tag(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            r = fn(*args, **kwargs)
            if tagname == 'p':
                return '<p>' + str(r) + '</p>'
            elif tagname == 'a':
                href = attrs.pop('href', '')
                rel = attrs.pop('rel', '')
                first = f'<a href={href!r} rel={rel!r}>'
                tail = '</a>'
                result = first + str(r) + tail
                return result
            else:
                logger.info(f'Unkonwn tag:{tagname!r}')
                return r

        return wrapper
    return _add_tag


class Model:

    @add_custom_tag(tagname='a', attrs={
        'href': 'https://www.offical.version',
        'rel': "noopener noreferrer nofollow",
    })
    def first_official(self):
        return "official version"

    @add_custom_tag(tagname='p')
    def second_para(self):
        return "This is a paragraph"


if __name__ == '__main__':
    model = Model()
    r2 = model.first_official()
    print(r2)

    r3 = model.second_para()
    print(r3)

add_custom_tag 这里定义一个装饰器, 如果 传入tagname 为 a 标签,就给函数的返回值 添加 a 标签, 同时 attrs 表示 当前标签的属性。

Mode 类中定义了两个函数,一个first_official 希望给这个函数返回值 添加一个a 标签, 属性有两个 href , rel 属性对应的值分别为 https://www.offical.versionnoopener noreferrer nofollow 。 然后还有一个 函数 希望添加p 标签,没有属性。

跑一下 代码结果如下:

img1

一切都很正常, 装饰器完成了期待的功能。

那么现在有一个问题来了, 被装饰器装饰的两个函数 first_officialsecond_para 通过传入参数 tagname , attrs 来给标签添加属性值。 这里有一个问题, 在装饰器 要修饰的函数first_official 我们要知道 给这个函数返回值 添加 a 标签, 并且需要传入 attrs ,里面有一个属性 href 代表 要添加的链接。

img2

注意 红色框里面的,就是我们添加a 标签 , 同时要传入一个href ,对应一个链接的值。 有的时候 我们 可能不想 那么快给这个标签 一个链接, 而是 Model 类 完成一些操作之后 ,来动态获取这个值,而不是确定一个值。 不知道我有没有 说明白, href 这个值是可以变化的而,不是固定一个值。

装饰器参数动态传入值

假设 Model 类 是获取页面的url ,获取页面不同位置的url

# -*- coding: utf-8 -*-
from functools import wraps
from typing import Dict

"""
pip install pysimple-log==0.0.4
"""
from simplelog import logger


class Model:

    def __init__(self, ):
        self._all_url_map = {}

    @property
    def all_url_map(self):
        if not self._all_url_map:
            self.craw_urls()
        return self._all_url_map

    def craw_urls(self) -> Dict[str, str]:
        """
        这里我需要动态获取的url,模拟抓取URL
        :return:
        """
        # mock data
        result = {
            'table_of_contents': 'https://www.content',
            'version': 'https://www.official.version',
            'memorandum': 'https://www.memorandum',
        }
        self._all_url_map = result
        return result

    def add_custom_tag(self, tagname='', attrs: Dict = None):
        """
        目前支持 添加  a 标签, p 标签
        :param tagname:
        :param attrs:
        :return:
        """
        if attrs is None:
            attrs = {}

        def _add_tag(fn):
            @wraps(fn)
            def wrapper(self, *args, **kwargs):
                r = fn(self, *args, **kwargs)
                fun_name = fn.__name__
                logger.info(f"fun_name:{fun_name}")
                if tagname == 'p':
                    return '<p>' + str(r) + '</p>'
                elif tagname == 'a':
                    if fun_name == '_first_official':
                        href = self.all_url_map['version']
                    elif fun_name == '_the_third_explanatory_memorandum':
                        href = self.all_url_map['memorandum']
                    else:
                        logger.info(f'Unkown function_name: {fun_name!r}')
                        href = attrs.pop('href', '')

                    rel = attrs.pop('rel', '')
                    first = f'<a href={href!r} rel={rel!r}>'
                    tail = '</a>'
                    result = first + str(r) + tail
                    return result
                else:
                    print(f'Unkonwn tag:{tagname!r}')
                    return r

            return wrapper

        return _add_tag

    @add_custom_tag(self='self', tagname='a', attrs={
        'href': 'xxxxxx',
        'rel': "noopener noreferrer nofollow",
    })
    def _first_official(self):
        return "official version"

    @add_custom_tag(self='self', tagname='a', attrs={
        'href': 'xxx',
        'rel': "noopener noreferrer nofollow",
    })
    def _the_third_explanatory_memorandum(self):
        return "the explanatory memorandum"

    @add_custom_tag(self='self', tagname='p')
    def _sec_para(self):
        return 'hello world'

    def parse_further_reading(self):
        """
        :return:
        """
        first = self._first_official()
        sec = self._sec_para()
        third = self._the_third_explanatory_memorandum()
        return first + sec + third


if __name__ == '__main__':
    model = Model()
    r3 = model.parse_further_reading()

    print(r3)


这里模拟抓取url

img3

img04

这里 注意 我们刚刚的装饰器 放入了,Model 类里面, 然后添加self 这个变量。 这样就可以通过 model 来动态抓取URL, 来改变 href 的值了。

然后调用的时候 我并不需要传入这个href ,但是要传入这个key ,href ,注意这里我用'xxx' 表示一个url

img06

在装饰器里面实现的时候,通过 被装饰的函数名,来动态的绑定 url ,这个url 是从网页抓取而来, 这里craw_url 这个方法 来模拟实现 抓取url 。

让我们来测试一下整个代码:

img06

从结果上面看已经可以正常实现了,url 就是通过 craw_url 来抓取的值,来完成的。

把装饰器实例方法 改成静态方法

注意到装饰器里面传入一个'self' 感觉奇怪 ,可以把装饰器定义成静态方法。

但是调用的时候,会报错 TypeError: 'staticmethod' object is not callable

add_custom_tag = add_custom_tag.__func__ 还是要强行转一下。

from functools import wraps
from typing import Dict

"""
pip install pysimple-log==0.0.4
"""
from simplelog import logger


class Model:

    def __init__(self, ):
        self._all_url_map = {}

    @property
    def all_url_map(self):
        if not self._all_url_map:
            self.craw_urls()
        return self._all_url_map

    def craw_urls(self) -> Dict[str, str]:
        """
        这里我需要动态获取的url,模拟抓取URL
        :return:
        """
        # mock data
        result = {
            'table_of_contents': 'https://www.content',
            'version': 'https://www.official.version',
            'memorandum': 'https://www.memorandum',
        }
        self._all_url_map = result
        return result

    @staticmethod
    def add_custom_tag(tagname='', attrs: Dict = None):
        """
        目前支持 添加  a 标签, p 标签
        :param tagname:
        :param attrs:
        :return:
        """
        if attrs is None:
            attrs = {}

        def _add_tag(fn):
            @wraps(fn)
            def wrapper(self, *args, **kwargs):
                r = fn(self, *args, **kwargs)
                fun_name = fn.__name__
                logger.info(f"fun_name:{fun_name}")
                if tagname == 'p':
                    return '<p>' + str(r) + '</p>'
                elif tagname == 'a':
                    if fun_name == '_first_official':
                        href = self.all_url_map['version']
                    elif fun_name == '_the_third_explanatory_memorandum':
                        href = self.all_url_map['memorandum']
                    else:
                        logger.info(f'Unkown function_name: {fun_name!r}')
                        href = attrs.pop('href', '')

                    rel = attrs.pop('rel', '')
                    first = f'<a href={href!r} rel={rel!r}>'
                    tail = '</a>'
                    result = first + str(r) + tail
                    return result
                else:
                    logger.info(f'Unkonwn tag:{tagname!r}')
                    return r

            return wrapper
        return _add_tag
    
    # 把静态方法 转成 function
    add_custom_tag = add_custom_tag.__func__

    @add_custom_tag(tagname='a', attrs={
        'href': 'xxxxxx',
        'rel': "noopener noreferrer nofollow",
    })
    def _first_official(self):
        return "official version"

    @add_custom_tag(tagname='a', attrs={
        'href': 'xxx',
        'rel': "noopener noreferrer nofollow",
    })
    def _the_third_explanatory_memorandum(self):
        return "the explanatory memorandum"

    @add_custom_tag(tagname='p')
    def _sec_para(self):
        return 'hello world'

    def parse_further_reading(self):
        """
        :return:
        """
        first = self._first_official()
        sec = self._sec_para()
        third = self._the_third_explanatory_memorandum()
        return first + sec + third

这样看起来是好一些。同时也完成了 url 先传入,后赋值的功能。

装饰器函数写类的外面

把装饰器 写在类的外面,其实这个装饰器 本质上和类 没有关系, 只是想获取类的实例对象,取到 url 值而已。

from functools import wraps
from typing import Dict

"""
pip install pysimple-log==0.0.4
"""
from simplelog import logger


def add_custom_tag(tagname='', attrs: Dict = None):
    """
    目前支持 添加  a 标签, p 标签
    :param tagname:
    :param attrs:
    :return:
    """
    if attrs is None:
        attrs = {}

    def _add_tag(fn):
        @wraps(fn)
        def wrapper(self, *args, **kwargs):
            r = fn(self, *args, **kwargs)
            fun_name = fn.__name__
            logger.info(f"fun_name:{fun_name}")
            if tagname == 'p':
                return '<p>' + str(r) + '</p>'
            elif tagname == 'a':
                if fun_name == '_first_official':
                    href = self.all_url_map['version']
                elif fun_name == '_the_third_explanatory_memorandum':
                    href = self.all_url_map['memorandum']
                else:
                    logger.info(f'Unkown function_name: {fun_name!r}')
                    href = attrs.pop('href', '')

                rel = attrs.pop('rel', '')
                first = f'<a href={href!r} rel={rel!r}>'
                tail = '</a>'
                result = first + str(r) + tail
                return result
            else:
                logger.info(f'Unkonwn tag:{tagname!r}')
                return r

        return wrapper

    return _add_tag


class Model:

    def __init__(self, ):
        self._all_url_map = {}

    @property
    def all_url_map(self):
        if not self._all_url_map:
            self.craw_urls()
        return self._all_url_map

    def craw_urls(self) -> Dict[str, str]:
        """
        这里我需要动态获取的url,模拟抓取URL
        :return:
        """
        # mock data
        result = {
            'table_of_contents': 'https://www.content',
            'version': 'https://www.official.version',
            'memorandum': 'https://www.memorandum',
        }
        self._all_url_map = result
        return result

    # 把 静态方法转成 function
    # add_custom_tag = add_custom_tag.__func__

    @add_custom_tag(tagname='a', attrs={
        'href': 'xxxxxx',
        'rel': "noopener noreferrer nofollow",
    })
    def _first_official(self):
        return "official version"

    @add_custom_tag(tagname='a', attrs={
        'href': 'xxx',
        'rel': "noopener noreferrer nofollow",
    })
    def _the_third_explanatory_memorandum(self):
        return "the explanatory memorandum"

    @add_custom_tag(tagname='p')
    def _sec_para(self):
        return 'hello world'

    def parse_further_reading(self):
        """
        :return:
        """
        first = self._first_official()
        sec = self._sec_para()
        third = self._the_third_explanatory_memorandum()
        return first + sec + third


if __name__ == '__main__':
    model = Model()
    r3 = model.parse_further_reading()
    print(r3)

img07

通过 self 作为 wrapper 的参数 即可, 这样就可以获取 self。 即类的实例对象, 然后就可以进行其他的操作了。

总结一下

​ 有点时候希望装饰器是带参数的, 但是参数的值,并不是一开始就是确定的, 那么我们就可以使用这种方法,来动态改变该值。

分享快乐,留住感动. '2021-03-12 20:48:33' 第二次更新 --frank
分享快乐,留住感动. '2021-03-07 17:59:43' --frank
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值