前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python 易混淆点

Python 易混淆点

原创
作者头像
花落花相惜
发布2021-12-06 13:12:51
2.1K0
发布2021-12-06 13:12:51
举报
文章被收录于专栏:花落的技术专栏

一、*args**kwargs

对于刚开始学习Python的读者,一定在编写代码的时候,遇到过这个问题。他们到底是怎么回事呢?且听我一一道来。

首先你需要知道的是:其实我们并不是一定要写成*args

**kwargs,只有前面的*才是必须有的。因此,我们也可以写成*var**var而写成*args

**kwargs只是一个通俗的命名约定而已。接下来给大家首先介绍*args

1、*args的用法

*args**kwargs主要用于函数定义。 你可以将不定数量的参数传递给一个函数。在这里的不定的意思是:预先并不知道,

函数使用者会传递多少个参数给你, 所以在这个场景 下使用这两个关键字。 *args

是用来发送一个非键值对的可变数量的参数列表给一个函数。这里有个例子帮我们理解这个概念:

代码语言:txt
复制
def test_var_args(f_arg, *argv):
代码语言:txt
复制
    print("first normal arg:", f_arg)
代码语言:txt
复制
    for arg in argv:
代码语言:txt
复制
        print("another arg through *argv:", arg)
代码语言:txt
复制
test_var_args('yasoob', 'python', 'eggs', 'test')

具体的执行结果如下:

通过这个案例,我希望这个小的知识点已经讲清楚了。接下来接着介绍**kwargs

2、**kwargs的用法

**kwargs允许你将不定长度的键值对, 作为参数传递给一个函数。 如果你想要在一个函数里处理带名字的参数,

我们应该使用**kwargs。我们同样通过一个案例来进行说明:

代码语言:txt
复制
def greet_me(**kwargs):
代码语言:txt
复制
    for key, value in kwargs.items():
代码语言:txt
复制
        print("{0} == {1}".format(key, value))
代码语言:txt
复制
greet_me(name="yasoob")

现在可以看出我们怎样在一个函数里, 处理了一个键值对参数了,具体执行结果如下:

这就是**kwargs的基础, 而且你可以看出它有多么管用。 接下来让我们谈谈,我们怎样使用*args 和

**kwargs来调用一个参数为列表或者字典的函数。

3、使用*args**kwargs 来调用函数

那现在我们将看到怎样使用 args和 *kwargs 来调用一个函数。 假设,你有这样一个小函数:

代码语言:txt
复制
def test_args_kwargs(arg1, arg2, arg3):
代码语言:txt
复制
    print("arg1:", arg1)
代码语言:txt
复制
    print("arg2:", arg2)
代码语言:txt
复制
    print("arg3:", arg3)
代码语言:txt
复制
args = ("two", 3, 5)
代码语言:txt
复制
test_args_kwargs(*args)

具体执行结果如下:

我们现在使用 **kwargs:将其进行实现:

代码语言:txt
复制
def test_args_kwargs(arg1, arg2, arg3):
代码语言:txt
复制
    print("arg1:", arg1)
代码语言:txt
复制
    print("arg2:", arg2)
代码语言:txt
复制
    print("arg3:", arg3)
代码语言:txt
复制
kwargs = {"arg3": 3, "arg2": "two", "arg1": 5}
代码语言:txt
复制
test_args_kwargs(**kwargs)

具体执行的结果如下:

那么如果你想在函数里同时使用所有这三种参数, 顺序是这样的:

代码语言:txt
复制
some_func(fargs, *args, **kwargs)

4、使用场景

这个还是需要特定的需求具体而定;最常见的用例是在写函数装饰器的时候。此外它也可以用来做猴子补丁。猴子补丁的意思是在程序运行时(runtime)修改某些代码。

打个比方,你有一个类,里面有个叫get_info的函数会调用一个API并返回相应的数据。如果我们想测试它,可以把API调用替换成一些测试数据。

二、调试 Debugging

利用好调试,能大大提高你捕捉代码Bug的。大部分新人忽略了Python debugger(pdb)的重要性。

在这个章节我只会告诉你一些重要的命令,你可以从官方文档中学习到更多。

1、从命令行运行

我们可以在命令行使用Python debugger运行一个脚本, 举个例子:

