Python之路--Python基础3--函数

一、函数简介

  函数是重(chong)用的程序段。它们容许你给一个语句块一个名称,而后你用这个名字可以在你的程序的任何地方,任意屡次地运行这个语句块。这被称为调用函数。咱们已经使用了许多内建的函数,好比 len 和 range 。函数用关键字 def 来定义。def 关键字后跟一个函数的标识符名称,而后跟一对圆括号。圆括号之中能够包括一些变量名,该行以冒号结尾。接下来是一块语句,它们是函数体。下面这个例子将说明这事实上是十分简单的:html

def sayhi(name): #函数名(参数)
    print("Hello,I'm %s" % name) sayhi("YL")  #调用函数

 

二、函数参数、局部变量、全局变量

  形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。所以,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量python

  实参能够是常量、变量、表达式、函数等,不管实参是何种类型的量,在进行函数调用时,它们都必须有肯定的值,以便把这些值传送给形参。所以应预先用赋值,输入等办法使参数得到肯定值linux

 

默认参数算法

先看下面代码:数据结构

def stu_register(name,age,country,course): print("----注册学生信息------") print("姓名:",name) print("age:",age) print("国籍:",country) print("课程:",course) stu_register("Jack",22,"CN","python") stu_register("Tom",21,"CN","linux") stu_register("Alex",25,"JP","C++")

  country 这个参数基本都是"CN", 就像咱们在网站上注册用户,像国籍这种信息,你不填写,默认就会是“CN”,这就是经过默认参数实现的,把country变成默认参数很是简单:闭包

  def stu_register(name,age,course,country="CN"):并发

这样,country这个参数在调用时不指定,那默认就是CN,指定了的话,就用你指定的值。注意:默认参数只能放到最后面 。 app

 

关键参数函数

正常状况下,给函数传参数要按顺序,不想按顺序就能够用关键参数,只需指定参数名便可,但记住一个要求就是,关键参数必须放在位置参数以后网站

   stu_register(age=22,name='alex',course="python")

 

非固定参数

若你的函数在定义时不肯定用户想传入多少个参数,就可使用非固定参数

def stu_register(name,age,*args):  #*args 会把多传入的参数变成一个元组形式
    print(name,age,args) stu_register("Tom",22) #输出 #Tom 22 ()               #后面这个()就是args,只是由于没传值,因此为空
 stu_register("Jack",32,"CN","Python") #输出 #Jack 32 ('CN', 'Python')

还能够有一个**kwargs

def stu_register(name,age,*args,**kwargs):  #*kwargs 会把多传入的参数变成一个dict形式
    print(name,age,args,kwargs) stu_register("Tom",22) #输出 #Tom 22 () {}                   #后面这个{}就是kwargs,只是由于没传值,因此为空
 stu_register("Jack",32,"CN","Python",sex="Male",province="ShanDong") #输出 # Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'}

调用函数时,没有对应上的位置参数会传入*args,变成元组形式;关键参数会传入**kwargs,变成字典形式。

 

全局变量与局部变量

在子程序中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
全局变量做用域是整个程序,局部变量做用域是定义该变量的子程序。
当全局变量与局部变量同名时:在定义局部变量的子程序内,局部变量起做用;在其它地方全局变量起做用。

 

三、返回值

要想获取函数的执行结果,就能够用return语句把结果返回

注意:

  1. 函数在执行过程当中只要遇到return语句,就会中止执行并返回结果,so 也能够理解为 return 语句表明着函数的结束
  2. 若是未在函数中指定return,那这个函数的返回值为None 

 

四、函数嵌套与函数递归

啥都别说直接看函数嵌套的代码1:

name = "YL"
def change_name(): name = "YL2"
    def change_name2(): name = "YL3"
        print("第3层打印", name) change_name2() #调用内层函数
    print("第2层打印", name) change_name() print("最外层打印", name)

#输出:
第3层打印 YL3
第2层打印 YL2
最外层打印 YL

代码2:

#嵌套调用
def my_max4(a, b, c, d): res1 = my_max2(a, b) res2 = my_max2(res1, c) res3 = my_max2(res2, d) return res3 def my_max2(x, y): if x > y: return x else: return y print(my_max4(11,35,34,-5))  #35


