一、定义函数

1、定义函数时,需要确定函数名和参数个数
定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

def function(arg1,arg2,arg3...):
    body

**如果有必要,在函数体内先对函数接受的参数进行数据类型的检查,因为在函数定义时不指定参数类型,若传入的参数不能被函数体内的语句执行,会报错。

def f1(x):
    if isinstance(x,int)==False:
      raise TypeError('age:',x,'should be numner')
f1(‘hello’)
 Traceback (most recent call last):
 File “/Users/shiytan/PycharmProjects/pythonStar/newstyle/helloPython.py”, line 19, in 
 f1(‘hello’)
 File “/Users/shiytan/PycharmProjects/pythonStar/newstyle/helloPython.py”, line 17, in f1
 raise TypeError(‘age:’,x,‘should be numner’)
 TypeError: (‘age:’, ‘hello’, ‘should be numner’)

2、空函数
如果想定义一个什么事也不做的空函数,可以用pass语句占位,可后续补充函数体:

def nop():
    pass

二、调用函数

1、要调用一个函数,需要知道函数的名称和参数
2、函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”

a = abs # 变量a指向函数abs()
 a(-1) # 所以也可以通过a调用abs函数
 1

三、函数的参数

1、位置参数

#计算x^n^
    def power(x, n):
        s = 1
        while n > 0:
            n = n - 1
            s = s * x
        return s
power(5, 3)
 125

power(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n。函数定义时不可省略的参数是位置参数。

2、默认参数

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s
power(5)
 25
 power(5, 2)
 25

*设置默认参数时,有几点要注意:
1)是必选参数在前,默认参数在后,否则Python的解释器会报错(因为不完全传参时不知道传入的参数应该赋给默认参数还是必选参数)

2)当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面,变化小的参数就可以作为默认参数。

3)使用默认参数最大的好处是能降低调用函数的难度。

4)有多个默认参数时可以按顺序传参,也可以不按顺序传参

def enroll(name, gender, age=6, city='Beijing'):
    print('name:', name)
    print('gender:', gender)
    print('age:', age)
    print('city:', city)
#按顺序,将7传给第一个默认参数age
 enroll(‘Bob’, ‘M’, 7)
 不按顺序,将Tianjin传给指定默认参数city
 enroll(‘Adam’, ‘M’, city=‘Tianjin’)

5)⚠️定义默认参数时,默认参数必须指向不可变对象

#错误示范
    def add_end(L=[]):
        L.append('END')
        return L
add_end()
 [‘END’, ‘END’]
 add_end()
 [‘END’, ‘END’, ‘END’]

多次用默认参数调用,函数似乎每次都“记住了”上次添加了’END’后的list,原因是Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。
因此为了L不被改变,定义时应该使用不变对象,减少了由于修改数据导致的错误。

#正确示范
    def add_end(L=None):
        if L is None:
            L = []
            L.append('END')
        return L

3、可变参数

可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个,这些可变参数在函数调用时自动组装为一个tuple。

#计算a^2^ + b^2^ + c^2^ + ……
    def calc(*numbers):
        sum = 0
        for n in numbers:
            sum = sum + n * n
        return sum
calc(1, 2, 3)
 14
 calc(1, 3, 5, 7)
 84
 calc()
 0

仅仅在参数前面加了一个*号。在函数内部,参数numbers接收到的是一个tuple,调用该函数时,可以传入任意个参数,包括0个参数。和先定义一个list或tuple再传给numbers是一样的。

如果已经有一个list或者tuple:nums = [1, 2, 3],要调用一个可变参数的函数calc(*numbers)怎么办?可以这样做:
1)(繁琐)calc(nums[0], nums[1], nums[2])
2)calc(*nums)
*nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。

4、关键字参数

关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)
person(‘Adam’, 45, gender=‘M’, job=‘Engineer’)
 name: Adam age: 45 other: {‘gender’: ‘M’, ‘job’: ‘Engineer’}

(繁琐)先组装一个dict,分开传入

extra = {‘city’: ‘Beijing’, ‘job’: ‘Engineer’}
 person(‘Jack’, 24, city=extra[‘city’], job=extra[‘job’])
 name: Jack age: 24 other: {‘city’: ‘Beijing’, ‘job’: ‘Engineer’}