代码语言:txt
复制
python -m pdb my_script.py

这会触发debugger在脚本第一行指令处停止执行。这在脚本很短时会很有帮助。你可以通过(Pdb)模式接着查看变量信息,并且逐行调试。

2、从脚本内部运行

同时,你也可以在脚本内部设置断点,这样就可以在某些特定点查看变量信息和各种执行时信息了。这里将使用pdb.set_trace()方法来实现。举个例子:

代码语言:txt
复制
import pdb
代码语言:txt
复制
def make_bread():
代码语言:txt
复制
    pdb.set_trace()
代码语言:txt
复制
    return "I don't have time"
代码语言:txt
复制
print(make_bread())

试下保存上面的脚本后运行之。你会在运行时马上进入debugger模式。具体效果如下:

现在是时候了解下debugger模式下的一些命令了。

以下是一些常用的命令:

c: 继续执行 w: 显示当前正在执行的代码行的上下文信息 a: 打印当前函数的参数列表 s: 执行当前代码行,并停在第一个能停的地方(相当于单步进入) n: 继续执行到当前函数的下一行,或者当前行直接返回(单步跳过)

单步跳过(next)和单步进入(step)的区别在于: **单步进入会进入当前行调用的函数内部并停在里面,

而单步跳过会(几乎)全速执行完当前行调用的函数,并停在当前函数的下一行。**

pdb真的是一个很方便的功能,上面仅列举少量用法,更多的命令强烈推荐你去看官方文档。

三、生成器 Generators

首先我们要理解迭代器(iterators)。根据维基百科,迭代器是一个让程序员可以遍历一个容器(特别是列表)的对象。然而,一个迭代器在遍历并读取一个容器的数据元素时,并不会执行一个迭代。你可能有点晕了,那我们来个慢动作。换句话说这里有三个部分:

可迭代对象(Iterable) 迭代器(Iterator) 迭代(Iteration)

上面这些部分互相联系。我们会先各个击破来讨论他们,然后再讨论生成器(generators).。

可迭代对象(Iterable)

Python中任意的对象,只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法(这些双下划线方法会在其他章节中全面解释),那么它就是一个可迭代对象。简单说,可迭代对象就是能提供迭代器的任意对象。那迭代器又是什么呢?

  • == 迭代器(Iterator) ==undefined任意对象,只要定义了__next__方法,它就是一个迭代器。就这么简单。现在我们来理解迭代(iteration)。undefined用简单的话讲,它就是从某个地方(比如一个列表)取出一个元素的过程。当我们使用一个循环来遍历某个东西时,这个过程本身就叫迭代。现在既然我们有了这些术语的基本理解,那我们开始理解生成器吧。
  • == 生成器 ==undefined生成器也是一种迭代器,但是你只能对其迭代一次。这是因为它们并没有把所有的值存在内存中,而是在运行时生成值。你通过遍历来使用它们,要么用一个“for”循环,要么将它们传递给任意可以进行迭代的函数和结构。大多数时候生成器是以函数来实现的。然而,它们并不返回一个值,而是yield(暂且译作“生出”)一个值。这里有个生成器函数的简单例子:
代码语言:txt
复制
def generator_function():
代码语言:txt
复制
    for i in range(10):
代码语言:txt
复制
        yield i
代码语言:txt
复制
for item in generator_function():
代码语言:txt
复制
    print(item)

具体执行结果如下:

其实,这个案例并不是非常实用。生成器最佳应用场景是:==你不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。==

这里需要注意的是:许多Python 2里的标准库函数都会返回列表,而Python

3都修改成了返回生成器,因为生成器占用更少的资源。下面是一个计算斐波那契数列的生成器:

代码语言:txt
复制
def fibon(n):
代码语言:txt
复制
    a = b = 1
代码语言:txt
复制
    for i in range(n):
代码语言:txt
复制
        yield a
代码语言:txt
复制
    a, b = b, a + b
代码语言:txt
复制
for x in fibon(1000000):
代码语言:txt
复制
    print(x)

具体执行结果如下:

用这种方式,我们可以不用担心它会使用大量资源。然而,之前如果我们这样来实现的话:

