# coding:utf-8

#抽象就是你的代码调用函数,看上去比较抽象易懂,很多细节代码步骤在函数中,只要知道它干嘛就行直接调用。


#----------------------------------------------  创建函数  ------------------------------------------


#callable

#callable函数可以用来判断函数是否可以调用

import math

x = 1

y = math.sqrt

print callable(x)

#False

print callable(y)

#True


#创建函数用def语句即可

def hello(name):            #name函数返回值,函数可以返回一个以上的值,元组中返回即可。

   return 'Hello, ' + name + '!'

#可以像内建函数一样使用它

print hello('word')    #调用函数

#Hello, word!

print hello('Gumby')

#Hello, Gumby!


#文档化函数

#给函数写上文档注释

def square(x):

   'Calculates the square of the number x.'                #函数的文档注释

   return x*x

print square.__doc__      #查看函数的文档注释

#Calculates the square of the number x.

help(square)               #help函数也可以查看文档注释

#Help on function square in module __main__:

#square(x)

#    Calculates the square of the number x.


#没有返回值的函数

def test():

   print 'This is printed'

   return                  #这里return只是起到结束函数作用,没有返回值

   print 'This is not'

x = test()          #运行了第一个print,可以看到第二个print被跳过,retrun类似break,跳出函数

#This is printed

print x             #但没有返回值,所以是None

#None


#--------------------------------------- 参数魔法  ----------------------------------------


#写在def语句中函数名后面的变量通常叫做函数的形参,而调用函数的时候提供的值是实参,或者称为参数。


#我能改变参数吗

#参数只是变量而已,在函数内为参数赋予新值不会改变外部任何变量的值

def try_to_change(n):

   n = 'Mr. Gumby'             #由于变量是函数内部变量

name = 'Mrs. Entity'

try_to_change(name)               #当外部变量改变参数时,就不会影响

print name

#Mrs. Entity

#类似下面这样:

name = 'Mrs. Entity'

n = name        #这句的作用基本上等于传参数

n = 'Mr. Gumby'     #在函数内部完成的

print name

#Mrs. Entity

#注意,参数存储在局部作用域内。


#列表参数可以修改

#当两个变量同时引用一个列表的时候,它们的确是同时引用一个列表。

def change(n):

   n[0] = 'Mr. Gumby'

names = ['Mrs. Entity', 'Mrs. Thing']

change(names)           #参数被改变

print names

#['Mr. Gumby', 'Mrs. Thing']

#不用函数再做一次:

names = ['Mrs. Entity', 'Mrs. Thing']

n = names          #再来一次,模拟传参数行为

n[0] = 'Mr. Gumby'

print names

#['Mr. Gumby', 'Mrs. Thing']

#为了防止上面列表参数被修改,可以用副本方式

names = ['Mrs. Entity', 'Mrs. Thing']

n = names[:]            #现在n和names包含两个独立的列表,其值相等

print n is names       #判断是否应用同一个列表

#False

print n == names        #判断值是否相同

#True

#现在改变n,则不会影响到names

n[0] = 'Mr. Gumby'

print n

#['Mr. Gumby', 'Mrs. Thing']

print names

#['Mrs. Entity', 'Mrs. Thing']

#再用change试一下

def change(n):

   n[0] = 'Mr. Gumby'

names = ['Mrs. Entity', 'Mrs. Thing']

change(names[:])

print names

#['Mrs. Entity', 'Mrs. Thing']


#为什么改变参数

#使用函数改变数据结构是一种程序抽象化的好方法。假设需要编写一个存储名字并且能用名字,中间名或姓查找联系人的程序。

#没有用函数方法的复杂臃肿写法:

#初始化数据结构:

storage = {}

storage['first'] = {}

storage['middle'] = {}

storage['last'] = {}

print storage

#{'middle': {}, 'last': {}, 'first': {}}

#   键      值    键    值     键    值

#把我的名字加入数据结构:

me = 'Magus Lie Hetland'

storage['first']['Magus'] = [me]

storage['middle']['Lie'] = [me]

storage['last']['Hetland'] = [me]

print storage

#{'middle': {'Lie': ['Magus Lie Hetland']}, 'last': {'Hetland': ['Magus Lie Hetland']}, 'first': {'Magus': ['Magus Lie Hetland']}}

