Python根基之函数,函数进级

一、函数

  我们可以用一段代码来实现我们需要的功能,但是当我们需要重复使用这段代码时,复制粘贴并不是一个酷的方法,我们可以用到函数来实现这一需求

  1、什么是函数:函数就是具备某一功能的工具

    函数的使用必须遵循先定义、后调用的原则

      事先准备工具的过程即函数的定义

      拿来就用即为函数的调用

    函数分为两大类:1、内置的函数   2、自定义的函数

一、函数定义

  2、为什么要用函数:

      2.1 程序的组织结构不清晰、可读性差

      2.2 日积月累冗余代码过多

      2.3 程序的可扩展性极差

函数是逻辑结构化和过程化的一种编程方法,通过一个函数名封装好一串用来完成某一特定功能的代码

  3、怎么用函数

函数的定义:

  3.1定义函数  

  3.1.1 语法 

# def 函数名(参数1,参数2,参数3,...):
#     """
#     文档注释
#     """
#     code1
#     code2
#     code3
#     ...
#     return 返回值

  3.1.2 定义函数阶段发生哪些事:只检测语法,不执行代码

def foo(): # foo=函数的内存地址
    print('first')
    print('sencod')
    print('asdfsadfasdfas')
    print('third')
foo()

# # 定义阶段
def foo():
    print('from foo')
    bar()

def bar():
    print('from bar')

#调用阶段
foo()

 3.1.3 定义函数的三种形式

        1.无参函数

def bar():
    print('from bar')
bar()

        2.有参函数

def func2(x,y):
    # x=1
    # y=3
    if x > y:
        print(x)
    else:
        print(y)

func2(1,3)

        3.空函数(做占位使用)

def foo():
    pass

def 函数名(参数1,参数2….):

 3.2调用函数

    语法:函数名()   

    调用函数发生了:1、根据函数名找到函数的内存地址 
2、函数的内存地址加括号可以触发函数体代码的执行

    调用函数的三种方式:

      3.2.1 语句

def f1():
    print('from 1')
f1()

      3.2.2表达式

def max2(x,y):
    if x > y:
        return x
    else:
        return y

res=max(1,2)*10
print(res)

      3.2.3当做参数传给其他函数

def max2(x,y):
    if x > y:
        return x
    else:
        return y

res=max2(max2(1,2),3)
print(res)

 

  ”注释”

 4、函数的返回值

  1、什么是函数的返回值:函数体代码运行的一个成果

  return 值:

    返回值没有类型限制、返回值没有个数限制

      逗号分割多个值:返回一个元组

      一个值:返回一个值

      没有return:默认返回None

图片 1图片 2

def login():
    while True:
        username=input('name>>: ').strip()
        pwd=input('password>>:').strip()
        if username not in user_dic:
            print('not found')
            continue
        if pwd != user_dic[username]:
            print('pwd error')
            continue
        print('login successful')
        return username
print(login())

return 登录名

# return是函数结束的标志:
# 函数内可以有多个return,但只要执行一次,整个函数就立即结束,并且将return后的值当作本次调用的结果返回

  函数体 

二、函数的参数

def func1():  #定义函数
    print('this is a function')  #函数体
func1()  #调用函数

#通常无参函数不需要返回值,有参函数需要有返回值
def func1(name):  #带参数的形式
    print('%s is a good man'%name)
func1('egon')

#关于函数的返回值,如果没有指定,默认是None
#函数与过程的定义就是过程没有返回值,用完之后就没有了,而函数有返回值
def func1(name):
    print('%s is a good man'%name)
print(func1('egon')) 

  1、函数的参数分为两大类:形参、实参

    形参:指的是在定义函数时,括号指定的参数,本质就是变量名

    实参:指的是在调用函数时,括号内传入的值,本质就是值

  只有在调用函数时才会在函数体内发生实参(值)与形参(变量名)的绑定关系

  该绑定关系只在调用函数时临时生效,在调用函数结束后就解除绑定

    def foo(x,y): #x=1,y=2
        print(x)
        print(y)
    # a=1
    # b=2
    # foo(a,b)
    foo(1,2)

–>  egon is a good man

  2、位置参数

    位置形参:在定义函数时,按照从左到右的顺序依次定义的形参称之为位置形参

       ps:但凡是按照位置定义的形参,在调用函数时必须为其传值,多一个不行少一个也不行

    位置实参:在调用函数时,按照从左到右的顺序依次传入的值

       ps:传值是按照顺序与形参一 一对应

def foo(x,y,z):
    print(x,y,z)
# foo(1,2)
foo(1,2,3,4)#多传一个值  报错
# foo(1,2,3)
foo(3,2,1)

    None

   3、关键字实参:

   
     在调用函数时,按照key=value的形式定义的实参,称之为关键字实参
      注意:
      1、在传值时可以完全打乱顺序,但仍然能指定道姓地为指定的参数传值
      2、可以在调用函数时,混合使用位置实参与关键字实参
      但是位置实参必须跟在关键字实参左边
      并且不能为一个形参重复传值

def register(name,sex,age):
    print(name)
    print(sex)
    print(age)
register(sex='male',name='mogu',age=18)
register('mogu',age=18,sex='male')#混合使用

函数的优点:1.代码重用

  4、默认参数

    在定义函数时,就已经为某些参数绑定值,称为默认参数

      ps:1、在定义阶段就已经有值,意味在调用阶段可以不用传值

        2、默认形参必须放在位置形参的后面

        3、默认形参的值只在定义阶段生效一次,在函数定义之后发生的改动无效

        4、默认形参的值通常应该是不可变类型

  默认形参:大多数情况下值都一样

  位置形参:大多数情况值都不同