代码语言:txt
复制
def fibon(n):
代码语言:txt
复制
    a = b = 1
代码语言:txt
复制
    result = []
代码语言:txt
复制
    for i in range(n):
代码语言:txt
复制
        result.append(a)
代码语言:txt
复制
    a, b = b, a + b
代码语言:txt
复制
    return result

这也许会在计算很大的输入参数时,用尽所有的资源。我们已经讨论过生成器使用一次迭代,但我们并没有测试过。在测试前你需要再知道一个Python内置函数:next()。它允许我们获取一个序列的下一个元素。那我们来验证下我们的理解:

代码语言:txt
复制
def generator_function():
代码语言:txt
复制
    for i in range(3):
代码语言:txt
复制
        yield i
代码语言:txt
复制
gen = generator_function()
代码语言:txt
复制
print(next(gen))
代码语言:txt
复制
print(next(gen))
代码语言:txt
复制
print(next(gen))

具体执行结果如下:

我们可以看到,在yield掉所有的值后,next()触发了一个StopIteration的异常。基本上这个异常告诉我们,所有的值都已经被yield完了。你也许会奇怪,为什么我们在使用for循环时没有这个异常呢?啊哈,答案很简单。for循环会自动捕捉到这个异常并停止调用next()

四、Map,Filter 和 Reduce

Map,Filter 和 Reduce 三个函数能为函数式编程提供便利。我们会通过实例一个一个讨论并理解它们。

1、Map

Map会将一个函数映射到一个输入列表的所有元素上。这是它的规范:

代码语言:txt
复制
map(function_to_apply, list_of_inputs)

大多数时候,我们要把列表中所有元素一个个地传递给一个函数,并收集输出。比方说:

代码语言:txt
复制
items = [1, 2, 3, 4, 5]
代码语言:txt
复制
squared = []
代码语言:txt
复制
for i in items:
代码语言:txt
复制
    squared.append(i**2)
代码语言:txt
复制
print(squared)

具体执行结果如下:

Map可以让我们用一种简单而漂亮得多的方式来实现。就是这样:

代码语言:txt
复制
items = [1, 2, 3, 4, 5]
代码语言:txt
复制
squared = list(map(lambda x: x**2, items))
代码语言:txt
复制
print(squared)

具体执行结果如下:

大多数时候,我们使用匿名函数(lambdas)来配合map, 所以我在上面也是这么做的。 不仅用于一列表的输入, 我们甚至可以用于一列表的函数!

代码语言:txt
复制
def multiply(x):
代码语言:txt
复制
    return (x*x)
代码语言:txt
复制
def add(x):
代码语言:txt
复制
    return (x+x)
代码语言:txt
复制
funcs = [multiply, add]
代码语言:txt
复制
for i in range(5):
代码语言:txt
复制
    value = map(lambda x: x(i), funcs)
代码语言:txt
复制
    print(list(value))

具体执行结果如下:

2、Filter

顾名思义,filter过滤列表中的元素,并且返回一个由所有符合要求的元素所构成的列表,符合要求即函数映射到该元素时返回值为True. 这里是一个简短的例子:

代码语言:txt
复制
number_list = range(-5, 5)
代码语言:txt
复制
less_than_zero = filter(lambda x: x < 0, number_list)
代码语言:txt
复制
print(list(less_than_zero))

具体执行结果如下:

这个filter类似于一个for循环,但它是一个内置函数,并且更快。注意:如果map和filter对你来说看起来并不优雅的话,那么你可以看看其他:列表/字典/元组推导式。

3、Reduce

当需要对一个列表进行一些计算并返回结果时,Reduce 是个非常有用的函数。举个例子,当你需要计算一个整数列表的乘积时。通常在 python

中你可能会使用基本的 for 循环来完成这个任务;现在我们来试试 reduce:

代码语言:txt
复制
from functools import reduce
代码语言:txt
复制
product = reduce( (lambda x, y: x * y), [1, 2, 3, 4] )
代码语言:txt
复制
print(product)

具体执行结果如下:

五、set 数据结构

set(集合)是一个非常有用的数据结构。它与列表(list)的行为类似,区别在于set不能包含重复的值。这在很多情况下非常有用。例如你可能想检查列表中是否包含重复的元素,你有两个选择,第一个需要使用for循环,就像这样:

