5. 再次接触装饰器,增加一种数据结构代替 if_else 的写法
Posted 梦想橡皮擦
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了5. 再次接触装饰器,增加一种数据结构代替 if_else 的写法相关的知识,希望对你有一定的参考价值。
在 Python 中,可以使用装饰器创建复合函数,复合函数是包含多个源函数功能的单函数。
概念比较抽象,简单的说明就是装饰器可以给某个函数增加功能,并且不用改变原函数代码,在滚雪球学 Python 第二轮中,我们已经学习了装饰器的基本使用,参考博客:https://dream.blog.csdn.net/article/details/114413806
typing
前 4 篇文章下来,让橡皮擦觉得有必要先普及一下 typing
注解模块相关知识。
该模块是 Python3.5 之后新增加的功能,可以校验数据类型,但是在 Python 运行时并不强制执行函数和变量类型注解,也就是不强制验证,如果需要验证,需要借助于第三方工具。
大家可以作为知识储备先学习起来。
类型别名
可以将 Python 原来存在的数据类型,重新定义为新的类型别名,例如下述代码:
from typing import List
Ca_List = List[int]
def show_code(num: Ca_List) -> Ca_List:
return num * 3
a = show_code([1, 2, 3])
print(a)
从 typing
模块导入了 List
,表示序列的每一项必须是同一类型,如果不一致,则直接返回 list
或 List[Any]
或注明可能的类型,比如:List[Union[str, int]]
。
常用的类型如下:
Dict
:格式为Dict[str, int]
其中key
、value
必须的类型必须填写,尽量保证value
类型一致,如果不一致,则直接返回dic
或Dict[str, Any]
或注明可能的类型,比如:Dict[str, Union[str, int]]
;Set
:格式为Set[int]
,变量必须是同一类型,如果不一致,则直接返回set
或Set[Any]
或 注明可能的类型,比如:Set[Union[str, int]]
;List
:上述已经说明;Tuple
:格式为Tuple[int, str]
,如果有多个同类型返回值,可写为Tuple[str, ...]
;FrozenSet
:冻结的集合,冻结后集合不能再添加或删除任何元素。
其余需要单独说明的内容:
NewType
用 NewType
可以新创建类型,例如创建一个 Ca
:
from typing import NewType
Ca = NewType("Ca",int)
ca = Ca(521)
print(ca)
当然这种类型,只有静态检查器会强制执行这些检查。
Any
Any
是一种特殊的类型。静态类型检查器将所有类型视为与 Any
兼容,反之亦然, Any
也与所有类型相兼容。
Callable
回调函数可以使用 Callable[[Arg1Type, Arg2Type],ReturnType]
的类型注释,如果只指定回调函数的返回值类型,则可以使用 Callable[..., ReturnType]
的形式,例如下述代码:
FuncType = Callable[..., Any]
该代码表示 FuncType
是一个回调函数,参数不限制,返回值为任意类型。
Optional
Optional[X]
等价于 Union[X, None]
,所以它是可选类型。
TypeVar
类型变量,主要是为静态类型检查器提供支持,用于泛型类型与泛型函数定义的参数。TypeVar
约束一个类型集合,但不允许单个约束。
除此之外,还可以使用类型绑定,确保数据类型,例如下述代码:
F = TypeVar('F', bound=FuncType)
typing
模块还有很多其它知识点,后续逐步补充,接下来咱们在原有知识的基础上,学习点难的。
装饰器
下面要定义的是,一个处理空 None 值的装饰器,代码与应用如下所示。
from functools import wraps
from typing import Callable, Optional, Any, TypeVar
# 函数类型
FuncType = Callable[..., Any]
# 声明一个装饰器,该装饰器接收 function 参数,即函数类型的参数,返回的也是函数类型的参数
def nullable(function: FuncType) -> FuncType:
@wraps(function) # wraps 函数确保被装饰的函数能保留原始函数的属性
def null_wrapper(arg: Optional[Any]) -> Optional[Any]: # 该函数接任意类型的参数,同时返回任意类型
# 如果值为 None,则返回 None
return None if arg is None else function(arg)
return null_wrapper
@nullable
def nabs(x: Optional[int]) -> Optional[int]:
return abs(x)
data = [-1, -2, None, -10]
test1 = map(nabs, data)
test2 = map(abs, data)
print(test1)
print(list(test1))
print(test2)
print(list(test2)) # 会报错,因为类型错误,abs函数不能对空对象使用
代码中实现了一个空值判断的装饰器,被其装饰的函数可以忽略 None 值。
特别注意:装饰器只返回函数而不处理其中的数据
条件表达式
本篇博客原计划全部写成装饰器相关知识点,后来发现有的地方理解起来难度有点过大,所以切换为条件表达式在函数式编程中的应用。
在 Python 中可以使用一些数据结构,去模拟 if 语句的效果,而且效果会很好,在开始学习前,先看一下下述代码:
my_dict = {
'a': 1,
'a': 2
}
print(my_dict)
上述代码演示的是当字典中,出现相同键时,会用第二个值替换第一个值。
基于上述逻辑,你可以实现一个 max
函数。
def my_max(a, b):
f = {
a >= b: lambda: a,
b >= a: lambda: b
}[True]
return f()
a = my_max(1, 2)
print(a)
上述代码先计算 a>=b
或者 a<=b
然后获取字典中的 True
键对应的值,最后返回的是 f()
(因为 f
的值是匿名函数)
如果 a==b
返回那个值都可以。
基于此,我们可以实现一个阶乘函数。
def factorial(n: int) -> int:
f = {
n == 0: lambda n: 1,
n == 1: lambda n: 1,
n == 2: lambda n: 2,
n > 2: lambda n: factorial(n - 1) * n
}[True]
return f(n)
运行一下上述代码,理解一下这样的写法吧,这种写法就修改了传统的 if...else
结构,通过一种数据结构进行了代替。
最后再补充一遍装饰器基本用法
在 Python 中用装饰器可以修改函数,或者类的可调用对象(类的实例)。
被装饰器修饰的函数,在调用的时候,优先调用装饰器函数,然后在装饰器函数的内部去调用原函数。
学好装饰器的第一步是学好函数。
函数三两句那些事儿
函数是头等对象,可以赋值给其它变量
def one_func():
print("我是测试函数")
two_func = one_func
two_func()
函数可以作为其它函数的输入参数
def add(a, b):
return a + b
def math_func(x, y):
return x(y, y + 2)
a = math_func(add, 6)
print(a)
函数可以嵌套在其它函数内部
def big():
def small():
return "小函数"
print("大函数",small())
big()
被嵌套的函数可以访问父函数的作用域,一般把这个技术点叫做闭包,但需要注意的是,这种访问是只读的,嵌套的函数不能给外部变量赋值,如果违反会出现如下错误:
local variable 'big_var' referenced before assignment
函数可以作为返回值
def hello(name):
print(name, "你好")
def call(func):
name = "橡皮擦"
return func(name)
call(hello)
函数装饰器
装饰器主要的实现,都是由 wrapper
函数实现的,展示一个装饰器的案例:
# 装饰器函数
def decorator_demo(something):
def wrapper():
print("装饰器函数,可以对函数进行修饰")
# 原函数调用
something()
return wrapper
# 被装饰函数
def func():
var = "我是橡皮擦"
print(var)
# 将被装饰函数作为参数传递给装饰器函数
code = decorator_demo(func)
code()
上述代码就是装饰器,可以看到就是主函数作为参数传递给了装饰器,在 Python 中对该内容存在一个语法糖 @
,可以直接替换 decorator_demo(func)
。
# 装饰器函数
def decorator_demo(something):
def wrapper():
print("装饰器函数,可以对函数进行修饰")
# 原函数调用
something()
return wrapper
# 被装饰函数
@decorator_demo
def func():
var = "我是橡皮擦"
print(var)
func()
上述代码虽然使用原函数名 func
调用函数,但是函数在运行的时候确被装饰器劫持了,修改了函数的执行过程。
类中的装饰器
首先定义一个类,一个简单的类:
class Boy:
def __init__(self, name, age):
self.name = name
self.age = age
def get_age(self):
return self.age
def get_name(self):
return self.name
def __repr__(self):
return f"重写:{self.name},{self.age}"
b = Boy("橡皮擦", 18)
print(b)
print(b.get_name())
get_name
方法必须由类的对象,即 b
去调用,无法直接通过 Boy
类名调用,下面通过装饰器 @staticmethod
,创建一个静态方法,该方法可以直接被调用,静态方法没有 self
参数,可以应用于实例与类本身。
类中除了静态方法以外,还有类方法,它使用的装饰器是 @classmethod
。
类方法的参数为 cls
,使用之后,可以判断出类名,包括其子类。所有的内容都集成在下述代码,可以敲打一遍进行学习。
class Boy:
def __init__(self, name, age):
self.name = name
self.age = age
@staticmethod
def show():
print("这是一个静态方法")
@classmethod
def say_hello(cls):
print("类方法")
print(cls.__name__)
def get_age(self):
return self.age
def get_name(self):
return self.name
def __repr__(self):
return f"重写:{self.name},{self.age}"
class Little_Boy(Boy):
def __init__(self):
pass
b = Boy("橡皮擦", 18)
print(b)
print(b.get_name())
# 对象可以调用
b.show()
# 类名可以调用
Boy.show()
# 类方法
b.say_hello()
lb = Little_Boy()
lb.say_hello()
写在后面
以上内容就是本文的全部内容。
今天是持续写作的第 221 / 365 天。
可以关注,点赞、评论、收藏。
更多精彩
以上是关于5. 再次接触装饰器,增加一种数据结构代替 if_else 的写法的主要内容,如果未能解决你的问题,请参考以下文章