def foo(x,y,z=3):
    print(x)
    print(y)
    print(z)
foo(1,2)
foo(1,2,4) #传值后改变了默认值

图片 3图片 4

def register(name,age,sex='female'):
    print(name)
    print(sex)
    print(age)
register('mogu',18)
register('xiaohuochai',28)
register('momo',19)
register('张三',33,'male')

默认与位置示例

m=10
def foo(x,y,z=m):
    print('x:%s' %x)
    print('y:%s' %y)
    print('z:%s' %z)
m=111111111    #定义阶段就是10   此更改无效
foo(1,2)

图片 5图片 6

def foo(name,hobby,l=None):
    if l is None:
        l=[]
    l.append(hobby)
    print('%s 的爱好是 %s' %(name,l))
l1=[]
foo('mogu','read',l1) #l1=['read']
foo('xiaomogu','movie')  #l1=['read','music']
foo('nvhai',' 卖火柴')
foo('张三','吹牛')
foo('关二爷','耍大刀')

默认形参示例2

      2.保持一致性,易于维护

  5、可变长度的参数

    可变长度指的是在调用函数时,函数参数的个数可以不固定

然而实参终究是要为形参传值的,针对两种形式实参个数不固定,对应着形参也必须有两种解决方案

那就是 * 和 ** 来分别处理溢出位置实参与溢出关键字实参

      3.可扩展性好

  5.1 * 会将溢出的位置实参存成元组,然后赋值给紧跟其后的变量名

5.1.1 形参中带 *

def foo(x,y,*z): #z=(3,4,5,6,7,8)
    print(x)
    print(y)
    print(z)
foo(1,2,3,4,5,6,7,8)

5.1.2  形参中带*   实参中带 * ,窍门:但凡碰到实参中带* 
都先将其打散成位置实参,然后考虑传值

def foo(x,y,*z): #z=(3,4,5,6,7,8)
    print(x)
    print(y)
    print(z)
foo(1,2,[3,4,5,6,7,8]) #z=([3,4,5,6,7,8],)
foo(1,2,*[3,4,5,6,7,8]) #foo(1,2,3,4,5,6,7,8)====>z=(3,4,5,6,7,8)
foo(1,2,*'hello') #foo(1,2,'h','e','l','l','o')
foo(1,*[2,3,4,5,6])  #foo(1,2,3,4,5,6)

5.1.3 实参中带 *  窍门同上

def foo(x,y,z):
    print(x,y,z)
l = ['mogu', 'huochai', 'nvhai']
foo(*l) # foo('mogu', 'huochai', 'nvhai')

注意:1.函数必须先定义,在使用,和变量类似,在定义前使用会报错

5.2  ** 会将溢出的关键字实参存成字典,然后赋值给紧跟其后的变量名

5.2.1 形参中带 **

图片 7图片 8

def foo(x,y,m,n,**z): #
    print(x)
    print(y)
    print(m)
    print(n)
    print(z)
foo(1,2,n=10,m=20,a=1,b=2,c=3)

View Code

5.2.2 形参中带 **  实参中带 **  窍门:但凡碰到实参中带** 
都先将其打散成位置实参,然后考虑传值

def foo(x,y,**z): #
    print(x,y,z)
foo(1,2,**{'a':1,'b':2,'c':2}) #foo(1,2,c=2,a=1,b=2)
foo(1,**{'a':1,'b':2,'c':2,'y':111}) #foo(1,c=2,a=1,b=2,y=111)

5.2.3 实参中带**

def foo(x,y,z):
    print(x,y,z)
foo(1,**{'y':111,'z':222}) #foo(1,y=111,z=222)
foo(**{'z':1,'y':2,'x':3}) #foo(y=2,z=1,x=3)

   2.函数在定义阶段只检测语法错误,不会执行代码,所以即使在函数体内有未定义的变量名,在函数未调用前也不会报错

   6、 *args   和  **kwargs

可接收任意长度,任意格式的参数(ps:实参中:位置实参必须在关键字实参的左边)

def foo(*args,**kwargs):
    print(args,kwargs)#可完美转嫁

foo(任意长度,格式的实参)

图片 9图片 10

#需要将外层函数的参数格式原封不动的转嫁给其内部调用的函数,就需要以下
def index(name,age,sex):
    print('name:%s age:%s sex:%s' %(name,age,sex))
def foo(*args,**kwargs):
    index(*args,**kwargs)#可完美转嫁

foo('mogu',sex='male',age=18)#虽然调用的是foo函数,但是需要遵循        
                                                   index函数的参数规则

示例

 

   3.函数的返回值可以是任意类型,如果是返回多个值,一定是元组形式

三、函数对象

  函数是第一类对象,意味着函数可以当做数据去使用

def foo():
    print('from foo')

1、可以被引用

print(foo)
func=foo
print(func)
func()

2、可以当做参数传给另外一个函数

def bar(x): #x=foo的内存地址
    print(x)
    x()
bar(foo)

3、可以当做函数的返回值

def bar():
    return foo
f=bar()
# print(f is foo)
f()

4、可以当作容器类型的元素

def f1():
    print('from f1')
def f2():
    print('from f2')
l=[f1,f2]   #列表内全为函数体
print(l)
l[1]()
l[0]()

 

   4.return
的作用是终止函数的执行,return只执行一次,后面的内容不执行

四、函数的嵌套调用

  1、嵌套调用

  在调用一个函数时,其内部的代码又调用其他的函数

def bar():
    print('from bar')

def foo():
    print('from foo')
    bar()
foo()

图片 11图片 12

def max2(x,y):
    if x > y:
        return x
    else:
        return y