**先组装一个dict,转化为关键字参数传入

extra = {‘city’: ‘Beijing’, ‘job’: ‘Engineer’}
 person(‘Jack’, 24, **extra)
 name: Jack age: 24 other: {‘city’: ‘Beijing’, ‘job’: ‘Engineer’}

5、命名关键字参数

如果要限制关键字参数的名字,就可以用命名关键字参数。
1)命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数,命名关键字参数必须传入参数名。
如果传入非限制内的关键字参数,会报获得了意料之外的关键字。

#只接收city和job作为关键字参数
def person(name, age, *, city, job):
    print(name, age, city, job)
person(‘Jack’, 24, city=‘Beijing’, job=‘Engineer’)
 Jack 24 Beijing Engineer

2)如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了

#*args是可变参数,city和job作为关键字参数,不需要*
def person(name, age, *args, city, job):
    print(name, age, args, city, job)
person(‘Amy’,25,‘baba’,‘mama’,city=‘bejing’,job=‘teacher’)
 Amy 25 (‘baba’, ‘mama’) bejing teacher

3)命名关键字参数具有默认值,调用时可不传入该参数

#*args是可变参数,city和job作为关键字参数,city有默认值
 def person(name, age, *args, city=‘beijing’, job):
        print(name, age, args, city, job)
person(‘Amy’,25,‘baba’,‘mama’,job=‘teacher’)
 Amy 25 (‘baba’, ‘mama’) bejing teacher

6、参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。
但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

def f2(a, b, c=0, *d,e='default',key, **kw):
    print('a =', a,'\nb =', b, '\nc =', c, '\nd =', d, '\ne =',e,'\nkey =',key,'\nkw =', kw)

1)按序调用

f2(‘Amy’,‘female’,25,‘baba’,‘mama’,key= 125343,home=‘bejing’,job=‘teacher’)
a = Amy
 b = female
 c = 25
 d = (‘baba’, ‘mama’)
 e = default
 key = 125343
 kw = {‘home’: ‘bejing’, ‘job’: ‘teacher’}

2)通过一个tuple和dict调用

arg = (‘Bob’,‘female’,25,‘baba’)
 kw={‘e’:‘newdata’,‘key’:12449,‘home’:‘bejing’,‘job’:‘doctor’}
 f2(*arg,**kw)a = Bob
 b = female
 c = 25
 d = (‘baba’,)
 e = newdata
 key = 12449
 kw = {‘home’: ‘bejing’, ‘job’: ‘doctor’}

**所以,对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的,虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。

四、函数的返回

1、函数体内部可以用return随时返回函数结果
一旦执行到return时,函数就执行完毕,并将结果返回;函数执行完毕也没有return语句时,自动return None。

2、函数可以同时返回多个值,但其实就是一个tuple

#计算两点坐标
    import math

def move(x, y, step, angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx, ny
x, y = move(100, 100, 60, math.pi / 6)
 print(x, y)
 151.96152422706632 70.0

看似返回了两个值,但实际上返回的是一个省略了括号的tuple,并按顺序赋值给接受的变量

五、递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

1)使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
2)针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
3)Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

汉诺塔的移动也可以看做是递归函数。

我们对柱子编号为a, b, c,将所有圆盘从a移到c可以描述为:
如果a只有一个圆盘,可以直接移动到c;
如果a有N个圆盘,可以看成a有1个圆盘(底盘) + (N-1)个圆盘,首先需要把 (N-1) 个圆盘移动到 b,然后,将 a的最后一个圆盘移动到c,再将b的(N-1)个圆盘移动到c。
请编写一个函数,给定输入 n, a, b, c,打印出移动的步骤:
move(n, a, b, c)
例如,输入 move(2, ‘A’, ‘B’, ‘C’),打印出:
A --> B
A --> C
B --> C

def move(n, a, b, c):
    if n == 1:
        print (a+'-->'+c)
        return
    else:
        move(n-1,a,c,b)
        print(a,'-->',c)
        move(n-1,b,a,c)
move(4, ‘A’, ‘B’, ‘C’)
 A–>B
 A–>C
 B–>C
 A–>B
 C–>A
 C–>B
 A–>B
 A–>C
 B–>C
 B–>A
 C–>A
 B–>C
 A–>B
 A–>C
 B–>C