#嵌套定义(一般2到3层,多了很差看懂)
x = 3
def f1(): x = 1
    def f2(): x = 2
        print(x) return f2 func = f1() func() #2

递归:若是一个函数在内部调用本身,这个函数就是递归函数

def calc(n): print(n) if int(n/2) ==0: return n return calc(int(n/2)) calc(10) 输出: 10
5
2
1

递归特性:

1. 必须有一个明确的结束条件

2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减小

3. 递归效率不高,递归层次过多会致使栈溢出(在计算机中,函数调用是经过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。因为栈的大小不是无限的,因此,递归调用的次数过多,会致使栈溢出

堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html 

 递归函数的实际应用:二分查找

def binary_search(find_str, data_set, count):  #参数说明:要找的数据,数据集合,查找计数器
    mid = int(len(data_set)/2) if mid == 0: if data_set[mid] == find_str: print("找到了->:", find_str, count) else: print("没找到:", find_str, count) return
    if data_set[mid] == find_str: print("找到了:", data_set[mid], count) elif data_set[mid] > find_str: print("Going to search in left:", data_set[mid], data_set[0:mid]) binary_search(find_str, data_set[0:mid], count+1) else: print("Going to search in right:", data_set[mid], data_set[mid+1:]) binary_search(find_str, data_set[mid+1:], count+1) binary_search(585, data, 0)

 

五、匿名函数

匿名函数就是不须要显式的指定函数

#这段代码
def calc(n): return n**n print(calc(10)) #换成匿名函数
calc = lambda n:n**n print(calc(10))

这个看不出啥NB之处,那就看看下面的:

res = map(lambda x:x**2,[1,5,7,4,8]) for i in res: print(i) #输出:
1
25
49
16
64
#匿名函数最复杂的函数就是三元运算,不能再复杂了
calc2 = lambda x, y: x**y print(calc2(10, 12)) for i in map(calc, [1, 2, 3]): print(i) for i in map(lambda x: x*x, [1, 2, 3]):  #逼格高
    print(i) for i in map(lambda x: x*2 if x>5 else x-1, [1, 2, 3]): print(i)

map()函数是Python内置函数,第一个参数接受一个函数名,后面的参数接受一个或多个可迭代的序列,返回的是一个集合。(后面的博客中会有详细介绍)

 

六、高阶函数

# 高阶函数:1.把一个函数的内存地址当作参数传给另外一个函数 # 2.一个函数把另外一个函数当作返回值 返回


def add(x, y, f): return f(x) + f(y) res = add(3, -6, abs)  #abs绝对值函数
print(res)

 

七、闭包函数

若是在一个内部函数里,对在外部做用域(但不是在全局做用域)的变量进行引用,那么内部函数就被认为是闭包。

举个栗子:

def closure(): x = 5
    def sub(): return x * x return sub

如上,在内部函数sub中包含了对函数closure中局部变量x的引用,这就是闭包。

闭包的意义:返回的函数对象,不只仅是一个函数对象,在该函数外还包裹了一层做用域,这使得,该函数不管在何处调用,优先使用本身外层包裹的做用域

应用领域延迟计算(原来咱们是传参,如今咱们是包起来)

再看一个nb一点的栗子:

from urllib.request import urlopen def page(url): #url = http//:www.baidu.com
    def get(): return urlopen(url).read() return get baidu = page('http://www.baidu.com') #爬取百度页面 # python = page('http://www.python.org')

# print("python:", python())
print("baidu:", baidu().decode("utf-8")) #这里要转码

 

八、装饰器

装饰器就是闭包函数的一种应用场景

装饰器遵循开放封闭原则:对修改封闭,对扩展开放

装饰器自己能够是任意可调用对象,被装饰者也能够是任意可调用对象。

装饰器的原则:一、不修改被装饰对象的源代码

       二、不修改被装饰对象的调用方式

装饰器的目标:在遵循以上两点原则的前提下,为被装饰对象添加上新功能

#装饰器语法
#被装饰函数的正上方,单独一行
@deco1 @deco2 @deco3 def foo(): pass

#此时调用foo就至关于---> foo=deco1(deco2(deco3(foo)))

举个栗子:

#无参装饰器
import time def timer(func): def wrapper(*arg, **kwargs): #任意参数传入
        strat_time = time.time() res = func(*arg, **kwargs)#运行最原始的index
        stop_time = time.time() print("run time is %s" % (stop_time-strat_time)) return res return wrapper @timer #index = timer(index)
def index(msg): print("in the index:%s" % (msg)) @timer def home(user, msg): print("in the home:%s,%s" % (user, msg)) return "home return 1" index("hello world") print(home("jack", msg="123456")) #输出-------------------
in the index:hello world run time is 0.0
in the home:jack,123456 run time is 0.0 home return 1

下面是有参装饰器的栗子:

#有参装饰器
accounts = {} current_logon_user = None def auth(auth_type): def auth_deco(func): def wrapper(*args, **kwargs): if current_logon_user not in accounts: #以前没有验证成功过
                username = input("username:") password = input("password:") if auth_type == "file": if username == "YL" and password == "123": accounts[username] = True global current_logon_user  #修改全局变量
                        current_logon_user = username return func(*args, **kwargs) elif auth_type == "ldap": print("----->ldap") return func(*args, **kwargs) else: return func(*args, **kwargs)   #若是验证成功了的直接返回执行最原始的函数
        return wrapper return auth_deco @auth("file") def index(msg): print("in the index %s" %(msg)) @auth("ldap") def home(msg): print("in the home %s" %(msg)) index("hello")  #只要第一次验证经过,后面就不须要验证了
home("hello")

 

九、生成器

如今有个需求,将列表[0,1,2,3,4,5,6,7,8,9]里的每个值都加一,有下面三种方法:

#普通青年版--->使用enumerate函数
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] for index, i in enumerate(a):  #将列表中的各项加一
    #print(index, i)       #打印下标和元素值
    a[index] += 1