#
def max4(a,b,c,d):
    res1=max2(a,b)
    res2=max2(res1,c)
    res3=max2(res2,d)
    return res3

print(max4(1,2,3,4))

数值大小比较

 

  2、嵌套定义:在一个函数的内部又定义了另外一个函数

def f1():
    x=1
    def f2():
        print('from f2')
    print(x)
    print(f2)
    f2()
f1()

 

二、函数参数

五、名称空间和作用域

1、什么是名称空间:名称空间是存放名字与值绑定关系的地方

  要取到值必须通过名字才能找,而名字又在名称空间中存放着,所以取值时首先去名称空间中找名字

    找到了名字自然拿到值的内存地址。

函数的参数分为形式参数和实际参数,在函数定义的时候,函数名后面括号里的就是形式参数,在函数调用的时候,传递的参数是实际参数。形式参数只在函数内部有效,外部无法引用。

2、名称空间分为三种:

  2.1 内置名称空间:存放的python解释器自带的名字

      生命周期:在解释器启动时产生,在解释器关闭时回收

  2.2 全局名称空间:除了内置的与局部的之外名字都属于全局名称空间

      生命周期:在程序文件执行时就立刻产生,在程序执行完毕后就回收

x=1
y=2
def foo():
    x=1
    y=2
foo()
if y > x:
    print(x)
z=3
# 其中:x,y,foo,z都是全局名称空间中的名字

  2.3 局部名称空间:存放的是函数内部定义的名字

      生命周期:在调用时临时生效,在函数结束后立刻回收

len=100
# print(len) # 站在全局查找

def foo():
    len=2222
    print(len)  #先在局部查找
foo()

1.形参

加载顺序

  内置名称空间—>全局名称空间—>局部名称空间

  加载名称空间的目的是为了将名字与值的绑定关系存放起来

  而存的目的是为了取,也就是当我们查找名字时,必然是在三者之一中找到

1)位置参数:按照从左到右的顺序依次定义的参数 def foo(x,y,z)

查找顺序

  局部名称空间—>全局名称空间—>内置名称空间

  基于当前所在位置往后查找

x=100
y=200

# 强调:函数的形参名属于局部名称空间
def foo(x,y):
    print(x,y)

foo(1,2)    #print结果为1,2

图片 13图片 14

x=2222
def f1():
    # x=1
    def f2():
        # x=2
        print('from f2',x)
    f2()

x=111
f1()   #print结果为   from f2  111

ps:全局查找

图片 15图片 16

x=2222
def f1():
    # x=1
    def f2():
        x=2           #先在局部查找
        print('from f2',x)
    f2()

x=111
f1()          #结果为  from f2   2

ps:局部查找

 

  位置形参必须被传值,且多一个少一个都不行

3、作用域

  域指的是范围,作用域指的是作用范围

    3.1.全局作用范围:包含内置名称空间与全局名称空间中的名字

        特点:全局有效,全局存活

    3.2.局部作用范围:包含局部名称空间中的名字

        特点:局部有效,临时存活

图片 17图片 18

x=1

def f1():
    def f2():
        def f3():
            x=3   #函数此处有值  所有全局的x=1 并不会取
            print(x)
        f3()
    f2()

f1()   #函数调用会先在函数内部寻找,所有结果为 3
#
def foo():
    print(x)

foo()  #而此处函数内部未定义x  所以会找全局   1

作用范围

#如何打破函数层级带来的访问限制,让我能够在任意位置都可以访问到一个内部函数
# 基于函数对象的概念将一个内部函数返回(return)到全局使用,从而打破了函数的层级限制

2)默认参数:在函数定义阶段就已经为形参赋值,调用阶段不赋值也会有默认值
def foo(x,y=10)

3.3.函数的作用域关系是在函数定义阶段就已经固定死的,与函数的调用位置无关

  即在调用函数时一定要跑到定义函数的位置寻找作用域关系

图片 19图片 20

x=111
def outter():
    x=33333
    def inner():
        print('from inner',x)
        # x=4444   #此处再定义会报错
    return inner
x=222
f=outter() #f=指向outter.locals.inner
f()     #结果为from inner  33333

作用域关系

 

  值经常变化的情况,通常定义成位置参数,但是值在多数情况下不变的情况下,可以定义成默认参数

3.4.  global 和  nonlocal

global :在局部声明名字是来自全局的

x=1
def func():
    global x   #声明x为全局
    x=2          #所以此处即修改了全局的x
func()

print(x)  #结果为 2

nonlocal :声明变量来自当前层外层(必须在函数内)

图片 21图片 22

x=222
def f1():
    x=111
    def f2():
        # nonlocal x
        x=3
        print('f2---->',x) #f2----->  3
    f2()
    print('f1---->',x)  # f1------->  111

f1()
print('global------>',x)   #global------> 222

无nonlocal结果

图片 23图片 24

x=222
def f1():
    x=111
    def f2():
        nonlocal x
        x=3
        print('f2---->',x) #f2----->  3
    f2()
    print('f1---->',x)  # f1------->  3

f1()
print('global------>',x)   #global------> 222

有nonlocal结果

 

注意:

六、装饰器

a.默认参数必须放在位置参数后面

一、闭包函数(函数体传值的新方式)

  1、 ——–>定义在函数内部的函数

  2、 ——–>该内部函数包含对其外层函数作用域名字的引用

闭包函数通常需要结合函数对象的概念,将闭包函数返回到外部使用

#闭包函数基本形式
def outter():
    x=1
    def inner():
        print(x)
    return inner

图片 25图片 26

# 将值包给函数
import requests  #导入爬虫模块
# def outter(url):
#     #url='https://www.jd.com'
#     def get():
#         response=requests.get(url)
#         print(len(response.text))
#     return get
#
# jd=outter('https://www.jd.com')#第一调用传入京东
# jd()   #  以后调用只需要 jd加括号就能得到结果, 新的传值方式

