Python学习笔记06----抽象

抽象和结构

 

抽象相当于一个函数,让程序去调用函数,当需多次使用一段程序时,可减少编写程序的工作量

自定义函数

函数执行特定的操作并返回一个值,可以调用它(调用时可能需提供一些参数--放在圆括号中的内容);要判断某个对象是否可调用,可使用内置函数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这样存储其所在作用域的函数成为闭包

递归

经典案例:阶乘和幂

经典案例:二分查找

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值