代码语言:txt
复制
some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']
代码语言:txt
复制
duplicates = []
代码语言:txt
复制
for value in some_list:
代码语言:txt
复制
    if some_list.count(value) > 1:
代码语言:txt
复制
        if value not in duplicates:
代码语言:txt
复制
            duplicates.append(value)
代码语言:txt
复制
print(duplicates)

具体执行结果如下:

但还有一种更简单更优雅的解决方案,那就是使用集合(sets),你直接这样做:

代码语言:txt
复制
some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']
代码语言:txt
复制
duplicates = set([x for x in some_list if some_list.count(x) > 1])
代码语言:txt
复制
print(duplicates)

具体执行结果如下:

集合还有一些其它方法,下面我们介绍其中一部分。

1、交集

你可以对比两个集合的交集(两个集合中都有的数据),如下:

代码语言:txt
复制
valid = set(['yellow', 'red', 'blue', 'green', 'black'])
代码语言:txt
复制
input_set = set(['red', 'brown'])
代码语言:txt
复制
print(input_set.intersection(valid))

具体执行结果如下:

2、差集

你可以用差集(difference)找出无效的数据,相当于用一个集合减去另一个集合的数据,例如:

代码语言:txt
复制
valid = set(['yellow', 'red', 'blue', 'green', 'black'])
代码语言:txt
复制
input_set = set(['red', 'brown'])
代码语言:txt
复制
print(input_set.difference(valid))

具体执行结果如下:

你也可以用符号来创建集合,如:

代码语言:txt
复制
a_set = {'red', 'blue', 'green'}
代码语言:txt
复制
print(type(a_set))

具体执行结果如下:

集合还有一些其它方法,我会建议访问官方文档并做个快速阅读。

六、三元运算符

三元运算符通常在Python里被称为条件表达式,这些表达式基于真(true)/假(not)的条件判断,在Python

2.4以上才有了三元操作。下面是一个伪代码和例子:

代码语言:txt
复制
condition_is_true if condition else condition_is_false
代码语言:txt
复制
is_fat = True
代码语言:txt
复制
state = "fat" if is_fat else "not fat"

它允许用简单的一行快速判断,而不是使用复杂的多行if语句。

这在大多数时候非常有用,而且可以使代码简单可维护。另一个晦涩一点的用法比较少见,它使用了元组,请继续看:

代码语言:txt
复制
(if_test_is_false, if_test_is_true)[test]
代码语言:txt
复制
fat = True
代码语言:txt
复制
fitness = ("skinny", "fat")[fat]
代码语言:txt
复制
print("Ali is ", fitness)

具体执行结果如下:

这之所以能正常工作,是因为在Python中,True等于1,而False等于0,这就相当于在元组中使用0和1来选取数据。上面的例子没有被广泛使用,而且Python玩家一般不喜欢那样,因为没有Python味儿(Pythonic)。这样的用法很容易把真正的数据与true/false弄混。另外一个不使用元组条件表达式的缘故是因为在元组中会把两个条件都执行,而

if-else 的条件表达式不会这样。例如:

代码语言:txt
复制
condition = True
代码语言:txt
复制
print(2 if condition else 1 / 0)
代码语言:txt
复制
print((1 / 0, 2)[condition])

具体执行结果如下:

这是因为在元组中是先建数据,然后用True(1)/False(0)来索引到数据。 而if-else条件表达式遵循普通的if-else逻辑树,

因此,如果逻辑中的条件异常,或者是重计算型(计算较久)的情况下,最好尽量避免使用元组条件表达式。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、*args 和 **kwargs
    • 1、*args的用法
      • 2、**kwargs的用法
        • 3、使用*args和**kwargs 来调用函数
          • 4、使用场景
          • 二、调试 Debugging
            • 1、从命令行运行
              • 2、从脚本内部运行
              • 三、生成器 Generators
                • 可迭代对象(Iterable)
                • 四、Map,Filter 和 Reduce
                  • 1、Map
                    • 2、Filter
                      • 3、Reduce
                      • 五、set 数据结构
                        • 1、交集
                          • 2、差集
                          • 六、三元运算符
                          相关产品与服务
                          容器服务
                          腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档