示例:闭包应用

b.默认参数通常定义成不可变类型

二、装饰器

  装饰指的是为被装饰对象添加新的功能

  指的工具

  装饰器本身是任意可以调用的对象,被装饰对象也是任意可以调用的对象

ps:写一个函数用来为另一个函数添加新功能,需要遵循开放封闭原则(对修改是封闭的,对扩展是开放的)

  1、不修改被装饰对象的源代码

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

图片 27图片 28

import time

def index():   #被装饰对象
    time.sleep(2)
    print('welcome to index page')
#
# index()  #原先功能
def outter(func): #func=最原始index
    def wrapper():
        start_time=time.time()
        func()  #上方的index() 原先功能
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time)) #加入新功能
    return wrapper

index=outter(index) #index=wrapper 加入新功能,并且调用方式没变
index()

装饰器的示例

图片 29图片 30

import time

def home(name):  #有参
    time.sleep(1)
    print('welcome %s to home page' %name)
    return 'lalala'

def timmer(func): #func=最原始index
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs) #原封不动转嫁参数
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper


home=timmer(home)  #home=warapper
print(home('mogu'))

被装饰对象有参

c.默认参数只在定义时被赋值一次

二、1、装饰器的语法糖  

  在被装饰对象正上方单独一行写上@装饰器名字

图片 31图片 32

import time

def timmer(func): #func=最原始index
    def wrapper(*args,**kwargs):
        start_time=time.time()
        res=func(*args,**kwargs)
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
        return res
    return wrapper


@timmer #index=timmer(index)
def index():
    time.sleep(1)
    print('welcome to index page')
    return 'lalala'
index()

装饰器的语法糖!!

图片 33图片 34

import time

current_userinfo={'user':None}

def outter(func):
    def wrapper(*args,**kwargs):
        if current_userinfo['user']:
            return func(*args,**kwargs)
        user=input('please input you username: ').strip()
        pwd=input('please input you password: ').strip()
        if user == 'mogu' and pwd == '123':
            print('login successfull')
            # 保存登录状态
            current_userinfo['user']=user
            res=func(*args,**kwargs)
            return res
        else:
            print('user or password error')

    return wrapper

@outter # index=outter(index)
def index():
    print('welcome to index page')
    time.sleep(3)

@outter #home=outter(home)
def home(name):
    print('welecom %s ' %name)
    time.sleep(2)
    return 123

index() # wrapper()
res=home('mogu') # res=wrapper('mogu')

认证功能装饰器

3)命名关键字参数:def register(*,name,age)
*后面定义的形参,必须被传值,且必须以关键字的形式传值

二、2、添加多个装饰器

图片 35图片 36

import time

current_userinfo={'user':None}

def timmer(func): #func=最原始的index指向的内存地址
    def wrapper2(*args,**kwargs):
        print('wrapper2.....')
        start=time.time()
        res=func(*args,**kwargs) # func=最原始的index指向的内存地址
        stop=time.time()
        print('run time is %s' %(stop - start))
        return res
    return wrapper2

def outter(func): # func=wrapper2
    def wrapper1(*args,**kwargs):
        print('wrapper1.....')
        if current_userinfo['user']:
            return func(*args,**kwargs)
        user=input('please input you username: ').strip()
        pwd=input('please input you password: ').strip()
        if user == 'mogu' and pwd == '123':
            print('login successfull')
            # 保存登录状态
            current_userinfo['user']=user
            res=func(*args,**kwargs) # func=wrapper2
            return res
        else:
            print('user or password err')
    return wrapper1


# 解释语法的时候应该自下而上
# 执行时则是自上而下
# 可以连续写多个装饰器,处于最顶层的装饰器先执行
@outter  # index=outter(wrapper2) # index=wrapper1
@timmer # timmer(最原始的index指向的内存地址) ==>wrapper2
def index():
    print('welcome to index page')
    time.sleep(3)

index() #wrapper1()

多个装饰器使用

2.实参

二、3、有参数的装饰器

图片 37图片 38

import time

current_userinfo={'user':None}

def auth(engine='file'):#参数代表认证模式
    def outter(func): #func=最原始的index
        def wrapper(*args,**kwargs):
            if engine == 'file':#if判断认证模式
                if current_userinfo['user']:
                    return func(*args,**kwargs)
                user=input('please input you username: ').strip()
                pwd=input('please input you password: ').strip()
                if user == 'mogu' and pwd == '123':
                    print('login successfull')
                    # 保存登录状态
                    current_userinfo['user']=user
                    res=func(*args,**kwargs)
                    return res
                else:
                    print('user or password err')
            elif engine == 'mysql':
                print('mysql 的认证机制')
                res = func(*args, **kwargs)
                return res
            elif engine == 'ldap':
                print('ldap 的认证机制')
            else:
                print('不支持该engine')
        return wrapper
    return outter

@auth(engine='mysql') #@outter # index=outter(最原始的index) # index= wrapper
def index():
    print('welcome to index page')
    time.sleep(3)

@auth(engine='ldap')
def home(name):
    print('welecom %s ' %name)
    time.sleep(2)
    return 123

index() #warpper()
home('蘑菇') #wrapper('蘑菇')

View Code

1)位置实参:与位置形参一一对应

二、4、wraps装饰器

  被装饰后的函数其实已经是另外一个函数了(函数名等函数属性会发生改变),为了不影响,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。

图片 39图片 40

#wraps作用将被装饰对象属性赋值给 装饰器的wrapper
import time
from functools import wraps

