带参数的装饰器动态传值
有一个函数,返回字符串类型, 现在需要在这个 字符串上添加链接。或者添加一个 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.version
和 noopener noreferrer nofollow
。 然后还有一个 函数 希望添加p 标签,没有属性。
跑一下 代码结果如下:
一切都很正常, 装饰器完成了期待的功能。
那么现在有一个问题来了, 被装饰器装饰的两个函数 first_official
,second_para
通过传入参数 tagname , attrs 来给标签添加属性值。 这里有一个问题, 在装饰器 要修饰的函数first_official
我们要知道 给这个函数返回值 添加 a 标签, 并且需要传入 attrs ,里面有一个属性 href 代表 要添加的链接。
注意 红色框里面的,就是我们添加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
这里 注意 我们刚刚的装饰器 放入了,Model 类里面, 然后添加self 这个变量。 这样就可以通过 model 来动态抓取URL, 来改变 href 的值了。
然后调用的时候 我并不需要传入这个href ,但是要传入这个key ,href ,注意这里我用'xxx'
表示一个url
在装饰器里面实现的时候,通过 被装饰的函数名,来动态的绑定 url ,这个url 是从网页抓取而来, 这里craw_url
这个方法 来模拟实现 抓取url 。
让我们来测试一下整个代码:
从结果上面看已经可以正常实现了,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)
通过 self 作为 wrapper 的参数 即可, 这样就可以获取 self。 即类的实例对象, 然后就可以进行其他的操作了。
总结一下
有点时候希望装饰器是带参数的, 但是参数的值,并不是一开始就是确定的, 那么我们就可以使用这种方法,来动态改变该值。
分享快乐,留住感动. '2021-03-07 17:59:43' --frank