#    键   : {           值               }

#           { 键  :        值            }

#每一个键下面都存储了我的名字,那么查找中间名为Lie的人名:

print storage['middle']['Lie']

#                  键       键      意思就是显示middle键里面值里有Lie键里的值

#['Magus Lie Hetland']

#查找我的名字的人名:

print storage['first']['Magus']

#['Magus Lie Hetland']

#查找我的姓的人名:

print storage['last']['Hetland']

#['Magus Lie Hetland']

#如果要加其他人名,就会很枯燥麻烦,尤其是要加入有相同姓名的人:

my_sister = 'Anne Lie Hetland'

storage['first'].setdefault('Anne',[]).append(my_sister)

storage['middle'].setdefault('Lie',[]).append(my_sister)    #setdefault意思是如果存在Lie键不添加,如果不存在添加Lie键

storage['last'].setdefault('Hetland',[]).append(my_sister)

print storage

#{'middle': {'Lie': ['Magus Lie Hetland', 'Anne Lie Hetland']}, 'last': {'Hetland': ['Magus Lie Hetland', 'Anne Lie Hetland']}, 'first': {'Magus': ['Magus Lie Hetland'], 'Anne': ['Anne Lie Hetland']}}

print storage['first']['Anne']

#['Anne Lie Hetland']

print storage['middle']['Lie']

#['Magus Lie Hetland', 'Anne Lie Hetland']


#使用函数方法(推荐):

#第一部分函数,初始化数据结构函数

def init(data):

   data['first'] = {}

   data['middle'] = {}

   data['last'] = {}

storage = {}

init(storage)

print storage

#{'middle': {}, 'last': {}, 'first': {}}

#字典的键没有特定顺序,所以当字典打印出来的时候,顺序是不同的,这是正常情况。

#在编写存储名字的函数前,先写个获得名字的函数:

#存储名字暂时用之前的方法,配合获得名字函数进行测试

me = 'Magus Lie Hetland'

storage['first']['Magus'] = [me]

storage['middle']['Lie'] = [me]

storage['last']['Hetland'] = [me]

#第二部分函数,获得名字函数

def lookup(data, label, name):

   return data[label].get(name)

print lookup(storage, 'middle', 'Lie')

#['Magus Lie Hetland']

#第三部分函数,增加名字store函数

def store(data, full_name):                             #使用参数data和full_name进入函数,这两个参数被设置为函数在外部获得的一些值。

   names = full_name.split()                           #通过拆分full_name,得到一个叫做names的列表。

   if len(names) == 2: names.insert(1, '')             #如果names的长度为2(只有首名和末名),那么插入一个空字符串作为中间名。

   labels = 'first', 'middle', 'last'               #创建数据标签

   for label, name in zip(labels, names):              #使用zip函数联合标签和名字,对于每一个(lable,name)对,进行一下处理:

       people = lookup(data, label, name)               #获得属于给定标签和名字的列表

       if people:                                       #标签加名字如果有全名

           people.append(full_name)                      #将full_name添加到列表中

       else:                                            #否则

           data[label][name] = [full_name]               #插入一个需要的新列表。

#调用函数:

#创建空数据

MyNames = {}

#调用初始化数据函数

init(MyNames)

#调用store函数,增加名字

store(MyNames, 'tom tao')

#调用获取名字函数

print lookup(MyNames, 'first', 'tom')

#['tom tao']

#再试试

store(MyNames, 'Robin Hood')

store(MyNames, 'Robin Locksley')

print lookup(MyNames, 'first', 'Robin')

#['Robin Hood', 'Robin Locksley']

store(MyNames, 'Mr. Gumby')

print lookup(MyNames, 'middle', '')

#['tom tao', 'Robin Hood', 'Robin Locksley', 'Mr. Gumby']


#如果我的参数不可变

#数值增1的函数

def inc(x): return x + 1

foo = 10

foo = inc(foo)

print foo

#11

#如果真想改变参数:

#将值放进列表:

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

foo = [10]

inc(foo)

print foo

#[11]


#关键字参数和默认值

#参数顺序

def hello_1(greeting, name):

   print '%s, %s!' % (greeting, name)