def timmer(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        start=time.time()
        res=func(*args,**kwargs)
        stop=time.time()
        print('run time is %s' %(stop - start))
        return res
    # wrapper.__doc__ = func.__doc__
    # wrapper.__name__= func.__name__
    return wrapper

@timmer
def index():
    """
    这是一个index函数
    :return:
    """
    print('welcome to index page')
    time.sleep(1)
    return 123

print(help(index)) # index.__doc__

# print(index.__name__)

wraps 装饰器

 

2)关键字参数:实参在定义时,按照key-values的形式定义

七、表达式,生成式

 1、三元表达式

  语法:【条件成立时的返回值】 +  if 条件    + 
else【条件不成立时的返回值】

图片 41图片 42

# res=条件成立时的返回值 if 条件 else 条件不成立时的返回值

def max2(x,y):
    if x > y:
        return x
    else:
        return y
res=max2(1,2)

x=10
y=2

res=x if x > y else y
res=True if x > y else False
print(res)

三元表达式

  2、列表生成式

  语法: 【i】  +   
for循环   可以跟 【条件】

# 原先列表生成
l=[]
for i in range(10):
    if i > 3:
        l.append(i)
print(l)
#列表生成式
l1=[i for i in range(10) if i > 3]  

print(l1)

图片 43图片 44

#原先操作列表
user=input('>>>:').strip()
names=['hello world','hello dream','hello %s'%(user)]
l=[]
for name in names:
    if name.startswith('he'):
        l.append(name.upper())
names=l
print(names)
#列表生成式操作
user=input('>>>:').strip()
names=['hello world','hello dream','hello %s'%(user)]
names=[name.upper() for name in names if 
              name.startswith('hello')]
print(names)

列表生成式:示例

 3、字典生成式

图片 45图片 46

info=[
    ['name','mogu'],
    ('age',18),
    ['sex','male']]
#将列表转成字典:方法一
d={}
for item in info:
    d[item[0]] = item[1]
print(d)

#字典生成式   :方法二
d={item[0]:item[1] for item in info}
print(d)

字典生成式

图片 47图片 48

d={
    'name':'蘑菇',
    'age':18,
    'sex':'male'
}
d={k.upper():v for k,v in d.items()}
print(d)

生成式改字典k的大小写

语法:同列表一样   用到 
for循环操作时优先考虑生成式操作

 

  def foo(x,y)

八、函数的递归调用

  在调用一个函数的过程中又直接或间接的调用了自身。

本质就是一个重复的过程,必须有两个明确的阶段

  1、回溯:一层一层地递归调用下去,每进入一层问题的规模都应该有所减少

  2、递推:递归必须有一个明确的结束条件,在满足该条件的情况下终止递归,往回一层一层地结束调用

图片 49图片 50

#询问年龄,第一个人说我比第二个人大2岁,第二个人说我比第三个人大2岁,。。。第六个人说“我18岁”
# age(6) = age(5) + 2
# age(5) = age(4) + 2
# age(4) = age(3) + 2
# age(3) = age(2) + 2
# age(2) = age(1) + 2
# age(1) = 18

# age(n) = age(n-1) + 2 #n > 1
# age(n) = 18           #n=1
#

def age(n):
    if n == 1:   #结束条件
        return 18
    return age(n-1) + 2  #基于上一次的结果调用自己

print(age(6))

示例1

图片 51图片 52

#  将一个列表中包含的值全部取出
l=[1,[2,[3,[4,[5,[6,[7,[8,[9,]]]]]]]]]

def tell(l):
    for item in l:
        if type(item) is list:
            #item 是列表
            # 再次调用本身的逻辑,传入item
            tell(item)
        else:
            #item是单独的元素
            print(item)

tell(l)

示例2

图片 53图片 54

#数字列表,数字是从小到大排列的
nums=[3,11,13,15,23,27,43,51,72,81,93,101]

#算法:就是如何高效地解决某一个具体问题的方法
l1=[3,11,13,15,23,27]
l2=[23,27]
l3=[23]

def binary_search(nums,find_num):
    print(nums)
    if len(nums) == 0:
        print('not exists')
        return
    mid_index=len(nums) // 2
    if find_num > nums[mid_index]:
        # in the right
        nums=nums[mid_index+1:]
        # 重复调用本身的逻辑,传入新的nums
        binary_search(nums,find_num)

    elif find_num < nums[mid_index]:
        # in the left
        nums=nums[:mid_index]
        # 重复调用本身的逻辑,传入新的nums
        binary_search(nums,find_num)
    else:
        print('find it')

binary_search(nums,94)

函数的递归调用+二分法

  foo(x=1,y=2)

九、匿名函数

  匿名函数lambda:临时用一次以后不再使用了。

#格式:
def sum2(x,y):
    return x + y
print(sum2)

sum2=lambda x,y:x + y

wage={
    'I':3800,
    'BOSS':9999999,
    'friends':10000,
    'web celebrity':1000000
}

print(max(wage,key=lambda x:wage[x]))#匿名函数使用场景 最大
print(min(wage,key=lambda x:wage[x]))#最小

# 按照薪资高低排序人名
print(sorted(wage,key=lambda x:wage[x])) #从小到大

print(sorted(wage,key=lambda x:wage[x],reverse=True))#翻转

  关键字参数可以不用向位置参数一样与形参一一对应,可以打破顺序限制

十、迭代器,生成器,生成器表达式

注意:a.位置参数和关键字参数混合使用的时候,位置参数必须在关键字参数前面

 1、迭代器

  迭代器就是迭代取值的工具

  迭代是一个重复的过程,但是每一次重复都是基于上一次的结果而进行

