写点什么

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,表示序列的每一项必须是同一类型,如果不一致,则直接返回 listList[Any] 或注明可能的类型,比如:List[Union[str, int]]


常用的类型如下:


  • Dict:格式为 Dict[str, int] 其中 keyvalue 必须的类型必须填写,尽量保证 value 类型一致,如果不一致,则直接返回 dicDict[str, Any] 或注明可能的类型,比如:Dict[str, Union[str, int]]

  • Set:格式为 Set[int],变量必须是同一类型,如果不一致,则直接返回 setSet[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 是一个回调函数,参数不限制,返回值为任意类型。


OptionalOptional[X] 等价于 Union[X, None],所以它是可选类型。


TypeVar 类型变量,主要是为静态类型检查器提供支持,用于泛型类型与泛型函数定义的参数。TypeVar 约束一个类型集合,但不允许单个约束。



除此之外,还可以使用类型绑定,确保数据类型,例如下述代码:


F = TypeVar('F', bound=FuncType)
复制代码


typing 模块还有很多其它知识点,后续逐步补充,接下来咱们在原有知识的基础上,学习点难的。

装饰器

下面要定义的是,一个处理空 None 值的装饰器,代码与应用如下所示。


from functools import wrapsfrom 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

@nullabledef 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_demodef 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()
复制代码

写在后面

以上内容就是本文的全部内容。


更多精彩


发布于: 刚刚阅读数: 2
用户头像

爬虫 100 例作者,蓝桥签约作者,博客专家 2021.02.06 加入

6 年产品经理+教学经验,3 年互联网项目管理经验; 互联网资深爱好者; 沉迷各种技术无法自拔,导致年龄被困在 25 岁; CSDN 爬虫 100 例作者。 个人公众号“梦想橡皮擦”。

评论

发布
暂无评论
5. 再次接触装饰器,增加一种数据结构代替 if_else 的写法