Python 学习笔记(函数有关)

版权声明:本文为博主原创文章,未经博主同意不可随意转载。 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的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

猜你喜欢

转载自blog.csdn.net/hellokandy/article/details/85053464