迭代器的作用:针对没有索引的数据类型,比如(字典、集合、文件)想要迭代取出其中包含的值

       python解释器提供了一种不依赖索引取值的工具

# 可迭代的对象:在python中但凡内置有__iter__方法的对象都称之为可迭代对象
  (字符串、列表、元组、字典、集合、文件)

图片 55图片 56

str1 = 'hello'
list1 = ['a', 'b', 'c']
t1 = ('a', 'b', 'c')
dic = {'x': 1, 'y': 2}
set1 = {'m', 'n'}
f = open('a.txt', mode='rt', encoding='utf-8')

可迭代对象

 

# 迭代器对象:
# 1、内置有__iter__方法,调用迭代器对象的__iter__方法得到仍然是迭代器本身
# ps: 文件对象本身就是一个迭代器对象
# 2、内置有__next__方法

dic = {'x': 1, 'y': 2}

iter_dic = iter(dic)   # iter_dic=dic.__iter__()
#上面得到迭代器对象 iter_dic

print(iter_dic.__next__())
print(iter_dic.__next__())
#就可以调__next__方法取值

图片 57图片 58

dic={'x':1,'y':2}
iter_dic=iter(dic) # 调__iter__得到迭代器

while True:
    try:      #检测异常
        k=next(iter_dic)    #执行__next__取值
        print(k)
    except StopIteration:  #接try 的检测   
        break

-----------------------------------------------------------------
#上述即为  for循环的   底层原理

for循环运行机制!!

   b.既可以是位置实参形式,也可以是关键字实参形式,但是一个形参只能传值一次

总结:

迭代器的优点:
  一、提供了一种能够不依赖索引的、通用的迭代取值方式
    补充:for循环可以称之为迭代器循环

 list1=['a','b','c']
    for item in list1:
    print(item)

    for循环的工作流程
   1、调用 in
后面那个对象的__iter__方法,拿到一个迭代器对象
   2、调用迭代器对象的__next__方法,拿到一个返回值赋值给变量名item
   3、循环往复,直到抛出异常,for循环会自动捕捉异常结束循环

  二、节省内存

迭代器的缺点:
  1、针对同一个迭代器对象只能取完一次,不如按照索引或者key取值方式灵活
  2、无法预测迭代器对象所包含值的个数

 

3)可变长参数:

2、生成器

在函数内但凡出现yield
关键字,在调用函数就不会触发函数体代码的执行了,

会得到一个返回值,返回值就是一个生成器对象

而生成器对象本质就是迭代器(ps:生成器是自定义的迭代器)

def foo():
    print('first')
    yield 1
    print('second')
    yield 2
    print('third')
    yield 3
    print('fourth')

g=foo() #g是生成器=》就是迭代器
g.__next__()
g.__next__()
g.__next__()

上述会触发g对应的函数体代码的执行,直到碰到一个yield就暂停住,该yield后的值当做本次__next__的返回值返回

print(g.__next__())
print(g.__next__())
print(g.__next__())

  按位置定义的可变长参数用*表示

总结yield的功能:

1、提供了一种自定义迭代器的方式

2、可以用于返回值

  yield和return的区别:

    相同点:都可以用于返回值,个数以及类型都没有限制

    不同点:yield可以返回多次值,return只能返回一次值整个函数就结束了

3、函数暂停以及继续执行的状态是由yield保存的

def my_range(start,stop,step=1):
    while start < stop: # 4 < 4
        yield start #start = 3
        start+=step #star=4
#----------------------------------------------------------
#range()的原理

for i in my_range(1,5): # 1  3
    print(i)

了解:yield关键字表达式形式的应用

图片 59图片 60

def cat(name):
    print('橘猫[%s]准备开吃' %name)
    food_list=[]
    while True:
        food=yield food_list#food=yield='鱼干'
        print('橘猫[%s]吃了: %s' %(name,food))
        food_list.append(food)
#
ct=cat('蘑菇')

# 强调:针对表达式形式的yield,在使用生成器时必先send(None),相当于先完成一个初始化操作
res0=next(ct) #ct.send(None)
# print(res0)

#send有两个功能:
#1、为当前暂停位置的yield赋值
#2、与next的效果一样
ct.send('鱼干')
# next(ct)
# ct.send(None)
ct.send('三鹿')
res1=ct.send('鱼干')
# print(res1)
res2=ct.send('牛奶')
print(res2)

yield关键字表达式

 

  按关键字定义的可变类型的参数用**表示  

 3、生成器表达式

 同上述列表、字典生成式一样的形式,只是两边换成了()

l=[i**2 for i in range(1,11)]
print(l)###列表生成式
#[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
#==================================
l1=(i**2 for i in range(1,11))
print(l1)
#<generator object <genexpr> at 0x0000000001EC05C8>
#()括号生成了一个迭代器对象,只要不调用l1,其内部就没有值,调一次next方法就取出一个值
print(next(l1))

优点是:

  更加节省内存

  自定义迭代器的方式

 

def func(x,y,*args):
    print(x,y,args)
func(1,2,3,4,5)  --->1 2 (3 4 5)

十一、面向过程编程

图片 61图片 62

#=============复杂的问题变得简单
#注册功能:
#阶段1: 接收用户输入账号与密码,完成合法性校验
def talk():
    while True:
        username=input('请输入你的用户名: ').strip()
        if username.isalpha():
            break
        else:
            print('用户必须为字母')

    while True:
        password1=input('请输入你的密码: ').strip()
        password2=input('请再次输入你的密码: ').strip()
        if password1 == password2:
            break
        else:
            print('两次输入的密码不一致')

    return username,password1

#阶段2: 将账号密码拼成固定的格式
def register_interface(username,password):
    format_str='%s:%s\n' %(username,password)
    return format_str

