第六章 抽象(函数)
6.1 懒惰是一种美德
6.2 抽象和结构
6.3 自定义函数
判断某个对象是否可调用,可使用内置函数callable。格式是callable(对象)。
函数是结构化编程的核心。使用def(表示定义函数)语句。
'''
def fun(str_s): #定义函数
return str_s #函数返回内容
f=fun("你好") #调用函数
print(f)
'''
你好
------------------
(program exited with code: 0)
请按任意键继续. . .
6.3.1 给函数编写文档要给函数编写文档,以确保其他人能够理解,可添加注释(以#打头的内容)。还有另一种编写注释的方式,就是添加独立的字符串。在有些地方,如def语句后面(以及模块和类的开头),添加这样的字符串很有用。放在函数开头的字符串称为文档字符串(docstring),将作为函数的一部分存储起来。
'''
def fun(a):
"这是一个简单的输入字符的函数"
return a*a
helps=fun.__doc__
print(helps)
'''
这是一个简单的输入字符的函数
------------------
(program exited with code: 0)
请按任意键继续. . .
也可以用三重双(单)引号定义函数文档。
'''
def fun_(str_s):
'''
"这是一个简单的函数"
'''
return str_s
fu=fun_("你好吗?")
fuc=fun_.__doc__
print (fu,"\n",fuc)
'''
这是一个简单的输入字符的函数
你好吗?
"这是一个简单的函数"
------------------
(program exited with code: 0)
请按任意键继续. . .
特殊的内置函数help很有用。在交互式解释器中,可使用它获取有关函数的信息,其中包含函数的文档字符串。
'''
print(help(fun_))
'''
'more' 不是内部或外部命令,也不是可运行的程序
或批处理文件。
------------------
(program exited with code: 0)
请按任意键继续. . .
注:如果出现这种情况,把C:/windows/system32添加到系统环境变量的path中。
6.3.2 其实函数并不是数学意义上的函数
数学意义上的函数始终有确定的返回值,而这里的函数即使有return语句也可能相当于break只是结束函数,而break是结束循环而已。
'''
def function_():
print("你好")
return
print("很好!")
function_()
'''
你好
None
------------------
(program exited with code: 0)
请按任意键继续. . .
这是一个熟悉的值:None。由此可知,所有的函数都返回值。如果你没有告诉它们该返回什么,将返回None。
6.4 参数魔法
6.4.1 值从哪里来
编写函数旨在为当前程序(甚至其他程序)提供服务,确保它在提供的参数正确时完成任务,并在参数不对时以显而易见的方式失败。
6.4.2 参数能修改吗?
定义函数是的参数名和要传递的参数名是两码事。定义的函数参数叫形参,要传递的参数叫实参。实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。在函数内部重新关联参数(即给它赋值)时,函数外部的变量不受影响。
6.4.3 关键字参数和默认值
没有默认值的参数是位置参数。
'''
def f1(n,m):
print("{},{}".format(n,m))
def f2(m,n):
print("{},{}".format(m,n))
print(f1("你","好"))
print(f2("你","好"))
'''
你,好
None
你,好
None
------------------
(program exited with code: 0)
请按任意键继续. . .
参数的排列顺序可能难以记住,尤其是参数很多时。为了简化调用工作,可指定参数的名称。
'''
def score(name="王五",va=80):
print("{}的成绩是:{}分".format(name,va))
print(score())
'''
王五的成绩是:80分
None
------------------
(program exited with code: 0)
请按任意键继续. . .
其中王五是name的默认值,80是va的默认值,而这种由默认值的参数。
'''
print(score("李四"))
'''
李四的成绩是:80分
None
------------------
(program exited with code: 0)
请按任意键继续. . .
'''
print(score("李四",90))
'''
李四的成绩是:90分
None
------------------
(program exited with code: 0)
请按任意键继续. . .
'''
print(score(va=90))
'''
王五的成绩是:90分
None
------------------
(program exited with code: 0)
请按任意键继续. . .
注意:如果python的函数可以用=直接给参数赋值。如果第一个关键字参数不赋值在python中用,号区隔会出现错误,只能用=号直接赋值。
6.4.4 收集参数
在形参前加*号可以允许输入任意多个实参。
'''
def number(*n):
print(n)
number(1,2,3,4,5,6)
'''
(1, 2, 3, 4, 5, 6)
------------------
(program exited with code: 0)
请按任意键继续. . .
参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来。这样的行为我们在5.2.1节见过:赋值时带星号的变量收集多余的值。它收集的是列表而不是元组中多余的值,但除此之外,这两种用法很像。
'''
def dic(**d):
return d
print(dic())
'''
{}
------------------
(program exited with code: 0)
请按任意键继续. . .
参数前面的双星号将提供的所有值都放在一个字典中,这种函数的实参要求同时传入参数名称和值。
'''
print(dic(name="许九",age=50))
'''
{'name': '许九', 'age': 50}
------------------
(program exited with code: 0)
请按任意键继续. . .
这种参数叫关键字参数,(有些书籍把带有默认值参数的位置参数也叫做关键字参数)。
6.4.5 分配参数
在调用函数时的实参前面加*号。
'''
def sum(x,y):
return (x+y)
s=[1,4]
print(sum(*s))
'''
5
------------------
(program exited with code: 0)
请按任意键继续. . .
'''
d={"ID":2018,"name":"张三","age":60}
print(dic(**d))
'''
{'ID': 2018, 'name': '张三', 'age': 60}
------------------
(program exited with code: 0)
请按任意键继续. . .
在python里还有一种参数,限制关键字参数,它要求实参不但要有名称和值而且关键字是指定的。
'''
def f(*,name,age):
return name+"的年龄是"+str(age)+"岁。"
print(f(name="王五",age=25))
'''
王五的年龄是25岁。
------------------
(program exited with code: 0)
请按任意键继续. . .
PEP 8(https://www.python.org/dev/peps/pep-0008/)建议代码行的长度不要超过79字符,这样只要编辑器窗口适中,就能看到整行代码。如果形参很多,导致函数定义的长度超过了79字符,可在函数定义中输入左括号后按回车键,并在下一行按两次Tab键,从而将形参列表和只缩进一层的函数体区分开来。
'''
def fnn(
numb1,numb2,
numb3,numb4,
numb5,numb6):
"""
这是一个空函数,用于测试多参数函数的书写方法,是否正确。
"""
pass
fnn(1,2,3,4,5,6)
help(fnn)
'''
Help on function fnn in module __main__:
fnn(numb1, numb2, numb3, numb4, numb5, numb6)
这是一个空函数,用于测试多参数函数的书写方法,是否正确。
------------------
(program exited with code: 0)
请按任意键继续. . .
6.5 作用域
变量的有效范围就是变量的作用域。在python中分为全局作用域和函数作用域。在函数外部定义的变量的作用域较全局作用域;在函数内部定义的变量叫函数作用域,又叫局部作用域。python中没有块作用域。
'''
v=5
if v!=5: #全局变量
a=2
pass
def fn():
fnv=3 #函数变量
print(v,fnv)
print(locals())
print(v)
fn()
print(locals())
'''
5
5 3
{'fnv': 3}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_f
rozen_importlib_external.SourceFileLoader object at 0x0000000001D6DA58>, '__spec
__': None, '__annotations__': {}, '__builtins__':
, '__file__': 'xx.py', '__cached__': None, 'v': 5, 'fn': }
------------------
(program exited with code: 0)
请按任意键继续. . .
由此可以看出,fn中的变量只有一个fnv,在全局中的全局变量是v。而locals所指示的是变量的存储空间,又叫命名空间,它是一个字典。作用域和命名空间有着密切关系,但由此可以看出,命名空间和作用域是有区别的,作用域是指的有效范围,而命名空间是指地所在的字典(空间),一个对象的作用域只有一个,而命名空间不是,对于变量,每调用一次变量所在的函数一次,就会产生一个命名空间,函数结束,这个空间瞬即消亡。
读取全局变量的值通常不会有问题,但还是存在出现问题的可能性。如果有一个局部变量或参数与你要访问的全局变量同名,就无法直接访问全局变量,因为它被局部变量遮住了。
如果需要,可使用函数globals来访问全局变量。这个函数类似于vars,返回一个包含全局变量的字典。
'''
w=10
def fn1():
w=7
print("函数外部变量w",globals()['w'])
print("函数内部变量w",w)
fn1()
'''
函数外部变量w 10
函数内部变量w 7
------------------
(program exited with code: 0)
请按任意键继续. . .
在函数中为全局变量赋值,用global明示该变量为全局变量。
'''
w=10
def fn1():
global w
w=7
print("函数外部变量w",globals()['w'])
print("函数内部变量w",w)
fn1()
'''
函数外部变量w 7
函数内部变量w 7
------------------
(program exited with code: 0)
请按任意键继续. . .
另外,还有一个关键字nonlocal用它可以在内部函数内给外部函数的变量赋值。正像用global给全局变量赋值那样。
6.6 递归
递归函数就是函数调用自身并返回。
6.6.1 两个经典案例:阶乘和幂
阶乘:n*(n-1)*(n-2)...*2*1
'''
def fact(n): #n的阶乘
if n==1: #如果n=1
return 1 #则返回1
else: #否则
return n*fact(n-1) #n*(n-1)
print(fact(10))
'''
3628800
------------------
(program exited with code: 0)
请按任意键继续. . .
幂:x^y即y个x相乘。
'''
def powe(x,y): #y个x相乘
if y==0: #如果y等于零
return 1 #就返回一,
else: #否则
return x*powe(x,y-1) #x乘于(y-1)个x
p=powe(2,3)
print(p)
'''
8
------------------
(program exited with code: 0)
请按任意键继续. . .
6.6.2 另一个经典案例:二分查找
如果上限和下限相同,就说明它们都指向数字所在的位置,因此将这个数字返回。
否则,找出区间的中间位置(上限和下限的平均值),再确定数字在左半部分还是右半部分。然后在继续在数字所在的那部分中查找。
def search(sequence, number, lower, upper): #sequence序列、number数、lower下限、upper上限。
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) #就把中间位置作为上限查找。
为方便调用,还可将上限和下限设置为可选的。为此,只需给参数lower和upper指定默认值,并在函数开头添加如下条件语句:
def search(sequence, number, lower=0, upper=None):
if upper is None: upper = len(sequence) - 1
'''
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()
print(seq)
sea=search(seq,34)
print(sea)
'''
[4, 8, 34, 67, 95, 100, 123]
2
------------------
(program exited with code: 0)
请按任意键继续. . .
实际上,模块bisect提供了标准的二分查找实现,可以参阅。
附:函数式编程——介绍几个函数
1、map
map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
格式为:map(function, iterable, ...)Python 2.x 返回列表;Python 3.x 返回迭代器。
其中function 为函数,iterable为一个或多个序列
'''
x=[1,2,3,4,5]
y=[9,8,7,6,5]
def lis(x,y):
return x+y
m1=map(lis,x,y)
print(list(m1))
'''
[10, 10, 10, 10, 10]
------------------
(program exited with code: 0)
请按任意键继续. . .
也可以这样写:
'''
m2=map(lambda i,j:i+j ,x,y)
print(list(m2))
'''
[10, 10, 10, 10, 10]
------------------
(program exited with code: 0)
请按任意键继续. . .
其中lambda是用来定义匿名函数的表达式。格式为:lambda 参数1,参数2...:函数体
2、filter
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。
接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
格式为filter(function, iterable)
其中function为判断函数,iterable为可迭代对象。
'''
nums=[2,"d","&&&","a3","..."]
fi=filter(lambda x:str(x).isalnum(),nums)
print(list(fi))
'''
[2, 'd', 'a3']
------------------
(program exited with code: 0)
请按任意键继续. . .
其中isalnum() 方法检测字符串是否由字母和数字组成。如果字符串中至少有一个字符并且所有字符都是字母或数字则返回 True,否则返回 False
3、 reduce
reduce() 函数会对参数序列中元素进行累积。
格式为reduce(function, iterable[, initializer])
其中function为函数,有两个参数;iterable为可迭代对象;initializer为可选,初始参数。
其功能是将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
'''
arr=[1,2,3,4,5,6,7,8,9,10] #这里不能用arr=range(10),不然结果为零。
from functools import reduce
ss=reduce(lambda x,y:x*y,arr) #10的阶乘
print(ss)
'''
3628800
------------------
(program exited with code: 0)
请按任意键继续. . .
(待续)