版权声明:本文为博主原创文章,未经博主同意不可随意转载。 https://blog.csdn.net/hellokandy/article/details/85053464
【关于调用函数】
print('abs(100) = ', abs(100))
print('abs(-123) = ', abs(-123))
print('hex(10) = ', hex(10))
print('hex(255) = ', hex(255))
print('hex(1000) = ', hex(1000))
#函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
myabs = abs #变量myabs指向abs函数
print('myabs(-45.67) = ', myabs(-45.67))#所以也可以通过myabs调用abs函数
#max()函数可以接收任意个参数,并返回最大的那个:
print('max(-1,-2,3,4,5) = ', max(-1,-2,3,4,5))
【数据类型转换】
print('int(\'123\') = ', int('123'))
print('int(\'12.34\') = ', int(12.34))
print('float(\'12.34\') = ', float('12.34'))
print('str(100) = ', str(100))
print('bool(1) = ', bool(1))
print('bool('') = ', bool(''))
【定义函数】
#定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:
def my_abs(x):
if x >= 0:
return x
if x < 0:
return -x
print('my_abs(-99) = ', my_abs(-99))
#如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
#【导入与引用在其他文件中的函数】
#如果你已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,
'''
#用from abstest import my_abs来导入my_abs()函数,注意abstest是文件名(不含.py扩展名):
from abstest import my_abs#导入abstest文件的my_abs函数
my_abs(-9.9)#调用my_abs函数
'''
【参数检查】
#调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError,
#但如果参数类型不对,Python解释器就无法帮我们检查。
#测试:my_abs('A')
#对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现:
def myabs(x):
if not isinstance(x, (int, float)):
raise TypeError('参数错误!')
if x >= 0:
return x
else:
return -x
#测试:myabs('A')
【返回多个值】
import math
#计算面积与周长
def PerimeterAndArea(radius):
if not isinstance(radius, (int, float)):
raise TypeError('radius bad operand type')
perimeter = 2 * math.pi * radius
area = math.pi * math.pow(radius, 2)
return perimeter, area
#测试
_perimeter,_area = PerimeterAndArea(3);
print( '_perimeter:', _perimeter, '_area:',_area )
print( '_perimeter:{0:.3f}, _area:{1:.3f}'.format(_perimeter, _area) )
#但其实这只是一种假象,Python函数返回的仍然是单一值(一个tuple)
ret = PerimeterAndArea(3);
print('ret : ', ret)#返回(18.8495, 28.2743)
#在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值。
#所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
#定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程(ax2 + bx + c = 0)的两个解。
def quadratic(a, b, c):
x1 = (-b + math.sqrt(b*b - 4*a*c)) / (2 * a)
x2 = (-b - math.sqrt(b*b - 4*a*c)) / (2 * a)
return x1, x2
#测试
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))
if quadratic(2, 3, 1) != (-0.5, -1.0):
print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
print('测试失败')
else:
print('测试成功')
【函数的参数】
#【函数的参数-位置参数】
#计算x的n次方
def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
#power(x, n)函数有两个参数:x和n,这两个参数都是位置参数
#调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。
print('power(5,3) = ', power(5,3))
#【函数的参数-默认参数】
def power(x, n=2): #注意:必选参数在前,默认参数在后。
s = 1
while n > 0:
n = n - 1
s = s * x
return s
print('power(5) = ', power(5))
#默认参数的坑
def add_end(L=[]):
L.append('END')
return L
print('add_end([1,2,3]) = ', add_end([1,2,3]))
print('add_end([\'x\',\'y\',\'z\']) = ', add_end(['x','y','z']))
#每次调用,都会多增加一个END
print('add_end() = ', add_end())
print('add_end() = ', add_end())
#Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],
#每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
def add_end2(L=None):#定义默认参数要牢记一点:默认参数必须指向不变对象!
if L is None:
L = []
L.append('END2')
return L
print('add_end2() = ', add_end2())
print('add_end2() = ', add_end2())
#【函数的参数-可变参数】
#顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
#使用list或者tuple方案:
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum
print('calc([1,2,3]) = ', calc([1,2,3]))
print('calc((1,2,3,4)) = ', calc((1,2,3,4)))
#函数的参数改为可变参数:
#定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。
#可变参数允许传入0个或任意个参数,在函数内部,参数numbers接收到的是一个tuple
def calc2(*numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum
print('calc2(1,2,3) = ', calc2(1,2,3))
print('calc2(1,2,3,4) = ', calc2(1,2,3,4))
#如果已经有一个list或者tuple,要调用一个可变参数:
#Python允许在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去(这种写法相当有用,而且很常见)
lst = [1,2,3]
tpl = (1,2,3,4)
print('calc2(*lst) = ', calc2(*lst))
print('calc2(*tpl) = ', calc2(*tpl))
#练习
#计算一个或多个数的乘积:
def product(*num):
for k in num:
if not isinstance(k, (int, float)):
raise TypeError('参数类型错误!')
ret = 1
for n in num:
ret = ret * n
return ret
print('product(5) =', product(5))
print('product(5, 6) =', product(5, 6))
print('product(5, 6, 7) =', product(5, 6, 7))
#【函数的参数-关键字参数】
#关键字参数允许你传入0个或任意个 含参数名 的参数,这些关键字参数在函数内部自动组装为一个dict
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Mr.Zhou', 30)
person('Mrs.Chan', 28, city='guangzhou', gender='Man', job='Engineer')
#和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
extra = {'city': 'Beijing', 'job': 'Engineer', 'birth':'1997-10-10'}
person('lily', 24, **extra)#注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。
#作为coder,怎么知道关心的参数有没有传入呢?以person2()函数为例,我们希望检查是否有city和job参数:
def person2(name, age, **kw):
if 'city' in kw:#有city参数
print('have city param')
#pass
if 'job' in kw:#有job参数
#pass
print('have job param')
print('name:', name, 'age:', age, 'other:', kw)
'''
关键字参数有什么用?它可以扩展函数的功能。比如:
一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,
利用关键字参数来定义这个函数,就能满足注册的需求。
'''
#【函数的参数-命名关键字参数】
扫描二维码关注公众号,回复:
4569360 查看本文章
#跟关键字参数相比,命名关键字参数则限制了关键字参数的名字,使得函数只接收指定参数名的参数
#例如:只接收city和job作为关键字参数。这种方式定义的函数如下:
def person2(name, age, *, city, job):
print(name, age, city, job)
#和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为 命名关键字参数。
#命名关键字参数必须传入参数名,这和位置参数不同。
person2('jack', 28, city='guangzhou', job='engineer')
#如果没有传入参数名,调用将报错:
'''
person2('Jack', 24, 'Beijing', 'Engineer')
TypeError: person2() takes 2 positional arguments but 4 were given
'''
#如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person3(name, age, *args, city, job):
print(name, age, args, city, job)
#可变参数个数为0
person3('xiaoming', 22, city='chengdu', job='student')
#>>> xiaoming 22 () chengdu student
#list作为可变参数传入
lst = [1,2,3]
#在list前面加*号,把它变为可变参数传入(可变参数内部接收是一个tuple)
person3('dabao', 22, *lst, city='wuhan', job='student')
#>>> dabao 22 (1,2,3) wuhan student
#命名关键字参数可以有缺省值,从而简化调用:
def person4(name, age, *, city='Beijing', job):
print(name, age, city, job)
person4('JackMa', 28, job='Engineer')
#【递归函数】
#在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
#例子A:计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示,可以看出:
def fact(n):
if not isinstance(n, (int)):
raise TypeError('参数类型错误!')
if n==1:
return 1
return n * fact(n-1)
print('fact(1) = ', fact(1))
print('fact(3) = ', fact(3))
print('fact(5) = ', fact(5))
'''
提示:使用递归函数需要注意防止栈溢出。
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,
每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。
解决递归调用栈溢出的方法是通过尾递归优化,
事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。
这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
'''
#上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。
#要改成尾递归方式:
def recursion(n):
return recursion_impl(n, 1)
def recursion_impl(num, product):
if num == 1:
return product
return recursion_impl(num - 1, num * product)
#调用递归
print('recursion(50) = ', recursion(50))
'''
尾递归调用时,如果做了优化,栈不会增长。因此,无论多少次调用也不会导致栈溢出。
遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化。
所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。
'''
#例子B:汉诺塔的移动用递归函数实现
#编写hanoi(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,打印出把所有盘子从A借助B移动到C的方法
def hanoi(n, a, b, c):
if n==1:
print('move', a, '-->', c)
else:
hanoi(n-1, a, c, b)
hanoi(1, a, b, c)
hanoi(n-1, b, a, c)
hanoi(3, 'A', 'B', 'C')
'''
摘自百度:
其实算法非常简单,当盘子的个数为n时,移动的次数应等于2^n – 1(有兴趣的可以自己证明试试看)。
后来一位美国学者发现一种出人意料的简单方法,只要轮流进行两步操作就可以了。首先把三根柱子按顺序排成品字型,
把所有的圆盘按从大到小的顺序放在柱子A上,根据圆盘的数量确定柱子的排放顺序:若n为偶数,按顺时针方向依次摆放 A B C;
若n为奇数,按顺时针方向依次摆放 A C B。
1、按顺时针方向把圆盘1从现在的柱子移动到下一根柱子,即当n为偶数时,若圆盘1在柱子A,则把它移动到B;若圆盘1在柱子B,
则把它移动到C;若圆盘1在柱子C,则把它移动到A。
2、接着,把另外两根柱子上可以移动的圆盘移动到新的柱子上。即把非空柱子上的圆盘移动到空柱子上,当两根柱子都非空时,
移动较小的圆盘。这一步没有明确规定移动哪个圆盘,你可能以为会有多种可能性,其实不然,可实施的行动是唯一的。
3、反复进行⑴⑵操作,最后就能按规定完成汉诺塔的移动。
'''
【小结】
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args是可变参数,args接收的是一个tuple;
**kw是关键字参数,kw接收的是一个dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})。
使用*args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。