#阶段3: 将拼好的格式写入文件
def handle_file(format_str,filepath):
    with open(r'%s' %filepath,'at',encoding='utf-8') as f:
        f.write(format_str)


def register():
    user,pwd=talk()
    format_str=register_interface(user,pwd)
    handle_file(format_str,'user.txt')


register()

面向过程编程

#1、首先强调:面向过程编程绝对不是用函数编程这么简单,
    面向过程是一种编程思路、思想,而编程思路是不依赖于具体的语言或语法的。
    言外之意是即使我们不依赖于函数,也可以基于面向过程的思想编写程序

#2、定义
面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么

基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式

#3、优点:复杂的问题流程化,进而简单化

#4、缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身

#5、应用:扩展性要求不高的场景,典型案例如linux内核,git,httpd

#6、举例
流水线1:
用户输入用户名、密码--->用户验证--->欢迎界面

流水线2:
用户输入sql--->sql解析--->执行功能

   写于:2018-08-27   15:42:22 

#遇到*就是位置参数,把*后面的全部拆开,再一一匹配,多余的就以元组的形式存放到一起

def func(x,y,**kwargs):
    print(x,y,kwargs)
func(1,y=2,z=3,a=1,b=2)---->1 2 {'z': 3, 'a': 1, 'b': 2}

#遇到**就是关键字参数,把**后面的全部拆成关键字,再一一匹配,多余的以字典形式存放到一起

def wrapper(*args,**kwargs):可以接受任意形式,任意长度的参数

参数的定义顺序:x,y=1,*args,z,**kwargs,分别是位置参数,默认参数,可变长位置参数,命名关键字参数,可变类型参数

但需要注意的是,这些参数并不会同时全部出现

三、名称空间和作用域

名称空间存放名字和值的绑定关系,以key-value 的形式

在Windows命令提示行中输入命令:import this ,在最后一行会看到这样一句话:

  Namespaces are one honking great idea — let’s do more of those!

名称空间分为三种:

1)内置名称空间:Python自带的,如print,int,len….当Python解释器启动的时候,就会生成内置名称空间

2)全局名称空间:文件级别定义的名字会存放到全局名称空间,执行Python程序的时候产生,简单点说就是没有缩进的变量名

3)局部名称空间:定义在函数(或模块、类)内部的名字,只有在函数(模块、类)调用的时候才生效,调用结束后就会释放

加载顺序是:内置名称空间–>全局名称空间–>局部名称空间

取值顺序是:局部名称空间–>全局名称空间–>内置名称空间

四、函数嵌套和作用域

1.函数嵌套包括函数的嵌套调用和函数的嵌套定义

函数嵌套调用可以用求最大值的例子来说明:

def max2(x,y):
    if x > y:
        return x
    else:
        return y
def max4(a,b,c,d):
    res1=max2(a,b) #23
    res2=max2(res1,c) #23
    res3=max2(res2,d) #31
    return res3

print(max4(11,23,-7,31))

函数嵌套定义:

def f1():
    def f2():
        def f3():
            print('from f3')
        print('from f2')
        f3()
    print('from f1')
    f2()
# print(f1)
f1()

2.作用域

1)全局作用域:内置名称空间与全局名称空间的名字属于全局范围,在整个文件的任意位置都能引用

2)局部作用域:属于局部范围,只在函数内部可以被引用,局部有效

一定要注意局部变量和全局变量的作用范围,在局部修改全局变量会出错,在全局范围引用局部变量也会出错

作用域在函数定义时就已经固定了,不会因调用位置而改变

但是如果一定要在局部修改全局变量,也是用办法的,就是在要修改的变量前加一个global

x=1
def foo():
    x=10
    print(x)        

foo()       #10
print(x)    #1

x=1
def foo():
    global x
    x=10
    print(x)

foo()       #10
print(x)    #10

def foo():
    x=1
    def f2():
        x+=x
        return x
    return f2()

print(foo())        #会报错UnboundLocalError: local variable 'x' referenced before assignment

def foo():
    x=1
    def f2():
        nonlocal x  #告诉Python解释器,这里的x不是局部变量,只会找函数内部的,不会修改全局变量
        x+=x
        return x
    return f2()

print(foo())    #会打印出修改后的x的值,2

五、闭包函数

定义在函数内部的函数,该内部函数包含对外部作用域,而非全局作用域的名字的引用,那么该内部函数称为闭包函数

name='egon'
def func():
    name='alex'
    def bar():
        print(name)
    return bar

f=func()        #f就是闭包函数
print(f.__closure__[0].cell_contents)       #该命令可以查看闭包函数外面包了什么东西------>alex

闭包函数的特点:a.自带作用域,b.延迟计算(f只是拿到了函数的内存地址,什么时候用,加括号就可以运行)

闭包函数最基本的形式:

def 外部函数名():

  内部函数需要的变量

  def 内部函数名():

    引用外部变量

  return 内部函数名

六、装饰器

1.开放封闭原则:对扩展是开放的,对修改是封闭的

2.装饰器本质是任意可调用的对象,被装饰对象也是任意可调用的对象

3.装饰器的功能是:在不修改被装饰对象源代码及调用方式的前提下,为其添加新的功能

4.装饰器语法:在被装饰对象的正上方的单独一行,写上@装饰器名字

5.有多个装饰器的时候,每行一个,执行时从上往下运行

6.被装饰函数有参数的情况:写成(*args,**kwargs)的形式

装饰器示例一:

#实现缓存网页内容的功能,下载的页面存放于文件中,如果文件内有值(文件大小不为0),
# 就优先从文件中读取网页内容,否则,就去下载,然后存到文件中
from urllib.request import urlopen
import os

