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

举报
梦想橡皮擦 发表于 2021/09/28 20:29:38 2021/09/28
【摘要】 在 Python 中,可以使用装饰器创建复合函数,复合函数是包含多个源函数功能的单函数。概念比较抽象,简单的说明就是装饰器可以给某个函数增加功能,并且不用改变原函数代码,在滚雪球学 Python 第二轮中,我们已经学习了装饰器的基本使用,参考博客:https://dream.blog.csdn.net/article/details/114413806 typing前 4 篇文章下来,让橡皮...

在 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 是一个回调函数,参数不限制,返回值为任意类型。

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 天。
可以关注点赞评论收藏

更多精彩

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。