一、背景引入
函数在python中称为一等公民,意思就是哪里都能用,所谓一等公民在英文中是(First-Classs Object).什么是一等公民呢?对你有优待,什么样的优待呢?一等公民指身份特殊享有权利。
我们说函数是对象,而且还是可调用对象,函数名呢?有两层意思,一个是函数的真正名字就是字符串形式的,还有一层意思是函数名这个名指的是标识符,这个标识符指向一个可调用的函数对象,这个标识符就是给程序员用的。
比如:
def abc():
pass
abc是标识符,它指向一个函数对象,这个函数对象类型就是function
<function __main__.abc()>,function
函数可以作为普通变量,也可以作为函数的参数、返回值
abc的类型是function,
__main__.abc():表示在main模块中定义的abc()函数
二、高阶函数
我们来看高阶函数,什么叫做高阶函数呢? High-order-Function 高阶函数,在数学和计算机科学中,高阶函数应当是至少满足下面一个条件的函数就是高阶函数。
什么意思呢?
它的参数可以接受函数作为参数,就是函数的参数可以写函数对象
返回值必须是一个函数,满足一个就是高阶函数
高阶函数:满足下面两个条件之一就行了
1.接受一个或者多个函数作为参数
2.return 输出一个函数
我们继续来看代码来理解高阶函数的概念
def counter(base):
def inc(step=1):
base+=step
return base
return inc
问题: 1 是高阶函数吗?
是高阶函数,因为返回的是一个函数对象,return inc
2 有没有嵌套函数,有闭包吗?
有闭包这个base+=step,用到了base,因为base是形参,形参
是局部变量.内层函数用到了外层函数的局部变量就有闭包了
3 代码对吗?如果不对,怎么改?
自己认为代码这样写是没问题的,可以执行的
如果自己使用的话,这样使用
c=counter(10)
print(c())
这段代码我放到编译器执行的时候,直接报错了,为什么代码会出问题呢?
问题出来base+=step这里,base=base+step python是赋值即定义
inc函数里的base和counter函数形参base,名字虽然一样,但是由于出现
赋值语句,所以inc里面的base,不是counter的形参base了。这是一个未
进行初始化的base,未初始化变量你就使用。所以才会报错。
怎么解决呢?
就是对这个base变量声明为nonlocal 类型,这样一来就说明base+=step
这里的base 是counter函数的形参base了。
def counter(base):
def inc(step=1):
nonlocal base
base+=step
return base
return inc
这样一来代码就正确了,要记住内层函数要使用外层局部变量,用nonlocal声明
上面这个就是一个典型的高阶函数应用,这个高阶函数用了哪些知识点呢?
函数定义、形参定义、局部变量、作用域、闭包、嵌套函数、返回值类型包括传参这些概念。
继续研究上面的代码
def counter(base):
def inc(step=1):
nonlocal base
base+=step
return base
return inc
f1=counter()
f2=counter()
print(f1==f2) 这个输出什么? f1和f2一样吗?
自己认为会输出false,是不等于的,原因是进行了两次函数调用,会生成两个不
同的地址空间,所以不同。
print(f1 is f2 ) 这个又输出什么呢?true还是false
通过在编译器进行运行发现,f1==f2 f1 is f2都输出了false,为什么?
我们先来理解f1=counter() 这句代码
它的意思是:
f1指向了inc所指向的对象,inc标识符,随着counter的调用结束而消亡
inc所指向的内部函数对象,没有消亡,被f1记住了。
每次函数调用都是独立的。
每一次counter 执行时,才会生成inc标识符,也就是说内部函数inc对象
才会真的在内存中生成。
我们来描述一下函数的执行过程:
内存空间中有堆和栈这两个空间,当然栈是有深度的。第一次调用counter()
的时候。counter函数会第一次压栈,压栈的时候base=10局部变量进栈,
还有局部变量inc,但是inc是一个地址,然后函数就进行返回了把inc地址返回
到了堆中的f1。f1是全局变量。然后由于闭包技术的存在,函数执行完,
inc指向的空间没有释放,依然被保存下来了。至此counter函数第一次就调用
结束了
在看第二次,我们说函数的调用是独立的,第二次函数调用又开辟了一段新的栈
空间base=10,inc指向的空间在堆中开辟。inc保存的是地址。然后函数就进行
返回了把inc地址返回到了堆中的f2.至此counter函数第二次就调用结束了
is 比较的是内存地址 id(f1) == f(2) 是这样的比较
== 是内容进行比较,两个函数对象,不能直接进行内容比较。
如果你使用了 == 在不能比较的对象上,他会使用 is比较
所以两个print(f1==f2) print(f1 is f2) 输出false就能解释了
两次函数调用后内存地址是不一样的
三、sorted函数原理
仿照内建函数sorted,请自行实现一个sort函数 (不用使用内建函数), 能够为列表元素排序.
思考:通过练习,思考sorted函数的实现原理,map、 filter函数的实现原理
思路:
编程原则:先简单后复杂
def sort(iterable,*,key=None,reverse=False):
newlist=[]
return newlist
怎么写呢?先简单后复杂,怎么个先简单法呢?就是key参数和reverse
参数先不考虑
先解决排序问题,注意这个排序不是就地解决,是在互相作用下实现的。
新建一个列表,遍历原列表,注意新列表的值是增加的
思路:
# 从原列表iterable遍历元素,每个元素插入到newlist合适的位置
newlist中的元素是在增长,从原来没有到逐渐增加
# 生成一个升序或者降序的新列表,原列表不表
# 如果可以,先实现 reverse。reverse是用来控制升序还是降序的
# 如果可以,在实现key。key用来控制对元素的比较
三步走:
1.实现核心算法
2.实现降序升序的控制
3.实现对比较的控制
假设原列表是[1,2,3] enumerate() 函数返回下标索引
细节:
从原列表当中拿来一个个元素,把他插入到目标列表合适的位置
for x in iterable: #从原列表中遍历拿到这个元素
第一次 x=1 插入到newlist的合适位置
for i,y in enumerate(newlist):
在newlist当中从第一个元素开始,一定要保证有序。
要保证插入完之后有序。要把newlist理解
为有序区。 x就相当于是一个待排序数。对于列表来讲,
如果插入要用insert函数,我们需要知道索引才能插入
def sort(iterable,*,key=None,reverse=False):
newlist=[]
for x in iterable:
for i,y in enumerate(newlist):
#if x >y: #降序
if x < y: #大于号和小于号调换下位置是什么 升序
newlist.insert(i,x)
break
#找到合适的位置就插入后break
else:
newlist.append(x)
#如果没有break出去 ,则末尾追加
return newlist
x=[5,4,3,2,1]
print(x)
print(sort(x))
看一下完整代码
def sort(iterable,*,key=None,reverse=False):
newlist=[]
for x in iterable:
for i,y in enumerate(newlist):
#if x >y:
if x < y:
newlist.insert(i,x)
break
else:
newlist.append(x)
return newlist
我们的问题在于怎么实现这个reverse,通过这个变量来控制,
我输出的列表到底是升序还是逆序的。
直接加一个变量就行了
comp= x>y if reverse else x < y
x>y 降序 x<y升序
key的实现:
最后一个问题key的实现,我们说key指参与比较,但是不影响结果
用key的意思就是 比如:数字1和字符1进行比较要转为字符串类型
这样比较是可以的
key=str 会把元素一个个str(x) 转为字符串
key后面要传入函数参数,key=int或者str 做类型转换的内建函数
cx =key(x)
cy =key(y)
sort([1,2,3,4],key=lambda x:str(x))
这就是原理
按照整数比较:
key=lambda x int(x,16) is isinstance(x,str) else x
通过讲的例题可以知道sorted原理
由于sorted执行完不会返回原来的列表,我们会经常使用,他会返
回一个新的列表。
sort([1,2,3,4,5],key=lambda x:6-x)
这个参数 6-x怎么理解呢?
给你了列表[1,2,3,4,5],这个6-x只用于比较,什么意思?
6-1得5 6-2得4 做升序 4小所以 2去了前面 6-5得1
所以这里出来是降序,是6-x以后的值小,所以5在最前面
先做6-x在进行比较