抽象和结构
抽象相当于一个函数,让程序去调用函数,当需多次使用一段程序时,可减少编写程序的工作量
自定义函数
函数执行特定的操作并返回一个值,可以调用它(调用时可能需提供一些参数--放在圆括号中的内容);要判断某个对象是否可调用,可使用内置函数callable
>>> import math
>>> x = 1
>>> y = math.sqrt
>>> callable(x)
False
>>> callable(y)
True
使用def(定义函数)
def hello(name):
return 'Hello, ' + name + '!'
#运行这些代码,可获得一个名为hello的新函数,返回一个字符串,其中包含向唯一参数指定的人发出的问候语
>>> print(hello('world'))
Hello, world!
>>> print(hello('Gumby'))
Hello, Gumby!
#计算斐波那契数(一种数列,其中每个数是前两个数的和,初始为[0,1])
def fibs(num):
result = [0, 1]
for i in range(num-2):
result.append(result[-2] + result[-1])
return result
#执行这些代码,就可以调用函数fibs
>>> fibs(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> fibs(15)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377]
#编写函数,num,result也可以使用其他名字,但returny语句非常重要。
#return语句用于从函数返回值(在前面的hello函数中,return语句的作用也是一样的)
给函数编写文档
给函数编写文档,方便其他人能够理解,可以添加注释以#打头的内容;还可以在函数开头添加独立的字符串;
放在函数开头的字符串成为文档字符串,将作为函数的一部分存储起来。
def square(x):
'Calculates the square of the number x.'
return x * x
#可以像下面这样访问文档字符串
>>> square.__doc__
'Calculates the square of the number x.'
#_doc_是函数的一个属性,属性名中的双下划线表示这是一个特殊的属性
#特殊的内置函数help,在交互式解释器中,可以用它获取有关函数的信息,其中包含函数的文档字符串
>>> help(square)
Help on function square in module __main__:
square(x)
Calculates the square of the number x.
其实并不是函数的函数
什么都不返回的函数,也叫过程,不包含return语句,或者包含return语句,但没有在return后面指定值;
def test():
print('This is printed')
return
print('This is not')
#这里的return语句只是为了结束函数
#跳过了第二条print()语句,像在循环中使用break,跳出的是函数
>>> x = test()
This is printed
>>> x
>>>
>>> print(x)
None
#所有的函数都返回值。如果你没有告诉它们该返回什么,将返回None。
参数魔法
值从哪里来
在def语句中,位于函数名后面的变量通常称为形参,而调用函数时提供的值称为实参
我能修改参数吗
在函数内部给参数赋值对外部没有任何影响;
在函数内部重新关联参数(即给它赋值)时,函数外部的变量不受影响;
参数存储在局部作用域内;
>>> def try_to_change(n):
... n = 'Mr. Gumby'
...
>>> name = 'Mrs. Entity'
>>> try_to_change(name)
>>> name
'Mrs. Entity'
#在函数内新值赋给了参数n,但对变量name没有影响
>>> name = 'Mrs. Entity'
>>> n = name #与传递参数的效果几乎相同
>>> n = 'Mr. Gumby' #这是在函数内进行的
>>> name
'Mrs. Entity'
字符串(以及数和元组)是不可变的,意味着不能修改,只能替换为新值;
参数为可变的数据结构(如列表)
>>> def change(n):
... n[0] = 'Mr. Gumby'
...
>>> names = ['Mrs. Entity', 'Mrs. Thing']
>>> change(names)
>>> names
['Mr. Gumby', 'Mrs. Thing']
#虽然也只是在局部变量赋值,但是修改了变量关联到的列表
>>> names = ['Mrs. Entity', 'Mrs. Thing']
>>> n = names #再次假装传递名字作为参数
>>> n[0] = 'Mr. Gumby' #修改列表
>>> names
['Mr. Gumby', 'Mrs. Thing']
#这类似于将同一个列表赋给两个变量时,这两个变量将同时指向这个列表。
#要避免这样的结果,必须创建列表的副本;对序列执行切片操作时,返回的切片都是副本
#若创建覆盖整个列表的切片,得到的是列表的副本
>>> names = ['Mrs. Entity', 'Mrs. Thing']
>>> n = names[:]
#n指向的是names的副本列表,并非names本身
#即n和names包含两个相等但不同的列表
>>> n is names
False
>>> n == names
True
#现在在函数内部修改n,将不会影响names
>>> n[0] = 'Mr. Gumby'
>>> n
['Mr. Gumby', 'Mrs. Thing']
>>> names
['Mrs. Entity', 'Mrs. Thing']
#>>> change(names[:])
>>> names
['Mrs. Entity', 'Mrs. Thing']
#由于参数n包含的是副本,因此原始列表是安全的
#函数内的局部名称(包括参数)和函数外的名称(全局名称)不冲突
1.为何要修改参数
例子:
#创建一个初始化数据结构的函数
def init(data):
data['first'] = {}
data['middle'] = {}
data['last'] = {}
#使用函数,承担了初始化职责
>>> storage = {}
>>> init(storage)
>>> storage
{'middle': {}, 'last': {}, 'first': {}}
#编写获取人员姓名的函数
#lookup接受参数label和name,并返回一个由全名组成的列表
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]
#函数store执行如下步骤。
(1) 将参数data和full_name提供给这个函数。这些参数被设置为从外部获得的值。
(2) 通过拆分full_name创建一个名为names的列表。
(3) 如果names的长度为2(只有名字和姓),就将中间名设置为空字符串。
(4) 将'first'、'middle'和'last'存储在元组labels中(也可使用列表,这里使用元组只是为
了省略方括号)。
(5) 使用函数zip将标签和对应的名字合并,以便对每个标签名字对执行如下操作:
获取属于该标签和名字的列表;
将full_name附加到该列表末尾或插入一个新列表。
2.如果参数是不可变的
在Python中,不能实现给参数赋值并让这种修改影响函数外部的变量,只能修改参数对象本身;
在这种情况下,应从函数返回所有需要的值(如果需要返回多个值,就以元组的方式返回它们);
>>> def inc(x): return x + 1
>>> foo = 10
>>> foo = inc(foo)
>>> foo
11
#如果一定要修改参数,可以将值放在列表中
>>> def inc(x): x[0] = x[0] + 1
>>> foo = [10]
>>> inc(foo)
>>> foo
[11]
#但更清晰的解决方案是返回修改后的值
关键字和参数
有时参数的排列顺序很难记住,为了简化调用工作,可指定参数的名称。
def hello_1(greeting, name):
print('{}, {}!'.format(greeting, name))
>>> hello_1(greeting='Hello', name='world')
Hello, world!
>>> hello_1(name='world', greeting='Hello')
Hello, world!
#在这里,参数的顺序无关紧要
#名称很重要了
def hello_2(name, greeting):
print('{}, {}!'.format(name, greeting))
>>> hello_2(greeting='Hello', name='world')
world, Hello!
这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用
#函数调用不再神秘
>>> store('Mr. Brainsample', 10, 20, 13, 5)
#每个参数的作用清晰明了。另外,参数的顺序错了也没关系
>>> store(patient='Mr. Brainsample', hour=10, minute=20, day=13, month=5)
#关键字最大的优点---指定默认值,如下面的greetig,name
def hello_3(greeting='Hello', name='world'):
print('{}, {}!'.format(greeting, name))
#这样给参数指定默认值后,调用函数时可不提供它,可以根据需要,一个参数值也不提供、提供部分参数值或提供全部参数值
>>> hello_3()
Hello, world!
>>> hello_3('Greetings')
Greetings, world!
>>> hello_3('Greetings', 'universe')
Greetings, universe!
#仅使用位置参数就可,不需要提供参数name,必须同时提供参申诉greeting
#如果只想提供参数name,并让参数greeting使用默认值呢
>>>hello_3(name='Gumby')
hello,Gumby!
#可结合使用位置参数和关键字参数,但必须先指定所有的位置参数,否则解释器不知道参数对应的位置
#例如:函数hello可能要求必须指定姓名,而问候语和标点是可选的
def hello_4(name, greeting='Hello', punctuation='!'):
print('{}, {}{}'.format(greeting, name, punctuation))
#调用函数例子
>>> hello_4('Mars')
Hello, Mars!
>>> hello_4('Mars', 'Howdy')
Howdy, Mars!
>>> hello_4('Mars', 'Howdy', '...')
Howdy, Mars...
>>> hello_4('Mars', punctuation='.')
Hello, Mars.
>>> hello_4('Mars', greeting='Top of the morning to ya')
Top of the morning to ya, Mars!
#必须指定姓名
>>> hello_4()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: hello_4() missing 1 required positional arg
收集参数
有时候,允许用户提供任意数量的参数很有用;
def print_params(*params):
print(params)
>>> print_params('Testing')
('Testing',)
#打印的是一个元组,因为里面有一个星号
#打印的是一个元组
>>> print_params(1, 2, 3)
(1, 2, 3)
#参数前面的星号将提供的所有值都放在一个元组中,也就是将这些值收集起来
#星号意味着收集余下的位置参数
def print_params_2(title, *params):
print(title)
print(params)
>>> print_params_2('Params:', 1, 2, 3)
Params:
(1, 2, 3)
#如果没有可供收集的参数,params将是一个空元组
>>> 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)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: in_the_middle() missing 1 required keyword-only argument: 'z'
#星号不会收集关键字参数
>>> print_params_2('Hmm...', something=42)
Traceback (most recent call last):
TypeError: print_params_2() got an unexpected keyword argument 'something'
#要收集关键字参数,可使用两个星号,但得到的是一个字典而不是元组
>>> def print_params_3(**params):
... print(params)
...
>>> print_params_3(x=1, y=2, z=3)
{'z': 3, 'x': 1, 'y': 2}
#调用例子
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
()
{}
例子不懂【????P102】
分配参数
使用两个运算符(*和**)也可执行相反的操作,分配参数:通过调用函数(而不是定义函数)时使用运算符*实现的。
def add(x, y):
return x + y
>>> add(*params)
3
params = (1, 2)
>>> add(*params)
3
分配参数也可以用于参数列表的一部分,条件是这部分位于参数列表末尾;通过使用运算符**,可将字典中的值分配给关键字参数。
def hello_3(greeting='Hello', name='world'):
print('{}, {}!'.format(greeting, name))
>>> params = {'name': 'Sir Robin', 'greeting': 'Well met'}
>>> hello_3(**params)
Well met, Sir Robin!
#使用拆分运算符传递参数很有用,无需操心参数个数的问题【???P103】
def foo(x, y, z, m=0, n=0):
print(x, y, z, m, n)
def call_foo(*args, **kwds):
print("Calling foo!")
foo(*args, **kwds)
如果在定义和调用函数时,都使用*和**运算符,将只传递元组或字典;
只有在定义函数(允许可变数量的参数)或调用函数时(拆分字典或序列)使用,星号才能发挥作用;
#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': 'Mr. Gumby', 'age': 42}
>>> with_stars(**args)
Mr. Gumby is 42 years old
>>> without_stars(args)
Mr. Gumby is 42 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='king', name='Gumby'))
Once upon a time, there was a king called Gumby.
>>> print(story(name='Sir Robin', job='brave knight'))
Once upon a time, there was a brave knight called Sir Robin.
#定义函数时使用**,字典分配
>>> params = {'job': 'language', 'name': 'Python'}
>>> print(story(**params))
Once upon a time, there was a language called Python.
#删除了job这个键,但是调用时又定义了job这个关键字参数
>>> del params['job']
>>> print(story(job='stroke of genius', **params))
Once upon a time, there was a stroke of genius called Python.
>>> power(2, 3)
8
>>> power(3, 2)
9
>>> power(y=3, x=2)
8
>>> params = (5,) * 2
>>> params
(5,5)
>>> power(*params)
3125
>>> power(3, 3, 'Hello, world')
Received redundant parameters: ('Hello, world',)
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
作用域
vars的内置函数,返回看不见的字典,“看不见的字典”叫命名空间或作用域;
除全局作用域外,每个函数调用都将创建一个;
#函数作用的空间
>>> def foo(): x = 42
...
>>> x = 1
>>> foo()
>>> x
1
#函数foo修改(重新关联)了变量x,调用foo时创建了一个新的命名空间,供foo中的代码块使用
#赋值语句x = 42是在这个内部作用域(局部命名空间)中执行的,不影响外部(全局)作用域内的x
#在函数内部使用的变量称为局部变量,参数类似于局部变量,参数与全部变量同名不会有问题
在函数中访问全局变量,若局部变量或参数与你要访问的全局变量同名,就无法访问全局变量,会被局部变量覆盖;必要时,使用globals()['parameter']来访问
vars函数,返回字典;
要访问全局变量,可使用globals函数,返回一个包含全局变量的字典;
要访问局部变量,可使用locals函数,返回一个包含局部变量的字典;
#在下面例子中,parameter为全局变量,但是有一个与之同名的参数,故无法在函数combine中访问
>>> def combine(parameter): print(parameter + external)
...
>>> external = 'berry'
>>> combine('Shrub')
Shrubberry
#必要时,使用globals()['parameter']来访问
>>> def combine(parameter):
... print(parameter + globals()['parameter'])
...
>>> parameter = 'berry'
>>> combine('Shrub')
Shrubberry
在函数内部给变量赋值时,变量默认为局部变量;
若想重新关联全局变量(使其指向新值),如下:
>>> x = 1
>>> def change_global():
... global x #告诉python:X为全局变量
... x = x + 1
...
>>> change_global()
>>> x
2
作用域嵌套
python函数可以嵌套,即将一个函数放在另一个函数内;使用函数嵌套,可以创建一个新函数
def foo():
def bar():
print("Hello, world!")
bar()
例子:一个函数位于另一个函数中,且外面的函数返回里面的函数(返回函数,而不是调用它),返回的函数能够访问其定义所在的作用域(携带着自己所在的环境和相关的局部变量)
每当外部函数被调用,都将重新定义内部的函数,而变量factor的值也可能不同【P107????】
内部函数这样存储其所在作用域的函数成为闭包
#由于python的嵌套作用域,可在内部函数中访问来自外部局部作用域的变量
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
#像内部函数multiplyByFactor这样存储其所在作用域的函数成为闭包