print(a)
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] #文艺青年版--->使用匿名函数和map函数
a = map(lambda x: x+1, a) for i in a: print(i)
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] #nb青年版----->使用列表生成
a = [i+1 for i in a] print(a)                       #[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

#列表生成式还可使用三元运算 #a = [i*i if i>5 else i-1 for i in a] #大于5的进行平方运算,小于5的进行减1运算 #print(a)              #[-1, 0, 1, 2, 3, 4, 36, 49, 64, 81]

  经过上面的列表生成式,咱们能够直接建立一个列表。可是,受到内存限制,列表容量确定是有限的。并且,若是建立一个包含100万个元素的列表,就会占用很大的存储空间,若是咱们仅仅须要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

  因此,若是列表元素能够按照某种算法推算出来,那咱们是否能够在循环的过程当中不断推算出后续的元素呢?这样就没必要建立完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

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

L = [x * x for x in range(10)] print(L)   #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
 g = (x * x for x in range(10)) print(g)   #<generator object <genexpr> at 0x00000133F4CE6FC0>

建立Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

  咱们能够直接打印出list的每个元素,但咱们怎么打印出generator的每个元素呢?若是要一个一个打印出来,能够经过next()函数得到generator的下一个返回值:

g = (x * x for x in range(4)) print(g)   #<generator object <genexpr> at 0x00000133F4CE6FC0>

print(next(g))       #0
print(g.__next__())  #1
print(g.__next__())  #4
print(next(g))       #9 #print(next(a)) #最后一个元素已经计算出了,再调用next(g)就会报错

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。固然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,由于generator也是可迭代对象:

for n in g: print(n) #输出:
0 1
4
9

  因此,咱们建立了一个generator后,基本上永远不会调用next(),而是经过for循环来迭代它,而且不须要关心StopIteration的错误。generator很是强大。若是推算的算法比较复杂,用相似列表生成式的for循环没法实现的时候,还能够用函数来实现。

  好比,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数均可由前两个数相加获得:1, 1, 2, 3, 5, 8, 13, 21, 34, ...

  斐波拉契数列用列表生成式写不出来,可是,用函数把它打印出来却很容易:

def fib(max): n, a, b = 0, 0, 1
    while n < max: print(b) a, b = b, a + b n = n + 1
    return 'done'