cache_path=r'C:\untitled\0615Python第8天\cache_file.txt'
def make_cache(func):
    def wrapper (*args,**kwargs):
        if os.path.getsize(cache_path):
            #有缓存
            print('\033[45m========>有缓存\033[0m')
            with open(cache_path,'rb') as f:
                res=f.read()
        else:
            res=func(*args,**kwargs)#下载
            with open(cache_path,'wb') as f:#制作缓存
                f.write(res)
        return res
    return wrapper


@make_cache

def get(url):
    return urlopen(url).read()


print(get('https://www.python.org'))

装饰器示例二:

#为多个函数加上认证的功能(用户的账号密码来源于文件),要求登录成功一次,后续的函数都无需再输入用户名和密码
db_dic={
    'egon':'123',
    'alex':'alex3714',
    'yuanhao':'smallb'
}


db_path=r'C:\untitled\0615Python第8天\db_dic.txt'

with open(db_path,'w',encoding='utf-8') as f:
    f.write(str(db_dic))

login_dic={
    'user':None,
    'status':False,
}

def auth(func):
    def wrapper(*args,**kwargs):

        #加一个验证状态的字典,如果已经登录成功,下次使用就不用重新验证
        if login_dic['user'] and login_dic['status']:
            res=func(*args,**kwargs)
            return res
        else:
            name=input('name:')
            password=input('password:')
            with open(db_path, 'r', encoding='utf-8') as f:
                auth_dic = eval(f.read())
            if name in auth_dic and password==auth_dic[name]:
                print('login ok')
                login_dic['user']=name
                login_dic['status']=True
                res=func(*args,**kwargs)
                return res
            else:
                print('error')
    return wrapper

@auth
def index():
    print('welcom to the page')

@auth
def home(name):
    print('welcom  to %s\'s home page'%name)


index()
home('egon')

七、迭代器

1.对于字符串、列表、元组的数据类型,我们可以依据索引来实现迭代的效果,但是字典、集合这种没有索引的数据类型,就需要其他方式

2.Python为了提供一种不依赖索引的迭代方式,为一些对象内置了__iter__方法,obj.__iter__()得到的结果就是迭代器

得到的迭代器既有.__iter__方法,又有.__next__方法

3.迭代器的优点:

  a.提供了一种不依赖索引的取值方式

  b.惰性计算,节省内存

4.迭代器的缺点:

  a.取值不如按照索引取值方便

  b.一次 性的,取值只能往后走,不能往前退

  c.无法获取迭代器的长度

5.for循环实际上会默认调用.__iter__方法

6.判断是否是可迭代对象和迭代器,可以用命令

print(isinstance(str1,Iterable)) —>判断是否为可迭代对象

print(isinstance(str1,Iterator)) —>判断是否为迭代器

八、生成器函数(语句形式和表达式形式)

1.生成器函数:函数体内包含有yield关键字,该函数的执行结果就是生成器

2.生成器实际就是迭代器的一种

3.yield的功能:

*  a.与return类似,都可以返回值,但不一样的地方在于yield返回多次值,而return只能返回一次值
  b.为函数封装好了__iter__和__next__方法,把函数的执行结果做成了迭代器
  c.遵循迭代器的取值方式obj.__next__(),触发的函数的执行,函数暂停与再继续的状态都是由yield保存的
4.生成器语句形式应用实例*

图片 63图片 64

 1 #模拟linux中tail -f a.txt|grep 'error' |grep '404'的功能
 2 import time
 3 def tail(filepath,encoding='utf-8'):
 4     with open(filepath,encoding='utf-8') as f:
 5         f.seek(0,2)  #以末尾为开始位,第0个
 6         while True:
 7             line=f.readline()
 8             if line:
 9                 yield line
10             else:
11                 time.sleep(0.5)
12 
13 def grep(lines,pattern):
14     for line in lines:
15         if pattern in line:
16             # print(line)
17             yield line
18 
19 g1=tail('a.txt')
20 g2=grep(g1,'error')
21 g3=grep(g2,'404')
22 
23 for i in g3:
24     print(i)

View Code

5.生成器的表达式形式

def foo():
    print('starting')
    while True:
        x=yield #默认就是yield None
        print('value :',x)

g=foo() 
next(g)  #初始化,等同于g.send(None)
g.send(2)  

将yield赋值给一个变量如x=yield,然后用send()传值,但注意要先做一个类似初始化的操作

g.send(2)的操作实际是先把2传值给yield,再由yield传值给x,send()既有传值的效果,又有next()的效果

生成器表达式形式应用示例

图片 65图片 66

 1 def init(func):
 2     def wrapper(*args,**kwargs):
 3         g=func(*args,**kwargs)
 4         next(g)
 5         return g
 6     return wrapper
 7 @init
 8 def eater(name):
 9     print('%s ready to eat' %name)
10     food_list=[]
11     while True:
12         food=yield food_list#return None
13         food_list.append(food)
14         print('%s start to eat %s' %(name,food))
15 
16 
17 e=eater('alex')
18 print(e.send('狗屎'))
19 print(e.send('猫屎'))
20 print(e.send('alex屎'))
21 
22 
23 def make_shit(people,n):
24     for i in range(n):
25         people.send('shit%s' %i)
26 
27 e=eater('alex')
28 make_shit(e,5)
29 #from egon
30 #egon老师的例子有味道,但是我又忍不住不用这个

View Code

九、三元表达式

res= x if x>y else
y—–>判断条件x>y是否为真,为真则把x赋给res,否则把y赋给res

十、列表解析

s='hello'
res=[i.upper() for i in s]
print(res)          #['H', 'E', 'L', 'L', 'O']

相关文章