def hello_2(name, greeing):

   print '%s, %s!' % (name, greeing)

#两个代码所实现的是完全一样的功能,只是参数顺序反过来了

hello_1('hello', 'world')

#hello, world!

hello_2('hello', 'world')

#hello, world!

#参数很多的时候,顺序很难记住,可以通过参数名字:

hello_1(greeting='Hello', name='world')       #这样一来顺序就没影响,但参数名和值一定要对应

#hello, world!

hello_1(name='world', greeting='Hello')

#hello, world!

hello_2(greeing='Hello', name='world')          #但参数名和值一定要对应

#world, Hello!

#这类使用参数名提供的参数叫做关键字参数。它的主要作用在于可以明确每个参数的作用。

#也就避免了下面这样的奇怪的函数调用:

#store('Mr. Brainsample', 10, 20, 13, 5)

#可以使用:

#store(patient='Mr. Brainsample', hour=10, minute=20, day=13, month=5)

#尽管这么做打的字多了些,但很显然,每个参数的含义变得更加清晰。而且就算弄乱了参数的顺序,对于程序的功能也没有任何影响。

#关键字参数最厉害的地方在于可以在函数中给参数提供默认值:

def hello_3(greeting='Hello', name='world'):

   print '%s, %s!' % (greeting, name)

#当参数具有默认值,调用就不用提供参数

hello_3()

#Hello, world!

hello_3('Greetings')

#Greetings, world!

hello_3('Greetings', 'universe')

#Greetings, universe!

hello_3(name='Gumby')

#Hello, Gumby!

#例如,hello函数可能需要名字作为参数,但是也允许用户自定义名字,问候语和标点:

def hello_4(name, greeting='Hello', punctuation='!'):

   print '%s, %s%s' % (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() #报错异常,如果为name也赋予默认值,那么不会报错


#收集参数

#参数用户每次只能存一个名字,如果能像下面这样存储多个名字就更好:

#store(data,name1,name2,name3)

#试着像下面这样定义函数:

def print_params(*params):

   print params

print_params('Testing')         #元组,有逗号,长度为1的元组

#('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)

print_params_2('Nothing:')          #如果不提供任何收集的元素,params就是个空元组。

#Nothing:

#()

#print_params_2('Hmm...', someting=42)       #关键字参数不行,会报错

#我们需要另外一个能处理关键字参数的“收集”操作:

def print_params_3(**params):

   print params

print_params_3(x=1, y=2, z=3)               #返回的是字典而不是元组。

#{'y': 2, 'x': 1, '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 init(data):

   data['first'] = {}

   data['middle'] = {}

   data['last'] = {}

def lookup(data, label, name):

   return data[label].get(name)

def store(data, *full_names):

   for full_name in full_names:

       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]

d = {}

init(d)

store(d, 'Luke Skywalker', 'Anakin Skywalker')

print lookup(d, 'last', 'Skywalker')

#['Luke Skywalker', 'Anakin Skywalker']


#参数收集的逆过程

#事实上,如果使用*和**的话,也可以执行相反的操作。那么参数收集的逆过程是什么样的

#加入有下列函数

def add(x, y):

   return x + y

#比如说有两个相加的数字组成元组

params = (1, 2)

#不是要收集参数,而是分配它们在“另一端”。使用*运算符就简单了:

print add(*params)

#3

#可以使用同样的技术来处理字典---使用双星号运算符。假设之前定义了hello_3,那么可以这样使用:

def hello_3(greeting='Hello', name='world'):

   print '%s, %s!' % (greeting, name)

params = {'name': 'Sir Robin', 'greeting': 'Well met'}

hello_3(**params)

#Well met, Sir Robin!

def with_starts(**kwds):

   print kwds['name'], 'is', kwds['age'], 'years old'

def without_starts(kwds):

   print kwds['name'], 'is', kwds['age'], 'years old'

args = {'name': 'Mr. Gumby', 'age': 42}

with_starts(**args)                    #函数用了双星号,所以调用必须加双星号

#Mr. Gumby is 42 years old

without_starts(args)                    #没有星号,可以不用加星号

#Mr. Gumby is 42 years old

#with_starts(args)                      #这样会报错

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)

