【Python】函数与控制流(五)

内容摘要.

到了函数与控制流(五),比较重要的应用和语法基本都已经介绍完了,这部分我们主要谈一谈偏函数的概念,以及高级语言中都有的“变量作用域”在Python中的表现。最后一部分,我们用于介绍Python中的内置函数,争取完成一个较广的内置函数覆盖面。

偏函数

还记得我们说过,引入函数的目的是为了代码复用、降低编程的难度。而我们有时候在复用函数时可能会需要固定某一个或几个参数,这一功能我们可以通过在定义函数时通过设置默认参数来实现。但我们设想一种情况,你并不是这份代码的原作者,你无法修改这份代码,这时候偏函数就是一种很便捷也很易于理解的解决方案。

偏函数是用于固定函数调用时的部分或全部参数的函数。

看下面一段代码:

def add(a,b,c):
    return a+b+c
def add_s(a,b):
    return add(a,b,0)
print(add_s(5,6))

在这里插入图片描述
这段代码中的add_s()函数就是一个偏函数,没有特殊语法也没有什么难以理解的代码跳转过程。

变量的作用域.

高级语言中的变量作用域对于每一个初入编程的人来说都是头疼的事,全局变量与局部变量总是记不住有什么区别,经常是在主函数中想要获取被调用函数的局部变量。更不用说C语言中还有着static、const等等各种修饰符,matters worse.好在Python中没有那些复杂的修饰符,Python中的变量可以简单地分为局部变量和全局变量。变量的作用域指的是这个变量起作用的代码范围,和该变量被定义的位置密切相关。

局部变量是在函数内部定义的普通变量,当函数被调用执行时,局部变量活动(activate)起来;而当函数执行结束将控制权交还给主函数时,局部变量自动删除。

def func():
    x=eval(input("Enter a num.\n"))
    print(x)
func()

在这里插入图片描述
在func()函数中我们定义了一个局部变量x,接下来我们试着在主程序中访问这个x:print(x),程序的执行结果如下:
在这里插入图片描述
NameError:name ‘x’ is not defined.这一结果已经说明我们上面关于局部变量的行为的描述是正确的,当调用func()的时候x存活,而当回到主程序时,x已经死了。

def func():
    x=9
    print(x)
x=1 #x is a global variable.
func()
print(x)

在这段代码中,看起来我们在函数func()中修改了变量x的值,但实际上有一个全局变量x=1,还有一个局部变量x=9,我们在func()中只是给局部变量x赋值为9,对于全局变量x没有任何影响,它的运行结果如下:
在这里插入图片描述
可以看到9是func()中局部x的结果,而1依旧是主程序中x的值。

能够同时作用于函数内外的变量称为全局变量,它通过global关键字声明。注意这个声明是需要在def定义的函数给出的,也就是说想要在函数中使用某个全局变量需要声明global,否则编译器会认为你在函数中重新创建了一个局部变量(恰巧和全局变量同名).

def func():
    global x
    x=x+1
    print(x)
x=1 #x is a global variable.
func()

在这里插入图片描述
func()中的global x表明这里的x是全局变量x,从而下面的print语句能够正确地打印出该全局变量。如果我们删去这个global语句,再看看结果:

def func():
    x=x+1
    print(x)
x=2 #x is a global variable.
func()

UnboundLocalError: local variable 'x' referenced before assignment,这是编译器给出的报错信息,局部变量x在赋值之前被引用,说明func()函数根本不知道x是一个全局变量,也不知道有一个全局变量名字是x.那么如果全局变量和局部变量同时出现在一个函数中,并且他们是同名的,函数会使用谁呢?

def func():
    x=1
    print(x)
    global x
    print(x)
x=2 #x is a global variable.
func()

事实上这样是不合法的,SyntaxError: name 'x' is used prior to global declaration,编译器给出的错误信息告诉我们,x在全局声明之前就已经被使用了,也就是说,我们一旦声明了同名于全局变量的局部变量,函数就不会再接受关于这个全局变量的声明了。
上面我们试验的代码中,变量都是不可变对象(tuple和str的行为也是如此,可以自己尝试),那么对于诸如列表这样的可变对象,行为会不会有所不同呢?

def func():
   list[0]=99
   print(list)
list=[1,2,3]    #list is a global variable.
func()

在这里插入图片描述
前面我们曾经看到过,在函数中对于局部变量x的赋值并不会影响到同名的全局变量,但为什么这里func()中修改list[0]却影响了全局变量list呢?我们需要搞清楚一件事,只有列表在被[ ]赋值、或者list()函数创建时才会真正创建一个列表变量,即使[ ]是空列表,否则都只是对之前创建的列表的引用,通过代码来证明:

list0=[1,2,3]
list1=list0
list2=list(list0)
list3=[1,2,3]
print(id(list0))
print(id(list1))
print(id(list2))
print(id(list3))
print(list0)
print(list1)
print(list2)
print(list3)

在这里插入图片描述
我们可以看到,list0和list1拥有同样的id,它们是通过=来指向同一块内存的,而list2和list3都是自己独立地创建了一个列表。即使将列表的内容改成[ ]空列表也是一样的道理。
所以上面代码中的func()函数在修改list[0]时,需要一个真实创建的列表list,但func()函数的空间内显然没有这样一个list,所以它会去全局内存空间中寻找,并且它找到了全局变量list,从而完成了修改。而我们在操作int变量x时,x=1这样的语句却会直接创建一个变量x。如果func()函数的内部有一个创建了的list变量,那么func()函数就会修改这个list,而不会前往全局内存空间中寻找。
至此我们可以给出下列总结:

  • 不可变类型的变量无论是否与全局变量同名,仅在函数内部创建和使用,函数退出后就会销毁
  • 不可变类型的变量在使用global声明后,作为全局变量使用
  • 可变类型的变量如果在函数中没有声明同名变量,函数会前往全局内存空间中寻找后操作,如果声明了,则函数只对局部变量进行操作。

Python常用内置函数

这部分内容只是介绍函数的功能以及输入输出关系,并不会具体分析函数内部的实现原理。

1.abs().

abs()是absolute的前三个字母,顾名思义,可以返回数值类型的绝对值。我们给出示例代码:print(abs(-999)),输出结果为999

2.all()与any().

all()函数针对组合数据类型,当且仅当所有的元素都是True时,才会返回True;而any()函数和all()函数相对应,当且仅当所有的元素都为False时,才会返回False.值得一提的是,像整数0、字符串""、空列表[ ]、空字典{ }以及空集合set()都会被认定为False.

tuple=(1,[],{1,'a',False})
print(all(tuple))
print(any(tuple))

在这里插入图片描述
可见因为tuple中存在一个空列表,所以all(tuple)会返回False,而因为tuple中不是所有的元素都是被判定为False的,所以any(tuple)返回True.

3.bin()、hex()与oct().

从这三个函数的函数名,binary、hexadecimal以及octonary,就大概能够知道它们的功能了。这三个函数分别返回输入参数的2、16、8进制字符串,注意返回的是字符串类型。

a=33
print(bin(a))
print(hex(a))
print(oct(a))
print(type(bin(a)))

在这里插入图片描述
而我们如果想要获得这些字符串对应的数值,可以使用eval()函数。

4.eval().

这个函数可以说是使用非常频繁的一个函数。它的功能也极其强大,我们先给出Python官方文档中对于eval()函数的描述,再对它进行比较通俗的解释。
在这里插入图片描述
这里这一大段话所要表达的核心意思就是,eval()函数能够计算出一个表达式的值,很容易理解吧,这个功能很evaluate.

print(eval("9**2"))
a=1987
print(eval(bin(a)))
int=eval(input("Enter a num.\n"))
print(int)

在这里插入图片描述
可以看到,eval()函数的功能是相当强大的,而强大的功能就意味着它内部复杂的实现。我们可以通过input()输入一个数学表达式,而后通过eval()函数来直接求得这个表达式的值。

result=eval(input("Enter an expression.\n"))
print(result)
print(type(result))

我们给定的输入时9*3/2,下面是代码的运行结果。
在这里插入图片描述

5.int()、float()、bool()、complex()、list()、str()、dict()与set().

这八个函数看起来比较类似,但其实它们可以分为两组:前四个与后四个。前四个都是类型转换函数,他们的功能较为相似:

  • int(x,base=10)函数将给定的字符串x或一个数字x转换为整型,第二个参数base则指出了转换后的进制表示,默认为10进制;
  • float(x)函数用于将整数x或字符串x转换为浮点数;
  • bool(x)函数拥有关于将给定的参数转换为布尔逻辑值,我们前面提到过的整数0,空列表[ ]等都会转换成False;
  • complex(real[,image])函数可以将参数转换成复数形式:real+image*j,这里我们注意,如果第一个参数直接由字符串给出,则不再需要第二个参数。

其实还有一个函数long()用于将数字或字符串转换为长整型,由于使用太少,并没有在标题列出。我们用一段代码来展示这几个类型转换函数:

print(int(3.6))
print(int(3.2))
print(int("123"))
print(float("1.23"))
print(float(1))
print(bool([]))
print(bool("Esperanto."))
print(complex(2,3))
print(complex("1+1j"))

在这里插入图片描述
对于这段代码,我们指出一些细节:

  • int()函数是直接截短操作,没有四舍五入,四舍五入是另一个函数round();
  • complex()函数中的一个细节在于,在数学上我们可以写1+j和1-j,但在Python中这样的形式很容易误认为j是一个变量,所以我们不能省略虚部为1时的数字1.

