函数定义
你可以定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号 ()。
- 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
#函数的定义
def my_abs(x):
if x >= 0:
return x
else:
return -x
#函数的调用
my_abs(-5)
函数的特性
特性:
- 减少重复代码
- 使程序变的可扩展
- 使程序变得易维护
常用函数
# https://docs.python.org/3/library/functions.html
abs(-5)
max()
min()
#用于数据类型转换
int('5')
float('2.3')
bool(-2312) #bool的数值除了0和空字符为0,其他为1
str(1.23)
函数参数
1.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
2.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
-
位置参数
def power(x): return x * x
-
默认参数
最大的好处是能降低调用函数的难度。只需要传递一个位置参数
def power(x, n=2): s = 1 while n > 0: n = n - 1 s = s * x return s
默认参数传递会出现个问题
>>> def add_end(L=[]): ... L.append('END') ... return L ... #正常调用 >>> add_end([1, 2, 3]) [1, 2, 3, 'END'] >>> add_end(['x', 'y', 'z']) ['x', 'y', 'z', 'END'] #异常调用 >>> add_end() ['END'] >>> add_end() ['END', 'END']
原因:
Python函数在定义的时候,默认参数
L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。注意:定义默认参数要牢记一点:默认参数必须指向不变对象(number,string,tuple)!
-
可变参数
可变参数就是传入的参数个数是可变的,这些输入的参数在函数内部自动组装为一个tuple,或者输入参数是一个list或tuple
def calc(*numbers): sum = 0 for n in numbers: sum = sum + n * n return sum
-
关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。或者输入参数是个字典
def person(name, age, **kw): print('name:', name, 'age:', age, 'other:', kw)
-
命名关键字参数
作用:限制函数的调用者传入任意不受限制的关键字参数
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
#函数定义中没有了一个可变参数 def person(name, age, *, city, job): print(name, age, city, job)
#函数定义中已经有了一个可变参数 def person(name, age, *args, city, job): print(name, age, args, city, job)
参数组合的情况,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
-
*args
是可变参数,args接收的是一个tuple;可变参数既可以直接传入:func(1, 2, 3)
,又可以先组装list或tuple,再通过*args
传入:func(*(1, 2, 3))
; -
**kw
是关键字参数,kw接收的是一个dict。关键字参数既可以直接传入:func(a=1, b=2)
,又可以先组装dict,再通过**kw
传入:func(**{'a': 1, 'b': 2})
。 -
n=“ ”
是默认参数,默认参数一定要用不可变对象 -
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符
*
,否则定义的将是位置参数。命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
-
位置参数要按照参数的先后顺序,否则传入的数据会混乱
函数返回值
默认返回值为None
返回值只能有一个,return 多个值其实只是返回一个tuple
例如
>>> def printxy():
... x = 1
... y = 2
... return x,y
...
>>> a,b = printxy()
>>> a
1
>>> b
2
>>> c = printxy()
>>> c
(1, 2)
递归函数
如果一个函数在内部调用自身本身,这个函数就是递归函数。
def calc(n):
print(n)
if int(n/2) ==0:
return n
return calc(int(n/2))
calc(10)
输出:
10
5
2
1
递归特性:
- 必须有一个明确的结束条件
- 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出),python栈有999个
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
例如:普通递归函数
#递归函数
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
例如:尾递归
def fact1(num):
return fact_iner(num,1)
def fact_iner(num,result):
if num == 1:
return result
return fact_iner(num-1,result*num)
print(fact1(5))
匿名函数
优点:
1.不需要显式的指定函数,减少了与函数名冲突的可能
2.函数调用完,内存立刻回收
#普通函数
def calc(n):
return n**n
print(calc(10))
#换成匿名函数
calc = lambda n:n**n
print(calc(10))
高阶函数
定义:变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,还可以把函数作为结果值返回,这种函数就称之为高阶函数。
例如,函数作为一个参数值
def add(x,y,f):
return f(x) + f(y)
res = add(3,-6,abs)
print(res)
例如,函数作为一个返回值
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>> f()
25
1. 函数作为返回值
闭包
定义:在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。(参考局部变量nonlocal)
闭包的用途:
用途1,当闭包执行完后,仍然能够保持住当前的运行环境。比如说,下棋,保留之前移动后的点
用途2,闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。
注意::使用闭包的过程中,一旦外函数被调用一次返回了内函数的引用,虽然每次调用内函数,是开启一个函数执行过后消亡,但是闭包变量实际上只有一份,每次开启内函数都在使用同一份闭包变量。
例如:返回函数不是立刻执行,而是等待内函数调用才执行
def count():
def f():
return i*i
fs = []
for i in range(1, 4):
fs.append(f)
return fs
#输出结果:
>>> f1, f2, f3 = count()
>>> f1()
9
>>> f2()
9
>>> f3()
9
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量
解决办法:闭包一定要使用循环变量(代码较长,可利用lambda函数缩短代码。)
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
2. 函数作为参数
map()
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
>>> map(lambda x:x*x,[1,2,3,4,5])
<map object at 0x000002EEFF020278>
>>> list(map(lambda x:x*x,[1,2,3,4,5]))
[1, 4, 9, 16, 25]
reduce()
reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,
>>> from functools import reduce
>>> def prod(L):
... return reduce(lambda x,y:x*y,L)
...
>>> prod([3, 5, 7, 9])
945
sorted()
Python内置的sorted()函数就可以对list进行排序:
#数值的排序
print(sorted([36, 5, -12, 9, -21]))
#默认情况下,对字符串排序,是按照ASCII的大小比较的
#由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面。
print(sorted(['bob', 'about', 'Zoo', 'Credit']))
#我们给sorted传入key函数,即可实现忽略大小写的排序
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower))
#要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True))
3.函数被赋值变量,通过变量调用
装饰器
定义:本质是函数,(装饰其他函数),为其他函数添加附加功能
原则:
- 不能修改被修饰的函数的源代码
- 不能修改被修饰的函数的调用方式
三个特征:
- 函数即变量
- 高阶函数(函数作为实参传递, 函数作为返回值返回)
- 嵌套函数(在函数定义另一个函数)
decorator就是一个返回函数的高阶函数
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log #相当于now = log(now)
def now():
print('2015-3-25')
decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
@log('execute') #相当于now =log('execute')(now)
def now():
print('2015-3-25')
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
>>> now.__name__
'wrapper'
需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
偏函数
偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数。
例如,转换大量的二进制字符串,每次调用函数都要使用 int(x,2)
def int2(x, base=2):
return int(x, base)
这样,我们转换二进制就非常方便了:
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial
就是帮助我们创建一个偏函数的,不需要我们自己定义int2()
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
创建偏函数时,实际上可以接收函数对象、*args
和**kw
这3个参数
第一种
kw = { 'base': 2 }
int('10010', **kw)
第二种:
max2 = functools.partial(max, 10)
max2()
args = (10, 5, 6, 7)
max(*args)
内置函数
#进制
oct(x)
#将一个整数转变为一个前缀为“0o”的八进制字符串。
hex(x)
#将整数转换为以“0x”为前缀的小写十六进制字符串。
abs(x)
#返回一个数的绝对值。实参可以是整数或浮点数。如果实参是一个复数,返回它的模。
ascii(object)
#就像函数 repr(),返回一个对象可打印的字符串,但是 repr() 返回的字符串中非 ASCII 编码的字符,会使用 x、u 和 U 来转义。生成的字符串和 Python 2 的 repr() 返回的结果相似。
#unicode码的字符和数字转换
chr(i)
#返回 Unicode 码位为整数 i 的字符的字符串格式。例如,chr(97) 返回字符串 'a',chr(8364) 返回字符串 '€'。这是 ord() 的逆函数。
ord(c)
#对表示单个 Unicode 字符的字符串,返回代表它 Unicode 码点的整数。
dir([object])
#如果没有实参,则返回当前本地作用域中的名称列表。如果有实参,它会尝试返回该对象的有效属性列表。
divmod(a, b)
#它将两个(非复数)数字作为实参,并在执行整数除法时返回一对商和余数。
eval(expression, globals=None, locals=None)
#实参是一个字符串,以及可选的 globals 和 locals。globals 实参必须是一个字典。locals 可以是任何映射对象。
exec(object[, globals[, locals]])
#这个函数支持动态执行 Python 代码。object 必须是字符串或者代码对象。如果是字符串,那么该字符串将被解析为一系列 Python 语句并执行(除非发生语法错误)。[1] 如果是代码对象,它将被直接执行。
filter(function, iterable)
#用 iterable 中函数 function 返回真的那些元素,构建一个新的迭代器。iterable 可以是一个序列,一个支持迭代的容器,或一个迭代器。如果 function 是 None ,则会假设它是一个身份函数,即 iterable 中所有返回假的元素会被移除。
hash(object)
#返回该对象的哈希值(如果它有的话)。哈希值是整数。它们在字典查找元素时用来快速比较字典的键。相同大小的数字变量有相同的哈希值
iter(object[, sentinel])
#返回一个 iterator 对象。
repr(object)
#返回包含一个对象的可打印表示形式的字符串。
reversed(seq)
#返回一个反向的 iterator。
round(number[, ndigits])
#返回 number 舍入到小数点后 ndigits 位精度的值。 如果 ndigits 被省略或为 None,则返回最接近输入值的整数。
#注解 对浮点数执行 round() 的行为可能会令人惊讶:例如,round(2.675, 2) 将给出 2.67 而不是期望的 2.68。 这不算是程序错误:这一结果是由于大多数十进制小数实际上都不能以浮点数精确地表示。
zip(*iterables)
#创建一个聚合了来自每个可迭代对象中的元素的迭代器。
>>>a = [1,2,3]
>>> b = [4,5,6]
>>> c = [4,5,6,7,8]
>>> zipped = zip(a,b) # 打包为元组的列表
[(1, 4), (2, 5), (3, 6)]
>>> zip(a,c) # 元素个数与最短的列表一致
[(1, 4), (2, 5), (3, 6)]
>>> zip(*zipped) # 与 zip 相反,*zipped 可理解为解压,返回二维矩阵式
[(1, 2, 3), (4, 5, 6)]
__import__(name, globals=None, locals=None, fromlist=(), level=0)
#此函数会由 import 语句发起调用。
局部变量和全局变量
全局与局部变量
在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
全局变量作用域是整个程序,局部变量作用域是定义该变量的子程序。
当全局变量与局部变量同名时:
在定义局部变量的子程序内,局部变量起作用;在其它地方全局变量起作用。
在局部声明局部变量,只会修改局部变量,不会修改到全局变量(即使传递参数也是一样)
gcount = 0
def global_test():
gcount = 1
gcount+=1
print (gcount)
global_test()
在局部声明全局变量,如果在局部要对全局变量修改,需要在局部也要先声明该全局变量:
gcount = 0
def global_test():
global gcount
gcount+=1
print (gcount)
global_test()
在局部如果不声明全局变量,并且不修改全局变量。则可以正常使用全局变量:
gcount = 0
def global_test():
print (gcount)
global_test()
nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量。
def make_counter():
count = 0
def counter():
nonlocal count
count += 1
return count
return counter
def make_counter_test():
mc = make_counter()
print(mc())
print(mc())
print(mc())
make_counter_test()
#输出结果
"D:Program FilesPython37python.exe" C:/Users/Phoenix/PycharmProjects/test/local_global_var.py
1
2
3