首页 > 代码库 > python之函数

python之函数

函数的意义

函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

函数能提高应用的模块性,和代码的重复利用率。你已经知道Python提供了许多内置函数,比如print()。但你也可以自己创建函数,这被叫做用户自定义函数。

函数的定义

  • 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()
  • 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。

def fun(形参):    "内容"    return Truefun(实参)

函数参数

定义函数时,申明的是形式参数简称形参,调用函数时,传递的参数是实际参数简称实参。而形参又可以分为四种

  • 必备参数
  • 关键字参数
  • 默认参数
  • 不定长参数

必备参数

必备参数须以正确的顺序传入函数。调用时的数量必须和声明时的一样

def f1(a):    print("OK")f1()def f2(a,b)    print(a+b)f2(3+4)

返回结果:OK和7

注:调用必备参数时,必须实参和形参位置对应,否则会报错。

关键字参数 

关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。

使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

def info(name,age):    print("Name %s"%name)    print("Age %s"%age)  info("flash",18)info("aa",19)

输出结果

Name flashAge 18Name aaAge 19

默认参数  

调用函数时,缺省参数的值如果没有传入,则被认为是默认值。

def info(name,age,sex="male"):    print("Name %s"%name)    print("Age %s"%age)    print("Sex %s"%sex)info("flash",18)info("aa",19,sex="female")

输出结果对比

Name flashAge 18Sex maleName aaAge 19Sex female

不定长参数  

你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数(又称动态参数),和上述2种参数不同,声明时不会命名。一种是无名名字参数,另一种是有命名参数。

无命名参数(输出的是元组)def add(*args):    sum = 0    for i in args:        sum += i    print(sum)add(1,2,3,4,5)有命名参数(输出的是字典)def fun(**kwargs):    for i in kwargs:        print("%s:%s"%(i,kwargs[i]))fun(name="flash",age=22)

函数调用

定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。

这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从Python提示符执行。

最简单的例子

def fun():    print("OK")fun()

函数返回值 

return语句[表达式]退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。

  • 函数在执行过程中只要遇到return语句,就会可以停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
  • 如果未在函数中指定return,那这个函数的返回值为None  
  • return多个对象,解释器会把这多个对象组装成一个元组作为一个一个整体结果输出

函数作用域

一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称。

大致可以分为四种情况

  • L:local,局部作用域,即函数中定义的变量;
  • E:enclosing,嵌套的父级函数的局部作用域,即包含此函数的上级函数的局部作用域,但不是全局的;
  • G:globa,全局变量,就是模块级别定义的变量;
  • B:built-in,系统固定模块里面的变量,比如int, bytearray等。 搜索变量的优先级顺序依次是:作用域局部>外层作用域>当前模块中的全局>python
total = 0; # 这是一个全局变量# 可写函数说明def sum( arg1, arg2 ):   #返回2个参数的和."   total = arg1 + arg2; # total在这里是局部变量.   print "函数内是局部变量 : ", total   return total; #调用sum函数sum( 10, 20 );print "函数外是全局变量 : ", total 

函数递归

特性

  • 调用自身函数
  • 有一个结束条件

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出

举个简单例子,求5的阶乘

def f(i):    if i == 1:        return 1    return i * f(i-1)print(f(5))

小结

  • 使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。
  • 针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
  • Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

高阶函数

高阶函数英文叫Higher-order function。

变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

  • 函数名可以进行赋值
  • 函数名可以作为函数参数,还可以作为函数的返回值

举个例子如

def f(n):    return n *ndef f1(a,b,f):    set = f(a) + f(b)    return setprint(f1(1,2,f))
返回值为 5

闭包函数 

什么是闭包函数,看一个简单的例子

def foo():    x = 5    def func():        print(x)    return funcfoo()()
#返回值是5

我们会想,x = 5是属于foo函数的局部变量,为什么可以调用?这就是闭包的闭包的特性。

定义:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。

所以对上述例子中,func()就是闭包函数。

 生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

L = [x * x for x in range(10)]#listg = (x * x for x in range(10))#生成器

生成器保存的是算法,如果调用元素中的只,最好的办法就是使用for循环,因为生成器也是一个可迭代的对象。

 g = (x * x for x in range(10)) for n in g:     print(n) 

如果算法过去复杂庞大,我们可以使用函数调用来实现。比如斐波那契数列

def fib(max):    n, a, b = 0, 0, 1    while n < max-1:        #print(b)
     yield b a, b = b, a + b n = n + 1 return fib(6)
#这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

迭代器  

我们已经知道,可以直接作用于for循环的数据类型有以下几种:

  • 一类是集合数据类型,如listtupledictsetstr等。
  • 一类是generator,包括生成器和带yield的generator function。

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

如何去判断一个对象是不是一个可迭代的对象,可以使用内置函数进行判断

>>>from collections import Iterable>>> isinstance([], Iterable)True#如果是返回True,否返回False

注:

  • 凡是可作用于for循环的对象都是Iterable类型;
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  • 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

这里我们也许会问,都已经是可迭代的了,为什么还不是Iterator?这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。 

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

装饰器

什么是装饰器?

装饰器本质上是一个函数,该函数用来处理其他函数,它可以让其他函数在不需要修改代码的前提下增加额外的功能,装饰器的返回值也是一个函数对象。本质就是将原函数封装到另外一个函数里面,让其等于一个新函数,在执行新函数的内容。

首先举个简单的例子,在我们事先写好的许多函数中,如果我们想要添加一个新的功能,不可能往每个函数写一遍,这是一个不明智的做法。这里我们就会用到装饰器。例如

def f1(fd):    def f():        print("OK")        fd()        print("OK")        # return r    return f@f1def f2():    print("F2")f2() 

 

这是一个最简单的装饰器。其中@符号是python中表示装饰器的语法。

当然装饰器还可以指定参数去更方便的执行例如

import timedef logger(flag = "True"):    def show_time(f):        def func():            start = time.time()            f()            end = time.time()            if flag =="True":                print("打印日志")            print(end - start)        return func    return show_time@logger("true")def foo1():    print("OK!!!!!!!!!!!!!")    time.sleep(1)foo1()def foo2():    print("OK!!!!!!!!!!!!!")    time.sleep(2)foo2()

也可以传入不定长参数做简单的计算

import timedef show_time(f):    def func(*args,**kwargs):        start = time.time()        f(*args,**kwargs)        end = time.time()        print(end - start)    return func@show_timedef foo1(*args,**kwargs):    Sum = 0    for i in args:        Sum += i    print(Sum)    time.sleep(1)foo1(1,2,3)

小结:

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

 

 

python之函数