后面四个函数就不需要过多了介绍了吧,它们是用于生成组合数据类型的构造器函数,我们在这里只进行一些细节的提醒:

  • list(seq) 方法用于将元组或字符串转换为列表。注意,元组与列表是非常类似的,区别在于元组的元素值不能修改,元组是放在括号中,列表是放于方括号中;
  • str() 函数将对象转化为适于人阅读的形式,即字符串。

6.dir()、globals()与locals().

  • dir() 函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度地收集参数信息。
  • globals() 函数会以字典类型返回当前位置的全部全局变量。
  • locals() 函数会以字典类型返回当前位置的全部局部变量。

7.map().

map()函数也是被使用的很多的一个内置函数,它的具体形式是map(function, iterable, ...),会根据提供的函数对指定序列做映射。第一个参数function会以参数序列中的每一个元素作为实际参数来调用调用function函数,返回包含每次function函数返回值的可迭代对象,我们后续可以将其构造成list列表处理。

def square(x) :            # 计算平方数
     return x ** 2
 
list1=list(map(square, [1,2,3,4,5]))   # 计算列表各个元素的平方

list2=list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))  # 使用 lambda 匿名函数

# 提供了两个列表,对相同位置的列表数据进行相加
list3=list(map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10]))

print(list1)
print(list2)
print(list3)

在这里插入图片描述
因为在Python3.8.3中,map()返回值是一个迭代器类型,所以如果想显式地看到当中的内容,需要用list()将其转换为列表再查看。

8.isinstance()与issubclass().

isinstance() 函数来判断一个对象是否是一个已知的类型,类似 type()。isinstance() 与 type() 区别:

  • type() 不会认为子类是一种父类类型,不考虑继承关系。
  • isinstance() 会认为子类是一种父类类型,考虑继承关系。

如果要判断两个类型是否相同推荐使用 isinstance()。
issubclass() 方法用于判断参数 class 是否是类型参数 classinfo 的子类。这涉及到面向对象的部分,我们只做简单的展示。

class A:
    pass
class B(A):
    pass
print(issubclass(B,A))

a = 2
print(isinstance (a,int))
print(isinstance (a,str))
print(isinstance (a,(str,int,list)))    #是元组中的一个返回True

b=False
print(isinstance(b,bool))
print(isinstance(b,int))
print(type(b)==int)

在这里插入图片描述
通过这段代码我们发现一下几点:

  • isinstance()的第二个参数可以是一个元组,只要第一个参数的类型在这个元组之中,就会返回True;
  • bool实际上是int的一个子类,因为实际上False与0,True与1的值都是相等的,但它们的id不同,说明是不同的对象。
print(False==0)
print(True==1)
print(id(False)==id(0))
print(id(True)==id(1))

在这里插入图片描述

9.max()与min().

这两个函数的功能很明显了,可以给出输入参数中的最大以及最小值,参数可以是一个序列。

tuple=tuple(range(20))
list=list(tuple)
print(max(list))
print(min(tuple))

在这里插入图片描述

a=[(1,2),(2,3),(3,4)] 
print(max(a))                                   
a=[('a',1),('A',1)]   
print(max(a))
a={1:2,2:2,3:1,4:'aa'}  
print(max(a))

在这里插入图片描述
如果序列中的元素是元组或者max()函数的对象是字典类型呢,我们给出以下结论:

  • 按照元素里面元组的第一个元素的排列顺序,输出最大值(如果第一个元素相同,则比较第二个元素,输出最大值),是按ascii码进行排序的;
  • max()的对象是字典的话,会输出最大的键值。

10.zip().

zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象,这样做的好处是节约了不少的内存。我们可以使用 list()转换来输出列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用号操作符,可以将元组解压为列表。
下面是Python3.8.3官方文档给出的描述:在这里插入图片描述
以及zip()与
运算结合达到的解压效果:
在这里插入图片描述

a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]

zipped1=zip(a,b)
print(zipped1)
print(*zipped1)
print(type(zipped1))
print(*(zip(a,c)))  #以最短的列表结束为结束

zipped2=zip(*zip(a,b))
print(*zipped2)

在这里插入图片描述
我们发现,zipped1本身只是一个类似指针的变量,而*zipped才是实际的zip()之后的结果,这也就很容易解释为什么zip(*zip(a,b))能够实现解压缩的功能了。*zip(a,b)时zip()实际的结果,也就是(1, 4) (2, 5) (3, 6),所以zip(*zip(a,b))实际上相当于zip((1, 4),(2, 5),(3, 6)).

11.filter().

filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。

def is_odd(n):
    return n % 2 == 1
 
tmplist = filter(is_odd, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
newlist = list(tmplist)
print(newlist)

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值