自定义函数

要判断某个对象是否可调用,可使用内置函数callable

import math
x = 1
y = math.sqrt
callable(x)#结果为:False
callable(y)#结果为:True

使用def(表示定义函数)语句,来定义函数

def sq(name):
    return name + ',say:Hello,beyond!'

print(sq("yanyu"))#结果为:yanyu,say:Hello,beyond!
print(sq("sq"))#结果为:sq,say:Hello,beyond!

编写一个函数,返回一个由斐波那契数组成的列表
return语句用于从函数返回值,很重要!!!

def fibs(num): 
    result = [0, 1] 
    for i in range(num-2): 
        result.append(result[-2] + result[-1]) 
    return result

print(fibs(10))#结果为:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
print(fibs(15))#结果为:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]

1,给函数写文档

给函数编写文档,以确保其他人能够理解
可添加注释(以#打头的内容)
也可以添加独立的字符串
放在函数开头的字符串称为文档字符串(docstring),将作为函数的一部分存储起来
__doc__是函数的一个属性,属性名中的双下划线表示这是一个特殊的属性

def beyond(x):
	'I like beyond band!!!'
	return x*x

beyond.__doc__#结果为:'I like beyond band!!!'

help(beyond)
'''
#结果为:
Help on function beyond in module __main__:

beyond(x)
    I like beyond band!!!
'''

beyond(8)#结果为:64

2,其实并不是函数的函数

在Python中,函数就是函数,即使它严格来说并非函数(例如什么都不返回)

def beyond():
	print("I like beyond band!!!")
	return这里的return就是为了结束函数
	print("beyondyanyu")

x = beyond()#结果为:I like beyond band!!!
x#结果为:什么也没有
print(x)#结果为:None

参数魔法

修改参数

def beyond(n):
	n = 'beyondyanyu'

yy = 'yanyu'
beyond(yy)
yy#结果为:'yanyu'

与下面代码段等价

yy = 'yanyu'
n = yy
n = 'beyondyanyu'
yy#结果为:‘yanyu’

明显可见,n变了,但是yy没发生改变
在函数内部给参数赋值对外部没有任何影响
参数存储在局部作用域

字符串(以及数和元组)是不可变的(immutable),不能修改,只能替换
为新值

def change(n):
	n[0] = 'beyond'

names = ['yanyu','huangjiaju','huangjiaqiang']
change(names)
names#结果为:['beyond', 'huangjiaju','huangjiaqiang']

于下面代码段等价

names = ['yanyu','huangjiaju','huangjiaqiang']
n = names
n[0] = 'beyond'
names#结果为:['beyond', 'huangjiaju','huangjiaqiang']

将同一个列表赋给两个变量时,这两个变量将同时指向这个列表
也就是说对列表操作的函数,已经对列表本身动手了!!!
要避免这样的结果,必须创建列表的副本
对序列执行切片操作时,返回的切片都是副本
如果你创建覆盖整个列表的切片,得到的将是列表的副本

names = ['yanyu','huangjiaju','huangjiaqiang']
n = names[:]

n is names#结果为:False
n == names#结果为:True

n[0] ='gotohere'
n#结果为:['gotohere','huangjiaju','huangjiaqiang']
names#结果为:['yanyu','huangjiaju','huangjiaqiang']
#n是names这个列表的切片,此时的n和那么是两个不同的列表,尽管列表元素一样

下面开始和函数进行结合

def change(n):
	n[0] = 'beyond'
	
names = ['yanyu','huangjiaju','huangjiaqiang']
change(names[:])
names#结果为:['beyond', 'huangjiaju', 'huangjiaqiang']

1,为何要修改参数(位置参数)

使用函数来修改数据结构(如列表或字典)是一种不错的方式
编写程序:存储姓名,并让用户能够根据姓名、中间姓或姓找人

def init(data): 
    data['first'] = {
   
     } 
    data['middle'] = {
   
     } 
    data['last'] = {
   
     }
    
def lookup(data, label, name): 
    return data[label].get(name)

def store(data, full_name): 
    names = full_name.split() 
    if len(names) == 2: names.insert(1, '') 
    labels = 'first', 'middle', 'last' 
 
    for label, name in zip(labels, names): 
        people = lookup(data, label, name) 
        if people: 
            people.append(full_name) 
        else: 
            data[label][name] = [full_name]
            

MyNames = {
   
     }
init(MyNames)
store(MyNames, 'Magnus Lie Hetland') 
print(lookup(MyNames, 'middle', 'Lie'))#结果为:['Magnus Lie Hetland']
#查找middle是Lie的

store(MyNames, 'Robin Hood')
store(MyNames, 'Robin Locksley') 
print(lookup(MyNames, 'first', 'Robin'))#结果为:['Robin Hood', 'Robin Locksley']
#查找first是Robin的

store(MyNames, 'Mr. Gumby') 
print(lookup(MyNames, 'middle', ''))#结果为:['Robin Hood', 'Robin Locksley', 'Mr. Gumby']
#查找middle是空的

2,如果参数(位置参数)是不可变的

def inc(x):
	return x+1

foo = 10
inc(foo)
foo#结果为:11

def inc(x):
	x[0] = x[0] + 1

foo = [10]
inc(foo)
foo#结果为:[11]

关键字参数和默认值

def hello_1(name,hobby):
    print('{},{}!'.format(name,hobby))
def hello_2(hobby,name):
    print('{},{}!'.format(hobby,name))

hello_1('yanyu','eat')#结果为:yanyu,eat!
hello_2('wangsiqi','sleep')#结果为:wangsiqi,sleep!
'''
yanyu--name       eat---hobby
wangsiqi--hobby   sleep---name
'''

hello_1(name='huangjiaju',hobby='sing')#结果为:huangjiaju,sing!
hello_1(hobby='sing',name='huangjiaju')#结果为:huangjiaju,sing!
hello_2(name='huangjiaju',hobby='sing')#结果为:sing,huangjiaju!

def hello_3(name='beyond',hobby='song'):
	print('{},{}!'.format(name,hobby))

hello_3()#结果为:beyond,song!
hello_3('huangjiaju')#结果为:huangjiaju,song!
hello_3('huangjiaqiang','guitar')#结果为:huangjiaqiang,guitar!
hello_3(hobby='game')#结果为:beyond,game!

函数hello_4可能要求必须指定name,而hobby和punctuation是可选的

def hello_4(name,hobby='guitar',punctuation='!'):
    print('{},{}{}'.format(hobby,name,punctuation))

hello_4('beyond')#结果为:guitar,beyond!
hello_4('beyond','song')#结果为:song,beyond!
hello_4('beyond','song','$$$')#结果为:song,beyond$$$
hello_4('beyond',punctuation='...')#结果为:guitar,beyond...
hello_4('yanyu',hobby='play football')#结果为:play football,yanyu!
hello_4()#结果为:TypeError: hello_4() missing 1 required positional argument: 'name'

收集参数

参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来

def print_params(*params): 
    print(params)

print_params('beyondyanyu')#结果为:('beyondyanyu',)
print_params(1014, 522, 319)#结果为:(1014, 522, 319)

与下面的代码段等价

星号意味着收集余下的位置参数,如果没有可供收集的参数,params将是一个空元组。

def print_params_2(title, *params): 
    print(title) 
    print(params)

print_params_2('Params:', 1, 2, 3)
#结果为:
#Params:
#(1, 2, 3)
print_params_2('Nothing:')
#结果为:
#Nothing:
#()

与赋值时一样,带星号的参数也可放在其他位置(而不是最后
在这种情况下你需要做些额外的工作:使用名称来指定后续参数

def in_the_middle(x, *y, z): 
    print(x, y, z)

in_the_middle(1, 2, 3, 4, 5, z=7)#结果为:1 (2, 3, 4, 5) 7
in_the_middle(1, 2, 3, 4, 5, 7)#结果为:TypeError: in_the_middle() missing 1 required keyword-only argument: 'z'

星号不会收集关键字参数

def print_params_2(band, *params): 
    print(band) 
    print(params)

print_params_2('beyond', age=22)#结果为:TypeError: print_params_2() got an unexpected keyword argument 'age'

要收集关键字参数,可使用两个星号;这样得到的是一个字典而不是元组

def print_params_3(band, **params): 
    print(band) 
    print(params)
    
print_params_3('beyond', x=1, y=2, z=3)
'''
结果为:
beyond
{'x': 1, 'y': 2, 'z': 3}
'''

可结合使用这些技术

def print_params_4(x, y, z=3, *pospar, **keypar): 
    print(x, y, z) 
    print(pospar) 
    print(keypar)

print_params_4(1, 2, 3, 5, 6, 7, foo=1, bar=2)
'''
结果为:
1 2 3
(5, 6, 7)
{'foo': 1, 'bar': 2}
'''
print_params_4(1, 2)
'''
结果为:
1 2 3
()
{}
'''

分配参数

分配参数,通过在调用函数(而不是定义函数)时使用运算符*实现的

def add(x, y): 
    return x + y
    
beyond = (10, 14)
add(*beyond)#结果为:24

使用运算符**,可将字典中的值分配给关键字参数

def hello_3(name='beyond',hobby='song'):
    print('{},{}!'.format(name,hobby))
    
params = {
   
     'name': 'huangjiaju', 'hobby': 'haikuotiankong'}
hello_3(**params)#结果为:huangjiaju,haikuotiankong!

如果在定义和调用函数时都使用*或**,将只传递元组或字典。
对于函数with_stars,我在定义和调用它时都使用了星号,而对于函数without_ stars,我在定义和调用它时都没有使用,但这两种做法的效果相同。
只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用

def with_stars(**kwds): 
    print(kwds['name'], 'is', kwds['age'], 'years old') 

def without_stars(kwds): 
    print(kwds['name'], 'is', kwds['age'], 'years old')
args = {
   
     'name': 'huangjiaju', 'age': 31}

with_stars(**args)#结果为:huangjiaju is 31 years old
without_stars(args)#结果为:huangjiaju is 31 years old

练习使用参数

def story(**kwds): 
    return 'Once upon a time, there was a {job} called {name}.'.format_map(kwds) 
def power(x, y, *others): 
    if others: 
        print('Received redundant parameters:', others) 
    return pow(x, y) 
def interval(start, stop=None, step=1): 
    'Imitates range() for step > 0' 
    if stop is None: 如果没有给参数stop指定值,
        start, stop = 0, start 就调整参数start和stop的值
    result = [] 

    i = start 从start开始往上数
    while i < stop: 数到stop位置
        result.append(i) 将当前数的数附加到result末尾
        i += step 增加到当前数和step(> 0)之和
    return result
print(story(job='band', name='beyond'))#结果为:Once upon a time, there was a band called beyond.
print(story(name='huangjiaju', job='singer'))#结果为:Once upon a time, there was a singer called huangjiaju.

params = {
   
     'job': 'language', 'name': 'Python'}
print(story(**params))#结果为:Once upon a time, there was a language called Python.

del params['job']
print(story(job='great code', **params))#结果为:Once upon a time, there was a great code called Python.

power(2, 3)#结果为:8
power(3, 2)#结果为:9
power(y=3, x=2)#结果为:8

params = (5,) * 2
power(*params)#结果为:3125

power(3, 3, 'Hello, beyond')
'''
结果为:
Received redundant parameters: ('Hello, beyond',)
27
'''

interval(10)#结果为:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
interval(1, 5)#结果为:[1, 2, 3, 4]
interval(3, 12, 4)#结果为:[3, 7, 11]

power(*interval(3, 7))
'''
结果为:
Received redundant parameters: (5, 6)
81
'''

作用域

可将变量视为指向值的名称
执行赋值语句x = 1后,名称x指向值1;
这几乎与使用字典时一样(字典中的键指向值),只是你使用的是“看不见”的字典

有一个名为vars的内置函数,它返回这个不可见的字典
一般而言,不应修改vars返回的字典,因为根据Python官方文档的说法,这样做的结果是不确定的。换而言之,可能得不到你想要的结果。

x = 1
scope = vars()
scope['x']#结果为:1

scope['x'] += 1
x#结果为:2

这种“看不见的字典”称为命名空间作用域
除全局作用域外,每个函数调用都将创建一个。

在这里,函数foo修改(重新关联)了变量x,但当你最终查看时,它根本没变。
这是因为调用foo时创建了一个新的命名空间,供foo中的代码块使用。
赋值语句x = 42是在这个内部作用域(局部命名空间)中执行的,不影响外部(全局)作用域内的x。
在函数内使用的变量称为局部变量(与之相对的是全局变量)。
参数类似于局部变量,因此参数与全局变量同名不会有任何问题。

def foo(): x = 42

x = 1
foo()
x#结果为:1

def output(x): print(x)

x = 1
y = 2
output(y)#结果为:2

像下面代码这样访问全局变量是众多bug的根源。务必慎用全局变量。

def combine(name): print(name + band)

band = 'beyond'
combine('huangjiaju')#结果为:huangjiajubeyond

读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。
如果有一个局部变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮住了。
如果需要,可使用函数globals来访问全局变量。这个函数类似于vars,返回一个包含全局变量的字典。(locals返回一个包含局部变量的字典。)
例如,在前面的示例中,如果有一个名为name的全局变量,就无法在函数combine中访问它,因为有一个与之同名的参数。
然而,必要时可使用globals()[‘name’]来访问它。

def combine(name):
    print(name + globals()['name'])

name = 'huangjiaju'
combine('beyond')#结果为:beyondhuangjiaju

在函数内部给变量赋值时,该变量默认为局部变量
可通过global明确地告诉Python它是全局变量

x = 1

def change_global():
    global x
    x = x + 1

change_global()
x#结果为:2

作用域嵌套

Python函数可以嵌套,即可将一个函数放在另一个函数内

def foo(): 
    def bar(): 
        print("Hello, world!") 
    bar()

嵌套通常用处不大,即:使用一个函数来创建另一个函数
一个函数位于另一个函数中,且外面的函数返回里面的函数。也就是返回一个函数,而不是调用它
重要的是,返回的函数能够访问其定义所在的作用域。换而言之,它携带着自己所在的环境(和相关的局部变量)!

每当外部函数被调用时,都将重新定义内部的函数,而变量factor的值也可能不同。
由于Python的嵌套作用域,可在内部函数中访问这个来自外部局部作用域(multiplier)的变量。
像multiplyByFactor这样存储其所在作用域的函数称为闭包
通常,不能给外部作用域内的变量赋值,但如果一定要这样做,可使用关键字nonlocal。这个关键字的用法与global很像,让你能够给外部作用域(非全局作用域)内的变量赋值。

def multiplier(factor): 
    def multiplyByFactor(number): 
        return number * factor 
    return multiplyByFactor

double = multiplier(2)
double(5)#结果为:10

triple = multiplier(3)
triple(3)#结果为:9

multiplier(5)(4)#结果为:20

递归

递归意味着引用(这里是调用)自身
递归函数通常包含下面两部分:
基线条件(针对最小的问题):满足这种条件时函数将直接返回一个值。
递归条件:包含一个或多个调用,这些调用旨在解决问题的一部分。

函数调用自身时,是两个不同的函数[更准确地说,是不同版本(即命名空间不同)的同一个函数]在交流。

阶乘和幂

阶乘
n的阶乘为n × (n-1) × (n-2) × … × 1
可以使用循环:首先将result设置为n,再将其依次乘以1到n-1的每个数字,最后返回result

def factorial(n): 
    result = n 
    for i in range(1, n): 
        result *= i 
    return result

factorial(5)#结果为:120

阶乘的数学定义:
1的阶乘为1
对于大于1的数字n,其阶乘为n-1的阶乘再乘以n

其中,函数调用factorial(n)和factorial(n – 1)是不同的实体

def factorial(n): 
    if n == 1: 
        return 1 
    else: 
        return n * factorial(n - 1)
        
factorial(5)#结果为:120


计算幂,可使用内置函数pow和运算符**

power(x, n)(x的n次幂)是将数字x自乘n - 1次的结果,即将n个x相乘的结果。
换而言之,power(2, 3)是2自乘两次的结果,即2 × 2 × 2 = 8。

def power(x, n): 
    result = 1 
    for i in range(n): 
        result *= x 
    return result

power(2, 3)#结果为:8

幂的数学定义:
对于任何数字x,power(x, 0)都为1
n>0时,power(x, n)为power(x, n-1)与x的乘积

def power(x, n): 
    if n == 0: 
        return 1 
    else: 
        return x * power(x, n - 1)

power(2, 3)#结果为:8

使用递归的可读性

二分查找

玩个游戏:对方心里想着一个1~100的数字,你必须猜出是哪个。当然,猜
100次肯定猜对,但最少需要猜多少次呢?
思路: 如果上限和下限相同,就说明它们都指向数字所在的位置,因此将这个数字返回
否则,找出区间的中间位置(上限和下限的平均值),再确定数字在左半部分还是右半部分。然后在继续在数字所在的那部分中查找
关键在于元素是经过排序的

def search(sequence, number, lower=0, upper=None): 
    if upper is None: upper = len(sequence) - 1
    if lower == upper: 
        assert number == sequence[upper] 
        return upper 
    else: 
        middle = (lower + upper) // 2 
        if number > sequence[middle]: 
            return search(sequence, number, middle + 1, upper) 
        else: 
            return search(sequence, number, lower, middle)

seq = [34, 67, 8, 123, 4, 100, 95]
seq.sort()
seq#结果为:[4, 8, 34, 67, 95, 100, 123]

search(seq, 34)#结果为:2
search(seq, 100)#结果为:5

Python提供了一些有助于进行这种函数式编程的函数:map、filter和reduce

# 与[str(i) for i in range(10)]等价
list(map(str, range(10)))结果为:['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']

可使用filter根据布尔函数的返回值来对元素进行过滤

def func(x): 
    return x.isalnum()

seq = ["foo", "x41", "?!", "***"]
list(filter(func, seq))#结果为:['foo', 'x41']

如果转而使用列表推导,就无需创建前述自定义函数

seq = ["foo", "x41", "?!", "***"]
[x for x in seq if x.isalnum()]#结果为:['foo', 'x41']

filter(lambda x: x.isalnum(), seq)

Python提供了一种名为lambda表达式的功能,让你能够创建内嵌的简单函数(主要供map、filter和reduce使用)

如果你要将序列中的所有数相加,可结合使用reduce和lambda x, y: x+y。
等价于内置函数sum,当然,下面这个案例确实还不如sum函数方便。

from functools import reduce
numbers = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
reduce(lambda x, y: x + y, numbers)#结果为:1161

本章节介绍的新函数

函数 描述
map(func, seq[, seq, …]) 对序列中的所有元素执行函数
filter(func, seq) 返回一个列表,其中包含对其执行函数时结果为真的所有元素
reduce(func, seq[, initial]) 等价于 func(func(func(seq[0], seq[1]), seq[2]), …)
sum(seq) 返回 seq 中所有元素的和
apply(func[, args[, kwargs]]) 调用函数(还提供要传递给函数的参数)