注意:

赋值语句:a, b = b, a + b 至关于: t = (b, a + b)  # t是一个tuple
            a = t[0] b = t[1]        

调用上面的函数能够输出斐波那契数列的前N个数:

>>> fib(10) 1
1
2
3
5
8
13
21
34
55 done

  仔细观察,能够看出,fib函数其实是定义了斐波拉契数列的推算规则,能够从第一个元素开始,推算出后续任意的元素,这种逻辑其实很是相似generator。也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只须要把print(b)改成yield b就能够了:

def generator(max): n, a, b = 0, 0, 1
    while n<max: #print(b)
        yield b             #生成器yield 保存了函数的中断状态
        a, b = b, a + b n = n+1
    return "done" g = generator(5) print(next(g)) print(g.__next__()) print("do something else") print(g.__next__()) print(g.__next__()) print(next(g)) # 输出: # 1 # 1 # do something else # 2 # 3 # 5

这就是定义generator的另外一种方法。若是一个函数定义中包含yield关键字,那么这个函数就再也不是一个普通函数,而是一个generator:

  注意,最难理解的就是generator和函数的执行流程不同。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

  在上面generator(max)的例子中,咱们在循环过程当中不断调用yield,就会不断中断。固然要给循环设置一个条件来退出循环,否则就会产生一个无限数列出来。

一样的,把函数改为generator后,咱们基本上历来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:

for n in generator(6): print(n) #输出
1
1
2
3
5
8

可是用for循环调用generator时,发现拿不到generator的return语句的返回值“done”。若是想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中。(关于如何捕获错误,后面的错误处理还会详细讲解)

还可经过yield实如今单线程的状况下实现并发运算的效果

#吃包子 #经过生成器实现协程并行运算
import time def consumer(name): print("%s 准备吃包子啦!" % name) while True:       #死循环
       baozi = yield  #接收到producer send 过来的值

       print("包子[%s]来了,被[%s]吃了!" % (baozi, name)) def producer(): c = consumer('A') c2 = consumer('B') c.__next__() c2.__next__() print("开始准备作包子啦!") for i in range(10): time.sleep(1) print("作了2个包子!") c.send(i) #调用next 并传了一个值给yield
 c2.send(i) producer()

 

十、迭代器

咱们已经知道,能够直接做用于for循环的数据类型有如下几种:

  一类是集合数据类型,如listtupledictsetstr等;

  一类是generator,包括生成器yield的generator function

这些能够直接做用于for循环的对象统称为可迭代对象Iterable

可使用isinstance()判断一个对象是不是Iterable对象:

>>> from collections import Iterable >>> isinstance([], Iterable) True >>> isinstance({}, Iterable) True >>> isinstance('abc', Iterable) True >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance(100, Iterable) False

而生成器不但能够做用于for循环,还能够被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示没法继续返回下一个值了。

能够被next()函数调用并不断返回下一个值的对象称为迭代器Iterator

可使用isinstance()判断一个对象是不是Iterator对象:

>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([], Iterator) False >>> isinstance({}, Iterator) False >>> isinstance('abc', Iterator) False

生成器都是Iterator迭代器对象,但listdictstr虽然是Iterable可迭代对象,却不是Iterator迭代器

listdictstrIterable变成Iterator可使用iter()函数:

>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True

那么,为何listdictstr等数据类型不是Iterator

  这是由于Python的Iterator迭代器对象表示的是一个数据流,Iterator对象能够被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。能够把这个数据流看作是一个有序序列,但咱们却不能提早知道序列的长度,只能不断经过next()函数实现按需计算下一个数据,因此Iterator的计算是惰性的,只有在须要返回下一个数据时它才会计算。

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

小结:

  凡是可做用于for循环的对象都是Iterable类型;

  凡是可做用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

  集合数据类型如listdictstr等是Iterable但不是Iterator,不过能够经过iter()函数得到一个Iterator对象。

 

Python的for循环本质上就是经过不断调用next()函数实现的,例如:

for x in [1, 2, 3, 4, 5]:
    pass

等价于下面的代码:

# 首先得到Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 得到下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break
相关文章
相关标签/搜索