Python进阶小技巧2

Python进阶小技巧

上一篇进阶小技巧阅读量尚可,给了我再写一篇的动力。这回讲一下:偏函数、强制关键字参数和字典相关的技巧。

1. 偏函数

使用偏函数之前,首先要回答2个问题:什么是偏函数?偏函数有什么用?

我看了很多文章对偏函数的解释、定义,说实话,都比较佶屈聱牙,晦涩难懂,对程序员来说,解释一百句,不如来一行代码。So, talk is cheap. Show u the code.
首先声明一个函数,对a和b求和的。

foo = lambda a, b: a+b

然后,祭出偏函数,对foo进行改造:

from functools import partial
foo_1 = partial(foo, 1)

这个foo_1,就是foo的偏函数,意思是每次调用foo_1的时候,它都会自动帮你指定a的值为1,然后执行foo的代码,返回1+b给你。这两个函数调用结果如下:

>>> foo(2,3)    # return 2+3
5
>>> foo_1(3)    # return 1+3
4

通过短短几行代码,应该可以理解偏函数的作用了。实际上partial翻译为偏函数表达的意思不是很贴切,我觉得这里partial更多的是要表达部分的意思,它的词根part就是部分,表示偏函数实现了原函数的部分功能,例如foo_1,就是实现了foo在a=1时的那一部分功能。所以把偏函数叫做“部分函数”也未尝不可。还是看英文更好理解,中文偏函数呢,第一次听完全猜不到它是干嘛的。

我已经尽我所能解释什么是偏函数了,还是没懂的话您在评论里喷我吧,我认了。

那么,偏函数有什么用呢?每次学到python稍微高级的功能时,我都会问一遍,这有什么用?除了炫技有实际意义吗?这类问题其实没有标准答案,视业务需求和实际场景而定。
偏函数就是如此,我来举个应用场景吧

def meal(beverage=None, staple=None):
    """
    设定1:小明每一餐都调用meal函数来决定自己吃什么,每一餐都包括饮料和主食。
    设定2:小明早饭必须喝牛奶,午饭必须喝可乐,晚饭则饮料随意,但主食一定要吃米饭
    :param beverage: 饮料
    :param staple: 主食
    :return: (beverage, staple)
    """
    beverages = ["milk", "cola", "wine", "tea"]
    staples = ["rice", "pizza", "steak", "noodle"]
    staple = staple or random.choice(staples)
    beverage = beverage or random.choice(beverages)
    return beverage, staple

那么根据设定,小明每天早上都要调用meal("milk"),每天中午都要调用meal("cola"),每天晚上都要调用meal(None, "rice")。这样每次都要指定不同的参数,就颇为繁琐,重新定义3个函数,那代码就显得很冗余了,此时,偏函数就可以发挥作用了:

breakfast = partial(meal, "milk")
launch = partial(meal, "cola")
dinner = partial(meal, staple="rice")

定义上述3个偏函数,早上就调用breakfast(),中午launch(),晚上dinner(),简洁,明了,优雅。

2. 强制关键字参数

python中的位置参数和关键字参数都是非常基础的概念了,这里就不再啰嗦了,主要讲一讲python3.0推出的Keyword-Only Arguments,强制关键字参数。
强制关键字参数的意思就非常明显了,就是不许用位置参数了,这里只能用关键字参数,像这样:

def foo(a, *, b):
    return a+b

这里的星号并不是参数,而是一个分隔符,表示星号后面的参数就是强制关键字参数,也就是说,只能通过foo(1, b=2)这样的形式调用,foo(1, 2)则会提示TypeError: foo() takes 1 positional argument but 2 were given,翻译一下就是foo只接受1个位置参数,而您老传了2个来,那可不行。
那强制关键字参数有什么用呢?请看如下场景:

def evaluate(chn: int, math: int, eng: int) -> None:
    _max = max(chn, math, eng)
    if _max is chn:
        print("语文成绩最好")
    elif _max is math:
        print("数学成绩最好")
    else:
        print("英语成绩最好")

evaluate的功能很简单,不做解释了。现在,秃头A调用了我的evaluate,像这样evaluate(80, 90, 28),我在review秃头A的代码,我想检查一下他有没有把成绩填错,但我撸多了记性又不好,记不得三个成绩的位置顺序,那就只能跑回去看evaluate的代码,确定一下各个位置的参数分别是什么。那我就很不爽了,每次写一串数字在那儿,谁记得清哪个参数是哪个,不让这么调了!改!

def evaluate(*, chn: int, math: int, eng: int) -> None:

这么一改,秃头A在调用的时候,只能这么写evaluate(chn=80, math=90, eng=28),那我review的时候就很舒服了,不需要再一次次的跑去检查。
反正事儿就这么个事儿,懂没懂你看着办吧。

在最近的python3.8中,又推出了Positional-Only Arguments,强制位置参数,用法和*类似,用/作为分隔符,区别在于,/前面的是强制位置参数,*后面的是强制关键字参数。

3. 字典

字典相信大家熟的不能再熟了,基础中的基础,这里讲一讲比基础稍微深入一点的内容。

首先,来看几种合并字典的方法:

>>> d1 = {"a": 1, "b": 2}
>>> d2 = {"c": 3, "d": 4}
>>> {**d1, **d2}        # 方法1
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> dict(**d1, **d2)    # 方法2
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> dict(d1, **d2)      # 方法3
{'a': 1, 'b': 2, 'c': 3, 'd': 4}
>>> {d1, **d2}          # 方法4
  File "", line 1
    {d1, **d2}
          ^
SyntaxError: invalid syntax

上面代码中的方法1是我非常喜欢的一种合并字典的方式,非常简洁漂亮,本质就是用字面量的形式创建一个字典,并将d1和d2中的键值对一一展开了传进去,所以dict()也可以用相同的方法来实现字典合并,如方法2所示。

dict()还能用方法3来实现合并,{}采用这种方式,却报错了,如方法4所示。我一度以为{}和dict()是换汤不换药,在创建字典时执行了同样的底层代码,如今看来却并不是,有必要探索一下。

>>> import dis
>>>
>>>
>>> def f():
...     return {}
...
>>>
>>> def g():
...     return dict()
...
>>>
>>> dis.dis(f)
  2           0 BUILD_MAP                0
              2 RETURN_VALUE
>>> dis.dis(g)
  2           0 LOAD_GLOBAL              0 (dict)
              2 CALL_FUNCTION            0
              4 RETURN_VALUE

果然,不一样,而且{}更快,差不多了,突然不想写了,拜了个拜。

你可能感兴趣的:(python,学习笔记(杂项))