此部分作为对函数知识的补充,逻辑性比较差,行业比较乱。
变量作用域
变量的作用域指的是变量的作何用范围。 每个变量有相应的作用范围限制,超出 作用范围的变量是不允许的。
变量按照作用域可以分类为:
- 全局(global): 定义在函数外部,整个程序内都可以使用
- 局部(local):在函数代码内部定义,超出某一段代码范围后就不能使用
变量的作用范围可以粗略的分为以下几种:
- 全局变量:在整个全局范围都有效
- 全局变量在局部可以使用(即函数内部可以方位函数外部定义的变量)
- 局部变量在局部范围可以使用
- 局部变量在全局范围无法使用
LEGB原则
Python中可以把变量的作用域概括成LEGB:
- L(Local: 局部作用域
- E(Enclosing function locale):外部嵌套函数作用域
- G(Global module):函数定义所在模块作用域
- B(Buildin):
Python内置模块的作用域
参见以下案例:
# 认为a1是全局的
a1 = 100
def fun():
print(a1)
print("I am in fun")
# a2的作用范围是fun
a2 = 99
print(a2)
print(a1)
fun()
print(a2)
我们在函数外部定义了变量a1, 函数内部对这个变量进行了打印操作,这样是可以的,函数内部可以直接使用 函数外部的变量, 同时函数内定义了变量a2, 定义后再函数内使用这个变量没有问题,但我们在外部使用这个变量 就会报错,参加一下运行结果。
函数内部使用函数外部定义的变量,在上面的情况只限于对变量进行读操作,不能有赋值等修改操作
代码运行结果如下:
100
100
I am in fun
99
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-4-36e1efbb7d66> in <module>()
12 print(a1)
13 fun()
---> 14 print(a2)
NameError: name 'a2' is not defined
提升局部变量为全局变量
如果我们试图在函数内修改函数外部定义的变量,此时需要使用关键字global还用来告诉程序 要对函数外部定义的变量进行修改,即进行全局声明, 此时也可以把global关键字理解成 是在函数内定义了一个作用域为全局的外部变量。
使用格式如下:
def 函数名()
#提升局部变量为全局变量
global 局部变量
其他操作...
参见以下案例:
def fun():
global b1
b1 = 100
print(b1)
print("I am in fun")
# a2的作用范围是fun
b2 = 99
print(b2)
#print(b2) #变量b2的作用域是局部,此处引用报错
fun()
print(b1)
代码运行结果如下, 需要注意的是变量b1是在函数内部定义的, 只有当函数运行之后,即在函数 被调用后系统才会还有这个变量,在函数调用前使用这个变量会报错:
100
I am in fun
99
100
nonlocal-提升局部变量为非局部变量
nonlocal的作用是声明当前变量不是当前函数内部的变量,他有可能是当前函数的外部变量(不是全局变量)。
语法格式为:
def 外部函数()
局部变量
def 内部函数():
nonlocal 变量名 #声明当前变量不是内部函数中的变量
其他代码...
return 代码
如果在内部函数中只是仅仅读外部变量,可以不在此变量前加
nonlocal如果在内部函数中尝试进行修改外部变量,且外部变量为不可变类型,则需要在变量前加nonlocal,如果变量为可变类型,则不需要添加
函数的几个特殊用法
函数的嵌套
函数嵌套是指一个函数里用def关键字来创建其它函数的情况。
常见的格式如下:
def fun1():
print("fun1被调用)
def fun2():
print("fun2被调用")
fun2() #调用上面定义的函数
print("fun1调用结束")
函数名字作为变量赋值
函数名可以看做普通变量,他在创建时绑定一个函数,此时可以对另外变量用函数名称赋值,此时被赋值的变量 可以看做是这个函数的第二个名字。
参加案例如下:
def fn():
print("hello world")
f1 = fn
f1() # 等同于fn()
函数作为函数的参数
函数可以当做变量赋值,也可以当做参数传入另外函数内部使用。
参考下面代码:
def myadd(x, y):
return x + y
def mymul(x, y):
return x * y
def getfun(fx):
if "add" == fx:
return myadd
elif "mul" == fx:
return mymul
return None
a = int(input("请输入第一个数"))
b = int(input("请输入第二个数"))
op = int(input("请输入操作方法:(mul/add):"))
f = getfun(op)
print(f(a,b)) #注意函数的调用方式
递归函数
函数写完以后被调用才会运行。
函数代码里可以继续调用其他函数,但如果函数调用自己,则此类函数叫做递归函数,即函数直接或间接调用 自己。
递归函数的优点为简洁和理解容易, 还有同学会觉得很难理解,但你是不理解递归的定义导致的,在有些特定的场合 如果不用递归的话,直接写出来的程序是很复杂的。
递归函数的缺点是系统对递归深度有限制,而且递归函数消耗资源大。
Python对递归深度有限制,即函数调用自己的次数有限制, 超过限制报错
在编写递归函数时,一定要注意递归函数的结束条件,递归函数不能无限制递归调用下去,否则会好近计算机所有资源。
下面案例是一个失败的递归调用案例,因为没有对结束条件进行规定导致递归函数一直运行:
# 递归调用深度限制代码
x = 0
def fun():
global x
x += 1
print(x)
# 函数自己调用自己
fun()
# 调用函数
fun()
函数运行结果如下, 注意最后的报错,是递归错误:
1
2
3
4
5
6
7
8
140
---------------------------------------------------------------------------
RecursionError Traceback (most recent call last)
<ipython-input-15-342ffb5916b5> in <module>()
11
12 # 调用函数
---> 13 fun()
<ipython-input-15-342ffb5916b5> in fun()
8 print(x)
9 # 函数自己调用自己
---> 10 fun()
11
12 # 调用函数
... last 1 frames repeated, from the frame below ...
<ipython-input-15-342ffb5916b5> in fun()
8 print(x)
9 # 函数自己调用自己
---> 10 fun()
11
12 # 调用函数
RecursionError: maximum recursion depth exceeded in comparison
斐波那契数列
递归函数一个经典的案例是斐波那契数列,参考一下代码:
# 斐波那契额数列
# 一列数字,第一个值是1, 第二个也是1, 从第三个开始,每一个数字的值等于前两个数字出现的值的和
# 数学公式为: f(1) = 1, f(2) = 1, f(n) = f(n-1) + f(n-2)
# 例如: 1,1,2,3,5,8,13.。。。。。。。。
# 下面求斐波那契数列函数有一定问题,比如n一开始就是负数,如何修正
# n表示求第n个数子的斐波那契数列的值
def fib(n):
if n == 1:
return 1
if n == 2:
return 1
# 思考:为什么后面return能够正确执行,而不用else语句
return fib(n-1) + fib(n-2)
print(fib(3))
print(fib(10))
代码运行结果如下:
2
55
系统内置函数
Python有一些内置的函数,都封装在了__builtins__模块中,可以通过 print((dir(__builtins__))) 来打印出所有内置函数和功能。
下面列举几个常用的内置函数:
eval()函数
在Python中,eval函数可以把一个字符串当成一个表达式来执行, 返回表达式执行后的结果作为函数执行 的结果。
函数的定义接口为eval(string_code, globals=None, locals=None), 其中:
string_code: 被当做命令执行的字符串globals: 执行代码时候的全局变量,作为执行环境参数locals: 执行代码时候的局部变量,作为执行环境参数
代码案例参考如下:
x = 100
y = 200
# 执行x+y
# z = x + y
z1 = x + y
z2 = eval("x+y")
print(z1)
print(z2)
运行结果为:
300
300
exec()函数
跟eval功能类似, 但是不返回结果.
函数的定义接口为exec(string_code, globals=None, locals=None)
代码案例参考如下:
# exec示例
x = 100
y = 200
# 执行x+y
# z = x + y
z1 = x + y
# 1, 注意字符串中引号的写法
# 2. 比对exec执行结果和代码执行结果
z2 = exec("print('x+y:', x+y)")
print(z1)
print(z2)
运行结果为:
x+y: 300
300
None
repr()函数
- repr函数用来取得对象的规范字符串表示。
- 反引号(也称转换符)可以完成相同的功能。
- 注意,在大多数时候有eval(repr(object))==object。
- 可以通过定义类__repr__方法来控制你的对象在被repr函数调用的时候返回内容
compile()函数
compile语句是从type类型中将str里面的语句创建成代码对象,以供以后执行。
- type:
- eval: 配合eval使用
- single: 配合单一语句的exec使用
- exec: 配合多语句的exec使用
- file 是代码存放的地方,通常为空字符串
参看以下代码:
>>> eval_code = compile( '1+2', '', 'eval')
>>> eval_code
<code object <module> at 0142ABF0, file "", line 1>
>>> eval(eval_code)
3
>>> single_code = compile( 'print "www.baoshu.red"', '', 'single' )
>>> single_code
<code object <module> at 01C68848, file "", line 1>
>>> exec(single_code)
www.baoshu.red
>>> exec_code = compile( """for i in range(5):
... print "iter time: %d" % i""", '', 'exec' )
>>> exec_code
<code object <module> at 01C68968, file "", line 1>
>>> exec(exec_code)
iter time: 0
iter time: 1
iter time: 2
iter time: 3
iter time: 4
globals, locals函数
在代码中我们可以通过globals,locals两个函数显示出我们所有的局部变量和全局变量。
# globals 和 locals
# globals 和 locals 叫做内建函数
a = 1
b = 2
def fun(c,d):
e = 111
print("Locals={0}".format(locals()))
print("Globals={0}".format(globals()))
fun(100, 200)
代码运行结果如下:
Locals={'e': 111, 'd': 200, 'c': 100}
Globals={'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '\ndef fun():\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\nprint(b2)\nfun()', '\ndef fun():\n global b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n e = 111\n print("Locals={0}".format(locals()))\n print("Globals={0}".format(globals()))\n \nfun(100, 200)'], '_oh': {}, '_dh': ['/home/tlxy/cookbook_and_code'], 'In': ['', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\n#print(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '\ndef fun():\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\nprint(b2)\nfun()', '\ndef fun():\n global b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n e = 111\n print("Locals={0}".format(locals()))\n print("Globals={0}".format(globals()))\n \nfun(100, 200)'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f891e18d198>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f8914136fd0>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7f8914136fd0>, '_': '', '__': '', '___': '', '_i': '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_ii': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_iii': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i1': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\n#print(a2)', 'a1': 100, 'fun': <function fun at 0x7f88fbfb5268>, '_i2': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\n#print(a2)', '_i3': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '_i4': '# 认为a1是全局的\na1 = 100\n\ndef fun():\n print(a1)\n print("I am in fun")\n # a2的作用范围是fun\n a2 = 99\n print(a2)\n \n \nprint(a1)\nfun()\nprint(a2)', '_i5': '\ndef fun():\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\nprint(b2)\nfun()', '_i6': '\ndef fun():\n global b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i7': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i8': '\ndef fun():\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i9': '\ndef fun():\n b1 = 100\n global b1\n b1 = 100\n print(b1)\n print("I am in fun")\n # a2的作用范围是fun\n b2 = 99\n print(b2)\n \n \nprint(b1)\n#print(b2)\nfun()', '_i10': '# globals 和 locals\na = 1\nb = 2\n\ndef fun(c,d):\n e = 111\n print("Locals={0}".format(locals()))\n print("Globals={0}".format(globals()))\n \nfun(100, 200)', 'a': 1, 'b': 2}
函数式编程(FunctionalProgramming)和高阶函数
函数式编程是基于lambda演算的一种编程方式, 在函函数式编程中程序只有函数,其中函数可以作为参数,同样可以作为返回值。
纯函数式编程语言有LISP,Haskell等, 但Python中要讲的函数式编程只是借鉴函数式编程的一些特点,可以理解成一半函数式一半Python。
lambda表达式
函数的作用是最大程度复用代码,但是也存在一些问题,比如如果函数的代码很少或很短:
- 会造成程序很细碎,反而显得比较啰嗦。
- 如果函数被调用次数少,则会造成资源浪费,比较每次调用函数需要一定的系统资源开销
- 对于阅读者来说,频繁的跳转会造成阅读流程的被迫中断
对于一些比较短小的代码,我们可以尝试使用匿名函数来替代一个比较常规的函数的定义流程,匿名函数也叫lambda表达式:
- 是一个表达式,代替函数体相对简单的函数来定义和执行
- 不是一个代码块,仅仅是一个表达式
- 可以有参数,甚至客户已有多个参数,但需要用逗号隔开不同的参数
下面的案例是一段很小的代码,也是一个很简短的函数:
# “小”函数举例
def printA():
print("AAAAAAAA")
printA() #调用了函数
下面我们聊下lambda 表达是的具体用法:
- 以lambda开头
- 紧跟一定的参数(如果有的话)
- 参数后用冒号和表达式主题隔开
- 只是一个表达式,所以,没有
return
具体请参照下面的案例来理解:
# 计算一个数字的100倍数
# 因为就是一个表达式,所以没有return
stm = lambda x: 100 * x
# 使用上跟函数调用一模一样
stm(89)
或者下面也是也是一个多参数的案例:
stm2 = lambda x,y,z: x+ y*10 + z*100
stm2(4,5,6) #执行表达式
高阶函数
函数的参数一般是一个值,但函数的名称同样可以作为一个值传递,把函数作为参数使用的函数,叫高阶函数。
下面的案例中funA是被定义的一个函数,然后把这个函数名字作为值赋值给funB,此时funB,funA都指向了一个共同的函数体,在 调用的时候可以用任何一个名字,结果相同。
# 变量可以赋值
a = 100
b = a
# 函数名称就是一个变量
def funA():
print("In funA")
funB = funA
funB()
以上代码得出的结论:
- 函数名称可以作为值,也可以是变量
funB和funA只是名称不一样而已
既然函数名称是变量,则应该可以被当做参数传入另一个函数, 这就是所谓高阶函数的基础。
下面案例展示函数的普通调用方式,即在函数内部调用别的函数:
# 高阶函数举例
# funA是普通函数,返回一个传入数字的100倍数字
def funA(n):
return n * 100
# 再写一个函数,把传入参数乘以300倍,
def funB(n ):
# 最终是想返回300n
return funA(n) * 3
print(funB(9))
上面代码是可以的,但如果想办法降低这两个函数的耦合度,就可以用高阶函数,即把函数funA作为参数传入函数funB:
# 写一个高阶函数
def funC(n, f):
# 假定函数是把n扩大100被
return f(n) * 3
print( funC(9, funA) )
比较funC和funB, 显然funC的写法要优于funB, funC的功能如果后期面临维护或者修改,要比funB灵活许多, 例如我们需要替换funA的功能部分,根本不需要修改代码funB,只需要传入不同的函数funD即可:
def funD(n):
return n*10
# 需求变更,需要把n放大三十倍,此时funB则无法实现
print(funC(7, funD))
map
map原意就是映射,即把集合或者列表的元素,每一个元素都按照一定规则进行操作,结构生成一个新的列表或者集合。
在Python中map函数是系统提供的具有映射功能的函数,返回值是一个迭代对象。
通过查询help(map)的帮助文档可以清晰的显示他的用法:
Help on class map in module builtins:
class map(object)
| map(func, *iterables) --> map object
|
| Make an iterator that computes the function using arguments from
| each of the iterables. Stops when the shortest iterable is exhausted.
map的具体使用请参阅下面案例:
如果有一个列表,想对列表里的每一个元素乘以10并得到换一个新的列表,我们可以通过以前的知识遍历列表来完成这个功能, 例如下面代码:
l1 = [i for i in range(10)]
print(l1)
l2 = []
for i in l1:
l2.append(i * 10)
但是如果我们用map函数实现就简单的多了,先编写一个函数,这个函数是map用来映射后进行操作的函数:
# 利用map实现
def mulTen(n):
return n*10
然我们调用map, 可以理解成用l1的每个元素逐个调用mulTen函数,然后放入结果中生成一个新的列表,虽然这种理解并不准确, 准确的说法是生成了换一个迭代器,对数据的映射操作也不是一次性完成全部,二是逐次完成:
l3 = map(mulTen, l1 )
# map类型是一个可迭代的结构,所以可以使用for遍历
for i in l3:
print(i)
reduce
reduce原意是归并或者缩减,在这里代表把一个可迭代的对象归并成一个结果。
reduce在functools模块里
reduce的帮助文档如下:
reduce(...)
reduce(function, sequence[, initial]) -> value
Apply a function of two arguments cumulatively to the items of a sequence,
from left to right, so as to reduce the sequence to a single value.
For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates
((((1+2)+3)+4)+5). If initial is present, it is placed before the items
of the sequence in the calculation, and serves as a default when the
sequence is empty.
reduce会把一个迭代对象中的内容,先把前两个元素作为才是调用某个函数,然后把函数结果作为一个参数,迭代对象 的第三个元素作为第二个参数再次调用函数,这样逐次调用迭代对象的全部参数,知道最后。
对于作为参数的函数有两个要求:
- 必须有两个参数
- 必须有返回结果
参加下面案例:
from functools import reduce
# 定义一个操作函数
# 操作函数要有两个参数和一个
def myAdd(x,y):
return x + y
# 对于列表[1,2,3,4,5,6]执行myAdd的reduce操作
rst = reduce( myAdd, [1,2,3,4,5,6] )
print(rst)
filter
过滤函数,对一组数据进行过滤,符合条件的数据会生成一个新的列表并返回。
filter跟map相比较:
- 相同点:都对列表的每一个元素逐一进行操作
- 不同:
map会生成一个跟原来数据相对应的新队列filter不一定,只要符合条件的才会进入新的数据集合
filter函数怎么写:
- 利用给定参数进行判断
- 返回值一定是个布尔值
调用格式:
filter(f, data),f是过滤函数,data是数据
filter函数的一个案例是:
# 对于一个列表,对其进行过滤,偶数组成一个新列表
# 需要定义过滤函数, 过滤函数要求有输入,返回布尔值
def isEven(a):
return a % 2 == 0
l = [3,4,56,3,2,3,4556,67,4,4,3,23455,43]
rst = filter(isEven, l)
# 返回的filter内容是一个可迭代对象
print(type(rst))
print(rst)
sorted
sorted把一个序列按照给定算法进行排序, 函数的帮助文档如下:
sorted(iterable, /, *, key=None, reverse=False)
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
在sorted的参数中, key参数代表的函数在排序前对每一个元素进行key函数运算,可以理解成按照key函数定义的逻辑进行排序
sorted函数在python2和python3相差巨大, 此处以python3为准
关于排序参考下面案例:
a = [234,22312,123,45,43,2,3,66723,34]
al = sorted(a, reverse=True)
print(al)
上面案例并没有时候用参数key, 下面案例将调用排序函数:
a = [-43,23,45,6,-23,2,-4345]
# 按照绝对值进行排序
# abs是求绝对值的意思
# 即按照绝对值的倒叙排列
al = sorted(a, key=abs, reverse=True)
下面案例通过调用字符串的小写转换函数,先将所有需要排序的字符串转换,然后排序:
astr = ['dana', 'Danaa', 'wangxiaojing', 'jingjing', 'haha']
str1 = sorted(astr)
print(str1)
str2 = sorted(astr, key=str.lower)
print(str2)
装饰器
函数作为返回值
函数调用可以返回具体的值,当然也可以返回一个函数作为结果。
函数作为返回值返回的时候,被返回的函数需要在函数体内定义,如下所示:
def myF2():
def myF3():
print("In myF3")
return 3
return myF3
# 使用上面定义, 调用myF2,返回一个函数myF3,赋值给f3
f3 = myF2() #得到的是函数myF3,此时f3和myF3是同一个函数
print(type(f3))
f3()
下面的案例返回一个函数,但返回函数中,使用了外层函数的某些值,这些值可能是外层函数的参数,也可能是外层函数中 定义的一些变量:
# args:参数列表
# 1 myF4定义函数,返回内部定义的函数myF5
# 2. myF5使用了外部变量,这个变量是myF4的参数
def myF4( *args):
def myF5():
rst = 0
for n in args: #内部函数使用了外部函数的值
rst += n
return rst
return myF5
上面函数的使用:
f5 = myF4(1,2,3,4,5,6,7,8,9,0)
# f5的调用方式
f5()
闭包(closure)
当一个函数在内部又定义了一个函数,并且内部的函数使用了外部函数的参数或者局部变量,当内部函数被当做返回值的时候, 相关参数和变量保存在返回的函数中,这种结果,叫闭包
上面定义的myF4是一个标准闭包结构。
闭包常见坑
闭包函数使用起来可能会遇到坑,要小心:
def count():
# 定义列表,列表里存放的是定义的函数
fs = []
for i in range(1, 4):
# 定义了一个函数f
# f是一个闭包结构
def f():
return i * i
fs.append(f)
return fs
f1, f2, f3 = count()
print(f1())
print(f2())
print(f3())
运行结果为:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 52813 --file F:/book/python/new_python/code/te.py
pydev debugger: process 7664 is connecting
Connected to pydev debugger (build 202.7660.27)
9
9
9
Process finished with exit code 0
出现的问题分析:
- 造成上述状况的原因是,返回函数引用了变量i, i并非立即执行,而是等到三个函数都返回的时候才统一使用,此时i已经变成了3,最终调用的时候,都返回的是 3*3
- 此问题描述成:返回闭包时,返回函数不能引用任何循环变量
- 解决方案: 再创建一个函数,用该函数的参数绑定循环变量的当前值,无论该循环变量以后如何改变,已经绑定的函数参数值不再改变
修改上述函数:
def count2():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1,4):
fs.append(f(i))
return fs
f1,f2,f3 = count2()
print(f1())
print(f2())
print(f3())
修改后的函数运行和能达到预期.
当变量使用的函数
请看下面平淡无奇的案例:
def hello():
print("Hello world")
hello()
函数名称可以作为变量进行赋值:
f = hello
f()
f和hello是一个函数, 这两个函数名称不同,但id相同,通过下面代码可以验证:
print(id(f))
print(id(hello))
print(f.__name__)
print(f.__name__)
现在我们有了新的需求:
- 对hello功能进行扩展,每次打印hello之前打印当前系统时间
- 而实现这个功能又不能改动现有代码
此时可以解逐装饰器来实现.
装饰器(Decrator)
- 在不改动函数代码的基础上无限制扩展函数功能的一种机制,本质上讲,装饰器是一个能够返回函数的高阶函数
- 装饰器的使用: 使用@语法, 即在每次要扩展到函数定义前使用@+函数名
任务:
- 对hello函数进行功能扩展,每次执行hello万打印当前时间
利用下面的代码, 及定义一个装饰器, 装饰要hello函数:
import time
# 高阶函数,以函数作为参数
def printTime(f):
def wrapper(*args, **kwargs):
print("Time: ", time.ctime())
return f(*args, **kwargs)
return wrapper
# 上面定义了装饰器,使用的时候需要用到@, 此符号是python的语法糖
@printTime
def hello():
print("Hello world")
#利用语法糖调用
hello()
函数的运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55245 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12548 is connecting
Connected to pydev debugger (build 202.7660.27)
Time: Mon Dec 27 18:34:36 2021
Hello world
Process finished with exit code 0
装饰器的好处是:一点定义,则可以装饰任意函数 而函数一旦被其装饰,则把装饰器的功能直接添加到定义函数的功能上, 但对函数原本的代码功能没有任何副作用.
参看下面代码:
@printTime
def hello2():
print("今天很高兴,被老板揪着讲课了")
print("还可以由很多的选择")
hello2()
上面对函数的装饰使用了系统定义的语法糖, 下面我们尝试手动执行下装饰器
我们的代码首次按使用定义好的的装饰器, 但我们手动调用这个装饰器,把相应函数传入, 调用有两种情况,一种是 调用装饰器后返回的结果放入原来被装饰的函数名:
hello3 = printTime(hello3)
另一种是返回结果单独起了一个名字:
f = printTime(hello3)
两种不同调用也引发了不同结果,思考下,为什么会这样?
import time
# 高阶函数,以函数作为参数
def printTime(f):
def wrapper(*args, **kwargs):
print("Time: ", time.ctime())
return f(*args, **kwargs)
return wrapper
def hello3():
print("我是手动执行的")
hello3()
print("*" * 40)
hello3 = printTime(hello3)
hello3()
print("*" * 40)
f = printTime(hello3)
f()
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55607 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12188 is connecting
Connected to pydev debugger (build 202.7660.27)
我是手动执行的
****************************************
Time: Mon Dec 27 18:40:00 2021
我是手动执行的
****************************************
Time: Mon Dec 27 18:40:00 2021
Time: Mon Dec 27 18:40:00 2021
我是手动执行的
Process finished with exit code 0
思考题答案: 装饰器返回结果的代码
ruturn f(...)是一个执行语句, 如果返回语句名称一样, 则覆盖一个, 否则会执行一个f函数一个hello3函数 再不明白可以进QQ群: 999 0960, 或者微信13119144223
偏函数
我们先看一个例子, 把字符串转化成十进制数字:
int("12345")
上面例子很简单,我们还可以通过这个函数求八进制的字符串12345,表示成十进制的数字是多少:
int("12345", base=8)
我们新建一个函数,此函数是默认输入的字符串是16进制数字, 把此字符串返回十进制的数字的用法是:
def int16(x, base=16):
return int(x, base)
int16("12345")
代码很好理解,不做过多解释了.
偏函数
偏函数是参数固定的函数,相当于一个由特定参数的一个函数, 实现偏函数需要用到functools.partial:
- functools.partial的作用是,把一个函数某些函数固定,返回一个新函数
参看下面案例:
import functools
#实现上面int16的功能
int16 = functools.partial(int, base=16)
int16("12345")
高级函数补充
zip
把两个可迭代内容生成一个可迭代的tuple元素类型组成的内容
zip 案例:
l1 = [ 1,2,3,4,5]
l2 = [11,22,33,44,55]
z = zip(l1, l2)
print(type(z))
print(z)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56228 --file F:/book/python/new_python/code/te.py
pydev debugger: process 11868 is connecting
Connected to pydev debugger (build 202.7660.27)
<class 'zip'>
<zip object at 0x00000213421CED88>
Process finished with exit code 0
另一个例子:
l1 = ["wangwang", "mingyue", "yyt"]
l2 = [89, 23, 78]
z = zip(l1, l2)
for i in z:
print(i)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56172 --file F:/book/python/new_python/code/te.py
pydev debugger: process 12704 is connecting
Connected to pydev debugger (build 202.7660.27)
('wangwang', 89)
('mingyue', 23)
('yyt', 78)
<class 'zip'>
Process finished with exit code 0
考虑下面结果,为什么会为空
l3 = [i for i in z]
print(l3)
上面是zip的结果是一个迭代器, 迭代器运行到最后就是返回空,参看迭代器知识点
enumerate
- 跟zip功能比较像
- 对可迭代对象里的每一元素,配上一个索引,然后索引和内容构成tuple类型
enumerate案例1:
l1 = [11,22,33,44,55]
em = enumerate(l1)
l2 = [i for i in em]
print(l2)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56285 --file F:/book/python/new_python/code/te.py
pydev debugger: process 800 is connecting
Connected to pydev debugger (build 202.7660.27)
[(0, 11), (1, 22), (2, 33), (3, 44), (4, 55)]
Process finished with exit code 0
另一个比较飘逸的例子是:
l1 = [11,22,33,44,55]
em = enumerate(l1, start=100)
l2 = [ i for i in em]
print(l2)
结果为:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 56381 --file F:/book/python/new_python/code/te.py
pydev debugger: process 13092 is connecting
Connected to pydev debugger (build 202.7660.27)
[(100, 11), (101, 22), (102, 33), (103, 44), (104, 55)]
Process finished with exit code 0
collections模块
collections 包含了一些特殊的容器,针对Python内置的容器,例如list、dict、set和tuple,提供了另一种选择;
-
namedtuple,可以创建包含名称的tuple; -
deque: 类似于list的容器,可以快速的在队列头部和尾部添加、删除元素; -
Counter: dict的子类,计算可hash的对象; -
OrderedDict: dict的子类,可以记住元素的添加顺序; -
defaultdict: dict的子类,可以调用提供默认值的函数;
namedtuple
命名的元组,意味给元组中的每个位置赋予含义,意味着代码可读性更强, namedtuple可以在任何常规元素使用的地方使用,而且它可以通过名称来获取字段信息而不仅仅是通过位置索引。
- 是一个
tuple类型 - 是一个可命名的
tuple
namedtuple在给csv或者sqlite3返回的元组附上名称特别有用
请看下面例子:
>>> import collections
>>> Point = namedtuple('Point', ['x', 'y'])
>>> Point.__doc__ # docstring for the new class
'Point(x, y)'
>>> p = Point(11, y=22) # instantiate with positional args or keywords
>>> p[0] + p[1] # indexable like a plain tuple
33
>>> x, y = p # unpack like a regular tuple
>>> x, y
(11, 22)
>>> p.x + p.y # fields also accessible by name
33
>>> d = p._asdict() # convert to a dictionary
>>> d['x']
11
>>> Point(**d) # convert from a dictionary
Point(x=11, y=22)
>>> p._replace(x=100) # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)
从某种意义上说, 相当于直接创建了一个类, 这个类名称是Point, 有两个属性, x, y
下面这段代码如果不看Circle的定义, 级别就是一个定义的类而已:
Circle = collections.namedtuple("Circle", ['x', 'y', 'r'])
c = Circle(100, 150, 50)
print(c)
print(type(c))
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 51985 --file F:/book/python/new_python/code/te.py
pydev debugger: process 16160 is connecting
Connected to pydev debugger (build 202.7660.27)
Circle(x=100, y=150, r=50)
<class '__main__.Circle'>
Process finished with exit code 0
想检测以下namedtuple到底属于谁的子类
isinstance(c, tuple)
运行结果是:
True
dequeue
deque是栈和队列的一种广义实现,是”double-end queue”的简称;
deque支持线程安全、有效内存地以近似o(1)的性能在deque的两端插入和删除元素- 尽管
list也支持相似的操作,但是它主要在固定长度操作上的优化,从而在pop(0)和insert(0,v)(会改变数据的位置和大小)上有o(n)的时间复杂度。 - 比较方便的解决了频繁删除插入带来的效率问题
把deque理解成列表即可, 只不过列表的追加操作比较简单,但从列表头插入数据效率比较低,deque有效解决了这个问题.
参看下面案例:
from collections import deque
q = deque(['a', 'b', 'c'])
print(q)
q.append("d")
print(q)
q.appendleft('x')
print(q)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 52828 --file F:/book/python/new_python/code/te.py
pydev debugger: process 14860 is connecting
Connected to pydev debugger (build 202.7660.27)
deque(['a', 'b', 'c'])
deque(['a', 'b', 'c', 'd'])
deque(['x', 'a', 'b', 'c', 'd'])
Process finished with exit code 0
defaultdict
defaultdict是内置数据类型dict的一个子类,基本功能与dict一样,只是重写了一个方法__missing__(key)和 增加了一个可写的对象变量default_factory。
当直接读取dict不存在的属性时,直接返回默认值
看一个例子:
d1 = {"one":1, "two":2, "three":3}
print(d1['one'])
print(d1['four'])
上面代码会出一个语法错误, 点解?
而如果用defaultdict则不会报错,参看下面代码:
from collections import defaultdict
# lambda表达式,直接返回字符串
func = lambda: "刘大拿"
d2 = defaultdict(func)
d2["one"] = 1
d2["two"] = 2
print(d2['one'])
print(d2['four'])
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 53018 --file F:/book/python/new_python/code/te.py
pydev debugger: process 15108 is connecting
Connected to pydev debugger (build 202.7660.27)
1
刘大拿
Process finished with exit code 0
再打印d2["four"]的时候, 因为没有这个值,最终调用func函数,返回字符串刘大拿`.
Counter
Counter统计字符串个可以支持方便、快速的计数数
例子:
from collections import Counter
# 为什么下面结果不把abcdefgabced.....作为键值,而是以其中每一个字母作为键值
# 需要括号里内容为可迭代
c = Counter("abcdefgabcdeabcdabcaba")
print(c)
s = ["liudana", "love", "love", "love", "love", "wangxiaona"]
c = Counter(s)
print(c)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 53101 --file F:/book/python/new_python/code/te.py
pydev debugger: process 3560 is connecting
Connected to pydev debugger (build 202.7660.27)
Counter({'a': 6, 'b': 5, 'c': 4, 'd': 3, 'e': 2, 'f': 1, 'g': 1})
Counter({'love': 4, 'liudana': 1, 'wangxiaona': 1})
Process finished with exit code 0
OrderedDict
-
OrderedDict类似于正常的词典,当在有序的词典上迭代时,返回的元素就是它们第一次添加的顺序, 及有序字典中的顺序指的是插入顺序 -
class collections.OrderedDict,返回已给dict的子类,支持常规的dict的方法 -
OrderedDict是一个记住元素首次插入顺序的词典,如果一个元素重写已经存在的元素,那么原始的插入位置保持不变,如果删除一个元素再重新插入,那么它就在末尾 -
OrderedDict.popitem(last=True)中,popitem方法返回和删除一个(key,value)对,如果last=True,就以LIFO方式执行,否则以FIFO方式执行。 -
OrderedDict也支持反向迭代,例如reversed() -
OrderedDict对象之间的相等测试,例如list(od1.items()) == list(od2.items()),是对顺序敏感的 -
OrderedDict和其他的映射对象(例如常规的词典)之间的相等测试是顺序不敏感的,这就允许OrderedDict对象可以在使用常规词典的地方替换掉常规词典。 -
OrderedDict构造器和update()方法可以接受关键字变量,但是它们丢失了顺序,因为Python的函数调用机制是将一个无序的词典传入关键字变量 -
一个有序的词典记住它的成员插入的顺序,可以使用排序函数,将其变为排序的词典
参看案例如下:
from collections import OrderedDict
a = {"banana":3,"apple":2,"pear":1,"orange":4}
print(a)
# dict sorted by key, 每个 (k,v)中按 k排序
b = OrderedDict(sorted(a.items(),key = lambda t:t[0]))
print(b)
# dict sorted by value
# 每个 (k,v)中按 v排序
c = OrderedDict(sorted(a.items(),key = lambda t:t[1]))
print(c)
# 按k的长度排序
d = OrderedDict(sorted(a.items(),key = lambda t:len(t[0])))
del d['apple']
print(d)
d["apple"] = 2
print(d)
运行结果如下:
C:\Users\Augs\Anaconda3\python.exe "C:\Program Files\JetBrains\PyCharm Community Edition 2020.2.3\plugins\python-ce\helpers\pydev\pydevd.py" --multiproc --qt-support=auto --client 127.0.0.1 --port 55485 --file F:/book/python/new_python/code/te.py
pydev debugger: process 16244 is connecting
Connected to pydev debugger (build 202.7660.27)
{'banana': 3, 'apple': 2, 'pear': 1, 'orange': 4}
OrderedDict([('apple', 2), ('banana', 3), ('orange', 4), ('pear', 1)])
OrderedDict([('pear', 1), ('apple', 2), ('banana', 3), ('orange', 4)])
OrderedDict([('pear', 1), ('banana', 3), ('orange', 4)])
OrderedDict([('pear', 1), ('banana', 3), ('orange', 4), ('apple', 2)])
Process finished with exit code 0