call_foo(1, 2, 3, 4, 5)


#练习使用参数

#第一部分示例函数

def story(**kwds):

   return 'Once upon a time, there was a %(job)s called %(name)s.' % kwds

#测试

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.

#第二部分示例函数

def power(x, y, *others):

   if others:

       print 'Received redundant parameters:', others

   return pow(x, y)               #pow函数是计算x的y次方

#测试

print power(2, 3)

#8

print power(3, 2)

#9

params = (5,) * 2

print power(*params)

#3125

print power(3, 3, 'Hello, world')

#Received redundant parameters: ('Hello, world',)

#27

#第三部分示例函数

def interval(start, stop=None, step=1):

   'Imitates rang() for step > 0'

   if stop is None:              #如果没有为stop提供值

       start, stop = 0, start    #简洁写法,同等于stop = start  start = 0 (同时进行)  指定参数

   result = []

   i = start                       #计算start索引

   while i < stop:                #直到计算到stop的索引

       result.append(i)            #将索引添加到result内

       i += step                   #用step(>0)增加索引i

   return result

#测试

print interval(10)

#[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

print interval(1,5)

#[1, 2, 3, 4]

print interval(3,12,4)

#[3, 7, 11]

print power(*interval(3,7))

#Received redundant parameters: (5, 6)

#81


#------------------------------------------  作用域  ----------------------------------------------------


#到底什么是变量?你可以把它们看作是值的名字。在执行x=1赋值语句后,名称x引用到值1。这就像字典一样,键引用值,当然,变量和所对应的值用的是个“不可见”的字典。

#内建的vars函数可以返回这个字典:

x = 1

scope = vars()

print scope['x']

#1

scope['x'] += 1

print x

#2

#这类“不可见字典”叫做命名空间或者作用域。那么到底有多少个命名空间?除了全局作用域外,每个函数调用都会创建一个新的作用域:

def foo():

   x = 42

x = 1

foo()

print x         #调用的是全局作用域的x(1),不是foo函数的局部作用域的x(42)

#1

#参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。

def output(x):

   print x

x = 1

y = 2

output(y)

#2

#函数内部访问全局变量(慎重使用)

def combine(parameter):

   print parameter + external

external = 'berry'

combine('Shrub')

#Shrubberry

#如果局部变量或者参数的名字和想要访问的全局变量名相同的话,就不能直接访问了。全局变量会被局部变量屏蔽。

#如果的确需要的话,可以使用globals函数获取全局变量值,它可以返回全局变量的字典(locals返回局部变量的字典)

def combine(parameter):

   print parameter + globals()['parameter']

parameter = 'berry'

combine('Shrub')

#Shrubberry

#重绑定全局变量(使变量引用其他新值)。如果在函数内部将值赋予一个变量,它会自动成为局部变量。除非告知python将其变为全局变量(global)。

x = 1

def chang_global():

   global x

   x = x + 1

chang_global()

print x

#2


#嵌套作用域

#Python的函数是可以嵌套的,也就是说可以将一个函数放在另一个里面。例如:

def foo():

   def bar():

       print "Hello, world!"

   bar()

foo()

#Hello, world!

def multiplier(factor):

   def multiplyByFactor(number):

       print factor

       print number

       return number*factor

   return multiplyByFactor

double = multiplier(2)      #这个参数传入multiplyByFactor里面的factor变量

print double(5)             #这样是调用multiplyByFactor(number)

#2

#5

#10

triple = multiplier(3)

print triple(5)

#3

#5

#15

print multiplier(5)(4)      #第一个参数是factor,第二个参数时number

#5

#4

#20


#--------------------------------------------  递归  ----------------------------------------------


#递归就是函数调用自身


#经典例子

#循环方式

def factorial(n):

   result = n

   for i in range(1,n):

       print '%s * %s' % (result, i)

       result *= i

   return result

print factorial(5)

#5 * 1

#5 * 2

#10 * 3

#30 * 4

#120

#递归,调用自身

def factorial(n):

   if n == 1:

       return 1

   else:

       return n * factorial(n-1)

print factorial(5)

#120

#递归更加易读,尽管可以避免使用递归,但作为程序猿来说要理解递归算法和看懂其他人写的递归程序。