函数

一、函数基础

1、为什么要用函数

# 增强程序的组织结构,可读性高
# 减少代码冗余
# 扩展性强

2、什么是函数

# 函数时组织好的,可以重复使用的的,用来实现单一,或相关联功能的代码段 
# 函数能提高应用的模块性,和代码的重复利用率
 
# 在程序中,函数必须遵守:先定义,再调用

3、函数的分类

# 内置函数

为了 方便程序开发,针对一些简单的功能,Python解释器已经定义好了的函数即内置函数,对于内置函数我们可以拿来就用而无需事先定义,如len()  sum() max()


# 自定义函数

很明显内置函数所能提供的功能有限,这就需要我们自己根据需求,事先定义好我们自己的函数来实现某个功能,以后在遇到应用场景时,调用自定义函数即可

4、定义函数

# 函数代码块以def关键字开头,后接函数名和小括号
# 任何传入的参数和自身变量必须放在小括号里面,参数名称可以多个
# 函数的第一行需要写函数说明
# 函数内容以冒号起始,并且缩进
# return [表达式]结束函数,选择性的返回一个值给调用方,不带表达式的return ,返回值为None

5、有参函数、无参函数、空函数

# 有参函数
def userinfo(name,age,):     # 定义时有参
    '''
    :param name:  姓名
    :param age:   年龄
    :return:
    '''
    print('姓名:%s 年龄:%s' %(name,age))

userinfo('fred','18')      # 调用时必须传参

# 无参函数

def func():                # 定义时无参
    '''
    :return:
    '''
    print('this is No Reference function')

func()                     # 调用时不需要传参

# 空函数
def pay():
    pass

# 空函数的作用:在项目立项阶段,使用空函数把此项目的大体功能干先列出来,后续只需要一个个实现小功能就可以

6、函数的返回值

return 是函数结束的标志,
函数内可以有多个return,但只要执行一个,函数就立即结束,并且把return后的值当做本次调用的结果返回
注意:
1、返回值没有类型限制
2、返回值没有个数限制,可以用逗号分开多个值一次返回
3、可以没有return,默认返回None
 
什么时候该有返回值?
     调用函数,经过一系列的操作,最后拿到一个明确的结果,则必须要有返回值
     通常有参数需要返回值,输入参数,经过计算,得到一个最终结果
什么时候不需要返回值?
     调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值
     通常无参函数不需要有返回值

7、函数调用:

# 语句形式:userinfo()

# 表达式形式:3*len('userinfo')

# 当做其他函数的参数:range(len('userinfo'))

 

8、函数基础阶段总结:

1、函数的使用必须遵守:先定义后调用的原则
2、在定义阶段,只检测函数命名语法以及函数体的语法
3、在调用阶段,通过函数名找到函数的内存地址,然后执行函数体代码
4、函数名;是用来访问到函数的内存地址的,拿到函数的内存地址加括号就可以触发函数体代码的执行
5、函数参数:是外界调用者为函数传值的媒介
6、函数体代码:是函数功能的具体实现
7、return是函数体执行的成果

二、函数的参数

1、实参与形参

函数的参数可以分为两大类:形参与实参
形参:函数定义阶段,括号内指定的参数,可以理解为变量名
实参:函数调用阶段,括号内指定的参数,可以理解为变量名对应的值

只有在调用函数时才会在函数体内发生实参(值)与形参(变量名)的绑定关系
该绑定关系仅在调用函数时临时生效,调用结束后就解除绑定

2、位置参数

'''
位置形参:函数定义阶段,按照从左到右的顺序依次定义的形参,称之为位置形参
位置实参:函数调用阶段,按照从左到右的顺序依次传入的值,称之为位置实参
'''

def userinfo(name,age):
    print('姓名:%s  年龄:%s' %(name,age))


userinfo('fred',18)         # 此时就相当于格式化输出中的%s
姓名:fred  年龄:18          # 从左到右 name=fred  age=18

# userinfo('fred',18,'sa')
typeError: userinfo() takes 2 positional arguments but 3 were give
   # 值传多了

userinfo('fred')
userinfo() missing 
1 required positional argument:
 'age'   # 没有给age传值

userinfo(20,'fred')
姓名:20  年龄:fred           # 由于严格按照位置传值,在不了解参数具体功能情况下,就会出现这种结果


# 总结:
   # 但凡是按照位置定义的形参,在调用时必须为其传值,多一个不行,少一个也不行
   # 在传值时,按顺序与形参一一对应

3、关键字实参

'''
关键字实参:在函数调用阶段,按照key=value的形式定义的实参,称之为关键字实参
注意:
   1、关键字实参可以完全打乱顺序,但仍然可以指名道姓的为指定形参传值
   2、可以在调用函数时,关键字实参和位置实参混合使用
   3、关键字实参要跟在位置实参的后边,并且不能为一个形参传多个值

'''

def userinfo(name,age,company):
    print('姓名:{name} 年龄:{age} 公司:{company}'.format(name=name,age=age,company=company))



userinfo('fred',company='9you',age=20)
# 姓名:fred 年龄:20 公司:9you


userinfo(age=20,company='9you','fred')
SyntaxError: positional argument follows keyword argument       # 位置参数不能跟在关键字参数右边

4、默认形参

'''
默认参数:在定义函数时,就已经为某些参数绑定值,称之为默认形参
注意:
   1、在定义阶段就已经有值,意味着在调用阶段可以不用为其传值
   2、默认形参必须放到位置形参后面
   3、默认形参的值只在定义阶段生效一次,在函数定义之后发生的改动无效
   4、默认形参的值通常应该为不可变类型

什么时候应该定义默认形参:
    大多数情况下都一样的,比如民族

什么时候应该定义位置形参
    大多数情况是不一样的,比如name

'''

def userinfo(name,age,company,sex='male'):
    print('姓名:{name} 年龄:{age} 公司:{company} 性别:{sex}'.format(name=name, age=age, company=company,sex=sex))

userinfo('fred',age=18,company='9you')             # 可以不用为默认形参传值
姓名:fred 年龄:18 公司:9you 性别:male

userinfo('jerry',age=18,company='9you',sex='female')
姓名:jerry 年龄:18 公司:9you 性别:female            # 也可为默认形参传值,以传的值为准
 
def userinfo(name,age,sex='male',company):
print('姓名:{name} 年龄:{age} 公司:{company} 性别:{sex}'.format(name=name, age=age, company=company,sex=sex))


SyntaxError: non-default argument follows default argument # 默认形参不能在位置形参左边

5、可变长参数

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

然而实参终究要为形参传值的,针对两种形式的参数不固定,对应着形参也必须有两种解决方案,来分别处理溢出的位置实参与关键字实参

python中约定俗成:
   *args       # 位置形参
   *kwargs     # 关键字形参
 
*  会把溢出的实参存成元组,然后赋值给紧跟其后的变量名
** 会把溢出的关键字实参存成字典,然后赋值给紧跟其后的变量名
'''

1)形参中带*

def userinfo(username,passwd,*args):
    print('用户名:%s' %username)
    print('密码:%s' %passwd)
    print('其他:',args)

userinfo('root','x', '0', '0', 'root', '/root', '/bin/bash')

用户名:root
密码:x
其他: ('0', '0', 'root', '/root', '/bin/bash')      

2)形参中带*   && 实参中带*

'''
实参中* 小窍门:
但凡实参中*,都先将其打散成位置实参,然后考虑传值
'''

def userinfo(username,passwd,*args):
    print('用户名:%s' %username)
    print('密码:%s' %passwd)
    print('其他:',args)

userinfo('root',*('x', '0', '0', 'root', '/root', '/bin/bash'))    #此处,只传一个位置实参,然后*后面跟了元组
用户名:root
密码:x
其他: ('0', '0', 'root', '/root', '/bin/bash')       #处理过程:先将元组打散成位置实参,然后将x传给passwd,剩下的整体传给*,并绑定给args

3)、实参中带*

'''
实参中* 小窍门:
但凡实参中*,都先将其打散成位置实参,然后考虑传值
'''

def userinfo(username,passwd,desc):
    print('用户名:%s' %username)
    print('密码:%s' %passwd)
    print('描述:%s' %desc)

l=['fred','1234','this is fred']

userinfo(*l)     #实参中带*,形参中没带*的情况,要严格遵守位置形参的规则,多一个不行,少一个也不行

4)、形参中带**

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

'''
def userinfo(username,passwd,**kwargs):
    print('用户名:%s' %username)
    print('密码:%s' %passwd)
    print('其他:',kwargs)

userinfo(username='root',passwd='x',uid='0',gid='0',desc='root')     # 溢出关键字实参被**存成字典,赋值给kwargs
用户名:root
密码:x
其他: {'uid': '0', 'gid': '0', 'desc': 'root'}

5)、形参带**,实参中带**

'''
形参中带**  溢出的关键字实参会存成字典,然后赋值给紧跟其后的变量名
实参中带**  但凡实参中带** ,先将其打散成关键字实参,然后再考虑传值

'''

def userinfo(username,passwd,**kwargs):
    print('用户名:%s' %username)
    print('密码:%s' %passwd)
    print('其他:',kwargs)

userinfo('root',**{'passwd':'x','uid':'0','gid':'0','desc':'root'})     # 实参中带** 先将打散成关键字实参,passwd=x 其他给了kwargs
用户名:root
密码:x
其他: {'uid': '0', 'gid': '0', 'desc': 'root'}

6)、实参中带**

'''
实参中带**  但凡实参中带** ,先将其打散成关键字实参,然后再考虑传值
'''

def userinfo(username,passwd):
    print('用户名:%s' %username)
    print('密码:%s' %passwd)

userinfo(**{'username':'fred','passwd':'1234','desc':'this is fred'})
TypeError: userinfo() got an unexpected keyword argument 'desc'       # 不能传多也不能传少

userinfo(**{'username':'fred','passwd':'1234'})
用户名:fred密码:1234

7、练习

'''
1、写函数,,用户传入修改的文件名,与要修改的内容,执行函数,完文件修改操作
'''
def replance_file(file_name,old,new):
    '''
    修改文件内容
    :param file_name:要修改的文件
    :param old: 修改前的的内容
    :param new: 修改后的内容
    :return:
    '''
    import os
    with open(file_name, mode='r', encoding='utf8') as f_r, 
            open('.%s.swap' % file_name, mode='w', encoding='utf8') as f_w:
        for line in f_r:
            if line.startswith(old):
                line = line.replace(old,new)
                f_w.write(line)
            else:
                f_w.write(line)

    os.remove(file_name)
    os.rename('.%s.swap' %file_name,file_name)

    return old,new


res=replance_file('src.txt','name','姓名')
print(res)
'''
2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数
'''
def result(s):
    counts={
        'alpha':0,
        'digit':0,
        'space':0,
        'other':0,
    }

    for i in s:
        if i.isalpha():                  # 判断是否为字母
            counts['alpha']+=1
        elif i.isdigit():                # 判断是否为数字
            counts['digit']+=1
        elif i.isspace():                # 判断是否为空格
            counts['space']+=1
        else:
            counts['other']+=1

    return counts

res=result('hello 18 age name %%%%&&&')
print(res)
'''
3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。
'''
def judge(*args):
    result=[]
    for i in args:
        if len(i) > 5:
            result.append(i)


    return result

res=judge('fred_li',[18,'9you','sa'],('read ','play'))
print(res)
'''
4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
'''
def new_list(l):
    if len(l) > 2:
        return l[0],l[1]
    else:
        return l

res=new_list(['fred','18','read','play'])
print(res)
'''
5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。
'''
def fun(l):
    return l[::2]

res=fun(['root','x','/home','this is root','/sbin/nologin'])
print(res)
'''
6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
dic = {"k1": "v1v1", "k2": [11,22,33,44]}
PS:字典中的value只能是字符串或列表
'''

def func(dic):
    d={}
    for k,v in dic.items():
        if len(v) > 2:
            d[k]=v[0:2]
        else:
            d[k]=v

    return d

print(func({'username':'fred','dept':('sa','ceo','cto','coo'),'hobby':['read','music','play','game'],'desc':('friend','father')}))

三、函数对象

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

# 函数身为一个对象,拥有对象的三个通用属性:id type value
def userinfo():
    print('from userinfo')

print(userinfo)
<function userinfo at 0x0387AA98>

print(type(userinfo))
<class 'function'>

print(id(userinfo))
59222680
'''
可以被引用,即函数可以赋值给一个变量
'''
def userinfo():
    print('from userinfo')

func=userinfo
print(func,userinfo)
<function userinfo at 0x051DBA98> 
<function userinfo at 0x051DBA98>
'''
可以当做参数传给另外一个函数
'''

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

def f2(func):
    print(func)
    f1()

f2(f1)         # f1是当做参数传给f2 ,f2内部调用了f1

<function f1 at 0x036CBA98>
from f1
'''
可以当做函数的返回值
'''

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

def f2():
    return f1

res=f2()
print(res)
<function f1 at 0x0369BA98>

res()
from f1
'''
可以当做容器类型(可以存多个值)的元素
容器对象(list、dict、set等)中可以存放任何对象,包括整数、字符串,函数也可以作存放到容器对象中
'''

def auth():
    print('from auth')

def pay():
    print('from pay')


func_dic={
    '1':auth,
    '2':pay,
}

func_dic['1']()
from auth

func_dic['2']()
from pay

四、函数嵌套

1、函数的嵌套调用

'''
函数的嵌套调用:在调用一个函数时,其内部的代码又调用了其他的函数
'''

def sum(x,y):
    sum=x+y
    return sum

def sums(a,b,c,d):
    res=sum(a,b)
    res1=sum(c,d)
    res2=res+res1
    return res2

print(sums(10,20,30,40))

2、函数的嵌套定义

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

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

f2()

五、名称空间与作用域

1、名称空间

'''
名称空间:是名称与对象之间的关系,可以将命名空间看做是字典,其中的键是名称,值是对象
'''

'''
内置名称空间:存放Python解释器自带的名称,如len max sum 
生命周期:在解释器启动时产生,关闭时回收
'''
print(len)
<built-in function len>

'''
全局名称空间:除了内置的与局部的名字之外的都属于全局名称空间
生命周期:在程序文件执行时,立刻产生,程序执行完毕后就回收
'''
name='root'

'''
局部名称空间:存放函数内部定义的名字
生命周期:在调用函数时临时生效,函数调用结束后立即回收
'''

def user():
    age=18
    return age

print(user())

print(age)
name 'age' is not defined       # 函数调用已结束,所以age和18的绑定关系也被回收了


'''
加载顺序:内置名称空间———全局名称空间————局部名称空间
查找顺序:基于当前名称空间查找  当前————上一级————上上级
假设目前在局部名称空间:查找顺序为:局部名称空间————全局名称空间————内置名称空间
强调::::: 函数的形参属于局部名称空间
'''
 
def foo():
x=4
print(locals()) # locals()方法是打印出当前局部名称空间中的名字与value的对应关系
foo()
{'x': 4} # 以字典方式存储

2、作用域

'''
作用域:指的是名字的作用范围
分为:全局作用域和局部作用域
'''

'''
全局作用域:包含内置名称空间与全局名称空间中的名字
特点:全局有效,全局存活
'''

'''
局部作用域:只包含局部名称空间的名字
特点:局部有效,临时存活
'''

x=3333

def area():
    x=222
    def area1():
        x=111
        print('from area1 %s' %x)
    area1()
    print('from area %s' %x)

area()                 # 次函数为嵌套函数
from area1 111         # 在area1函数内部,x与111绑定,area1被调用时,area1内部有x的名字与值对应的关系,所以x此时等于111
from area 222          # area1调用结束后,x=111的绑定关系也随着解除,在area内部,x与222绑定,area被调用,所以此时x=222
print(x)
3333                   # area调用结束,x与222的绑定关系也随之解除,所以此时是在全局作用域里查找x对应的valve,所以此时x=3333

'''
总结:
   函数的作用域关系是在函数定义阶段就已经固定死的,与函数的调用位置无关
   即在调用函数时一定要到定义函数的位置寻找作用域关系

'''

3、global和nonlocal

'''
global:在局部名称空间声明其后的变量是作用于全局
'''

x=3333
def func():
    x=2222
    def func1():
        global x
        x=4444
        print('from func1 %s' %x)
    func1()
    print('from func %s' %x)


func()
from func1 4444     # 由于func1中声明了x是全局名称空间中的名字,所以它作用于本层局部作用域和全局作用域,
from func 2222      # 但不影响上层局部作用域

print(x)
4444                # 此时就算全局名称空间中定义了x=3333,也以global所在的局部名称空间中定义的为准
'''
nonlocal 在局部名称空间声明其后的变量作用于上层
'''
x=1111
def func():
    x=4444
    def func1():
        nonlocal x
        x=3333
        print('from func1 %s' %x)
    func1()
    print('from func %s' %x)


func()
from func1 3333      # 由于func1中nonlocal声明了x=3333
from func 3333       # 所以就算func中定义了x=4444,也不生效

print(x)
1111                  # nonlocal并未影响全局名称空间

六、闭包函数

'''
大前提:函数的作用域关系是在函数定义时就已经固定死的,与调用位置无关
闭包函数:
    1、闭指的是:定义在函数内部的函数
    2、包指的是:该内部函数包含对其外层函数作用域名字的引用

闭包函数通常需要结合函数对象的概念,打破层级限制,将闭包函数返回到外部使用
'''

import requests                    # 要事先安装requests模块
def outter(url):
    def get():
        response=requests.get(url)
        print(len(response.text))
    return get                     # 返回get函数的内存地址

jd=outter('http://www.jd.com')      # 调用outter函数,实际是调用内部的get
jd()

baidu=outter('https://www.baidu.com')
baidu()

'''
总结:
    1、闭包函数传一次值,多次调用
    2、通过外部函数把值传给内部函数
    3、闭包的意义:返回的函数对象,不仅仅是一个函数对象,在该函数外还包裹了一层作用域,这使得无论在何处调用,都优先使用自己外层包裹的作用域
'''

七、装饰器

'''
装饰器是闭包函数一种应用场景
装饰器:
    装饰指的在不修改被装饰对象的前提下,为被装饰对象添加新功能
    器指的是工具

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

原则:
    写一个函数给另外一个函数添加新功能,需要遵守开放封闭的原则(对修改是封闭的,对扩展是开放的)
    1、不修改被装饰对象的源代码
    2、不修改被装饰对象的调用方式
'''

1、无参装饰器之装饰无参对象

'''
无参装饰器
'''
import requests
import time
def get():
    response=requests.get('http://www.baidu.com')
    print('The url have %s' %(len(response.text)))

# 为get函数加上统计时间的功能
def timmer(func):
    start_time=time.time()
    func()
    stop_time=time.time()
    print('run time is %s' %(stop_time - start_time))

# timmer(get)             # 此方法虽然能实现需求,但是其改变了原对象的调用方式,pass掉

# 只能考虑使用闭包函数咯
def wrapper(func):    # func为最原始的get
    def timmer():     # 此处一定不能传值,如果传值就失去了闭包的意义
        start_time=time.time()
        func()          #此处的的func是由包在外层的wrapper传进来的
        stop_time=time.time()
        print('run time is %s' %(stop_time - start_time))
    return timmer
get=wrapper(get)   # 此时(get)为最原始的get函数,传给了func,然后将wrapper(get)重新赋值get
get()              # 此处get被伪装了,站在用户的角度上,还以为调用的是最原始的get函数,其实get调用的是wrapper,在wrapper内部为get加了统计时间的功能

# 到此为止,新功能实现了,并且遵守了开放封闭原则(未修改被装饰对象的源代码和调用方式)

'''
装饰器语法
    在被调用对象的正上方使用"@装饰器名"  
    @装饰器名   ==   get=wrapper(get)
'''

# 上面的功能可以改写为:

def wrapper(func):    # func为最原始的get
    def timmer():     # 此处一定不能传值,如果传值就失去了闭包的意义
        start_time=time.time()
       func()          #此处的的func是由包在外层的wrapper传进来的
        stop_time=time.time()
       print('run time is %s' %(stop_time - start_time))
    return timmer

@wrapper         #get=wrapper(get)
def get():
    response=requests.get('http://www.baidu.com')
    print('The url have %s' %(len(response.text)))

get()
The url have 2381
run time is 0.034737348556518555

2、无参装饰器之装饰有参对象

'''
有参装饰器

接上面的例子,只能统计http://www.baidu.com的时间统计,功能被写死了
此时就得给装饰器传参了
'''

import requests
import time


def wrapper(func):     # 最原始的get
    def timmer(*args,**kwargs):           #给被调用对象传参   'http://www.jd.com'
        start_time=time.time()
        func(*args,**kwargs)              #get('http://www.jd.com') / home()
        stop_time=time.time()
        print('run time is %s' %(stop_time - start_time))
    return timmer                  

@wrapper
def get(url):
    response=requests.get(url)
    print('The url have %s' %(len(response.text)))

@wrapper(‘http://www.jd.com’)
def home():
    time.sleep(1)
    print('welcome access home page')

# 过程分析:
# get=wrapper(get)  # 首先将get传给wrapper,也就间接传给timmer,拿到timmer的返回值,即wrapper(get)
# print(get)
# <function wrapper.<locals>.timmer at 0x051AFDB0>

get() # 第二步,此时是给最原始的get传参,由timmer接收,并传给内部的get函数,完成新加功能并且未修改被装饰对象的源代码和调用方式

home()              

# 到此为止,可实现即可装饰有参对象和无参对象
'''
完整版装饰器语法
'''
import requests
import time

def wrapper(func):
    def timmer(*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 timmer

@wrapper
def get(url):
    response=requests.get(url)
    # print
    return ('The url have %s lines' %(len(response.text)))           # 源对象有返回值

print(get('https://www.baidu.com'))

示例:

'''
为被装饰对象加上认证功能'''

import requests
import time
current_userinfo={'user':None}           # 存储当前登录用户的信息

def auth(func):
    def wrapper(*args,**kwargs):
        if current_userinfo['user']:               # 如果current_userinfo['user']的值非空,则证明当前用户已登录,不用重复登录
            func(*args,**kwargs)
            return print(func(*args,**kwargs))
        else:                                       # 否则,从新登录
            username=input('请输入账号:').strip()
            passwd=input('请输入密码:').strip()
            if username == 'fred' and passwd == '123':
                print('welcome to login')
                current_userinfo['user']=username
                func(*args,**kwargs)
                return print(func(*args,**kwargs))
            else:
                print('用户或密码错误')
    return wrapper

@auth
def get(url):
    response=requests.get(url)
    return ('The url have %s' %(len(response.text)))

@auth
def home():
    time.sleep(1)
    return ('welcome access home page')

get('http://www.jd.com')
home()

3、多个装饰器执行顺序

'''
为一个对象添加多个装饰器
'''
import requests
import time
current_userinfo={'user':None}           # 存储当前登录用户的信息

def auth(func):
    def wrapper(*args,**kwargs):
        if current_userinfo['user']:
            func(*args,**kwargs)
            return print(func(*args,**kwargs))
        else:
            username=input('请输入账号:').strip()
            passwd=input('请输入密码:').strip()
            if username == 'fred' and passwd == '123':
                print('welcome to login')
                current_userinfo['user']=username
                func(*args,**kwargs)
                return print(func(*args,**kwargs))
            else:
                print('用户或密码错误')
    return wrapper

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

@timmer
@auth
def get(url):
    response=requests.get(url)
    return ('The url have %s' %(len(response.text)))

get('http://www.jd.com')

'''
分析:
    1、@auth === 最原始的get 》》》 wrapper(get)  即get=wrapper(get)
    2、@timmer == 不是最原始get   》》 wrapper1(wrapper(get))    即get=wrapper1(wrapper(get))
执行顺序:
    1、先执行timmer,就是wrapper1(wrapper(get)), 即执行auth
    2、等auth执行完,再回到timmer,执行res=func(*args,**kwargs)之后的代码
    3、整个装饰器执行完成,本次时间统计是从timmer开始到auth结束,查看结果
    请输入账号:fred
    请输入密码:1234
    用户或密码错误
    run time is 5.6903979778289795     
结论:多个装饰器的情况下,自上而下一次执行
'''

4、有参装饰器

'''
有参装饰器:本质就是在无参装饰器外又包了一层
'''
import requests
import time
current_userinfo={'user':None}           # 存储当前登录用户的信息

def multi_auth(engine):
    def outter(func):
        def wrapper(*args,**kwargs):  # engine是关键字实参传进来的,所以file一定会传给engine
            if engine == 'file':     
                if current_userinfo['user']:
                    func(*args,**kwargs)
                    return print(func(*args,**kwargs))
                else:
                    username=input('请输入账号:').strip()
                    passwd=input('请输入密码:').strip()
                    if username == 'fred' and passwd == '123':
                        print('welcome to login')
                        current_userinfo['user']=username
                        func(*args,**kwargs)
                        return print(func(*args,**kwargs))
                    else:
                        print('用户或密码错误')
            elif engine == 'mysql':
                print('mysql认证机制')
            elif engine == 'ldap':
                print('ldap认证机制')
            else:
                print('不支持改认证机制')
        return wrapper
    return outter

@multi_auth(engine='file')   # outter(get)
# @multi_auth(engine='mongdb')
def get(url):
    response=requests.get(url)
    return ('The url have %s' %(len(response.text)))

get('http://www.jd.com')

5、完全装饰被装饰对象(继承被装饰对象的属性)

'''
wraps装饰器:获取被装饰对象的属性(函数名、帮助信息等)
    Help on function wraps in module functools:
    wraps(wrapped, assigned=('__module__', '__name__', '__qualname__', '__doc__', '__annotations__'), updated=('__dict__',))
'''
import requests
import time
from functools import wraps
current_userinfo={'user':None}           # 存储当前登录用户的信息

def multi_auth(engine):
    def outter(func):
        @wraps(func)                # 将最原始的get对象的属性转嫁给wrapper
        def wrapper(*args,**kwargs):  # engine是关键字实参传进来的,所以file一定会传给engine
            if engine == 'file':
                if current_userinfo['user']:
                    func(*args,**kwargs)
                    return print(func(*args,**kwargs))
                else:
                    username=input('请输入账号:').strip()
                    passwd=input('请输入密码:').strip()
                    if username == 'fred' and passwd == '123':
                        print('welcome to login')
                        current_userinfo['user']=username
                        func(*args,**kwargs)
                        return print(func(*args,**kwargs))
                    else:
                        print('用户或密码错误')
            elif engine == 'mysql':
                print('mysql认证机制')
            elif engine == 'ldap':
                print('ldap认证机制')
            else:
                print('不支持改认证机制')
        return wrapper
    return outter

@multi_auth(engine='file')
def get(url):
    '''
    获取指定url的response.text的长度
    :param url:
    :return:
    '''
    response=requests.get(url)
    return ('The url have %s' %(len(response.text)))

# get('http://www.jd.com')

print(help(get))            # 获取函数的帮助信息(注释)

Help on function get in module __main__:
get(url)
    获取指定url的response.text的长度
    :param url:
    :return:
None

八、三元表达式、列表生成式、字典生成式、二分法、函数与的递归调用

1、三元表达式

'''
三元表达式:
语法:
   条件成立的返回值 if 条件 else 条件不成立的返回值
'''
x=10
y=20

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

2、列表生成式

'''
列表生成式:和for循环搭配
    语法:l=[i for i in 序列 条件(可以没有条件)]   条件只能跟一个
'''

l=[i for i in range(5)]
print(l)

# 等同于
for_l=[]
for i in range(5):
    for_l.append(i)

print(for_l)

l1=[i for i in range(5) if i > 2]
print(l1)

# 等同于
for_l1=[]
for i in range(5):
    if i > 2:
        for_l1.append(i)

print(for_l1)

str='root:x:0:0:root:/root:/bin/bash'
l2=str.split(':')


l2=[i.upper() for i in l2 if i == 'root']
print(l2)

# 等同于
l3=[]
for i in str.split(':'):
    if i == 'root':
        l3.append(i.upper())
print(l3)

3、字典生成式

'''
字典生成式:
    语法:d1={k.upper():v for k,v in d1.items() if }  可以不加条件
'''

d1={
    'name': 'fred',
    'age': 18,
    'commany': '9you',
    'weight': 50.4
}

print({k.upper():v for k,v in d1.items() if k == 'weight'})

# 等同于
d2={}
for k,v in d1.items():
    d2[k.upper()]=v

print(d2)

4、函数的递归调用

'''
函数的递归调用:
    在调用一个函数的过程中又直接或间接调用了自己,称之为函数的递归调用
    本质就是一个重复的过程,必须有两个明确阶段:
        1、回溯:一层一层的递归下去调用下去,每进入下一层问题的规模都应该有所减少
        2、递推:往回一层一层的结束调用
            递归必须要有一个明确的结束条件,在满足该条件的情况下会终止递归
递归调用的分类:
    直接调用:自身调用
    def foo():
        foo()
    间接调用:通过别的函数间接调用自身
        def bar()
            foo()
递归 VS while循环
    递归只需要把控结束或进入递归的条件即可,至于循环次数或者嵌套层数无须考虑

调用函数会产生局部的名称空间,占用内存,因为上述这种调用会无需调用本身,
    python解释器的内存管理机制为了防止其无限制占用内存,对函数的递归调用做了最大的层级限制
import sys
sys.getrecursionlimit()         查看当期最大层级限制
sys.setrecursionlimit(2000)     修改最大层级限制
'''

# 示例:
# age(5) = age(4) + 2
# age(4) = age(3) + 2
# age(3) = age(2) + 2
# age(2) = age(1) + 2
# age(1) = 18

#推算出age(5)的值

def age(n):     # n从高到底,一步一步逼近1
    if n == 1:
        return 18
    return age(n-1)+2

print(age(2))

# 取出l中的每个元素
l=[1,[2,[3,]]]

def foo(l):
    for item in l:
        if type(item) is list:
            foo(item)
        else:
            print(item)

foo(l)
1
2
3

image

#总结递归的使用:
1. 必须有一个明确的结束条件

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

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

5、二分法

'''
二分法是一种快速查找的方法,时间复杂度低,逻辑简单易懂,总的来说就是不断的除以2除以2...
分法查找非常快且非常常用,但是唯一要求是要求数组是有序的
'''
# 查找次序列中有没有43
nums=[3,11,13,15,23,27,43,51,72,81,93,101]


def binary_search(num,nums):
    print(nums)
    if len(nums) == 0:
        print('%s is not found' %num)
        return
    #
    mid_index=len(nums) // 2

    if num > nums[mid_index]:
        # in right
        nums=nums[mid_index+1:]    # 从mid_index索引号加一位(如果不加1,则取到mid_index)开始-数组最后
        binary_search(num,nums)    #重复调用本身逻辑,传入新的nums
    elif num < nums[mid_index]:
        # in left
        nums=nums[:mid_index]      #从数组索引0开始取-mid_index
        binary_search(num,nums)     # 重复调用本身逻辑,传入新的nums
    else:
        print('find it')

binary_search(94,nums)

[3, 11, 13, 15, 23, 27, 43, 51, 72, 81, 93, 101]
[51, 72, 81, 93, 101]
[93, 101]
[93]
[]
94 is not found

九、匿名函数

'''
匿名函数:即没有名字的函数,与函数有相同的作用域,但是匿名函数意味着引用计数为0,使用一次就释放,除非让其有名字

定义匿名函数就是定义出了一个函数的内存
语法:
    lambda 实参 函数体代码

lambda 只是一个表达式,函数体比 def 简单很多。
lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
'''

func=(lambda name,age:print('my name is %s my age is %s' %(name,age)))('name',20)
print(func)

'''
示例
 文件内容标题为name,age,work,commany
 fred|20|SA|haymarker
 lck|30|IT|u9

取出每一行,按如下格式存储
[{'name':'fred','age':'20','work':'SA','commany':'haymarker'}]
'''
# 字典生成式的另一种应用场景
with open('info.txt','rt',encoding='utf8') as info:
    items=[line.strip().split('|') for line in info]  # 列表生成式将文件内容按行转换成列表
    # 字典生成式将items的值传给字典里的key
    userinfo=[{'name':name,'age':age,'salary':salary,'work':work,'commany':commany} for name,age,salary,work,commany in items]

# print(userinfo)

# 取出年龄最大的成员的信息
def maxage(dic):
    return dic['age']     # 返回age对应的value

for dic in userinfo:
    res=maxage(dic)       # 循环取出最大age的值
print(max(userinfo,key=maxage))  # 调用maxage函数,取出age最大用户信息,由于循环的是userinfo列表,所以返回最大age所在的列表
print(max(userinfo,key=lambda dic:dic['age']))   #dic是参数,
{'name': 'lck', 'age': '30', 'salary': '1000', 'work': 'IT', 'commany': 'u9'}

# 取出薪资最高的成员信息
print(max(userinfo,key=lambda dic:dic['salary']))
{'name': 'fred', 'age': '20', 'salary': '3000', 'work': 'SA', 'commany': 'haymarker'}

# 将成员信息映射成首字母大写的形式
def capital(dic):
    return {'name':dic['name'].capitalize(),
            'age':dic['age'],
            'salary':dic['salary'],
            'work':dic['work'],
            'commany':dic['commany']}

for dic in userinfo:
    res=capital(dic)
user=map(capital,userinfo)
print(user)
<map object at 0x04CD1ED0>

print(list(user))
[{'name': 'Fred', 'age': '20', 'salary': '3000', 'work': 'SA', 'commany': 'haymarker'}, {'name': 'Lck', 'age': '30', 'salary': '1000', 'work': 'IT', 'commany': 'u9'}]
users=map(lambda dic:{
            'name':dic['name'].capitalize(),
            'age':dic['age'],
            'salary':dic['salary'],
            'work':dic['work'],
            'commany':dic['commany']
},userinfo)

print(list(users))

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

1、迭代器

'''
什么是迭代器
    迭代器就是一个迭代取值的工具
    迭代是一个重复的过程,但是每一次重复都是基于上一次的结果而进行

为何要用迭代器
    针对没有索引的数据类型,比如字典、集合、文件,要想迭代取出其中包含的一个个的值
    Python解释器必须提供一种能够不依赖与索引的迭代取值工具

什么是可迭代对象
    内置有__iter__()方法的都是可迭代对象
    以下数据类型都是可迭代对象:
        <str_iterator object at 0x04AB24F0>
        <list_iterator object at 0x04AB2970>
        <dict_keyiterator object at 0x04AB38A0>
        <set_iterator object at 0x04AB85D0>
        <_io.TextIOWrapper name='info.txt' mode='r' encoding='utf8'>
    int和float不是可迭代对象
    
迭代器对象
    1、内置有__iter__()
    2、内置有__next__()
        文件既是可迭代对象,又是迭代器对象
            f.__iter__()
            f.__next__()
    对于可迭代对象来说,调用__iter__()方法,得到就是迭代器对象
 
注意:
   迭代器对象一定是可迭代对象
   可迭代对象不一定是迭代器对象'''
'''
迭代器对象的使用
'''
info=['adm','/var/adm','/sbin/nologin']
obj=info.__iter__()

print(obj.__next__())          # obj.__next__() 等同于next(obj)
print(next(obj))
print(next(obj))
print(next(obj))               # next()方法抛出StopIteration异常时,标志着迭代器对象里的值已经被取完了
StopIteration

# 使用while循环来取值
while True:
    try:
        print(next(obj),end=' ')
    except StopIteration:       # except捕捉到StopIteration异常,则break掉
        break
'''
try...except:
try的工作原理是,当开始一个try语句后,python就在当前程序的上下文中作标记
这样当异常出现时就可以回到这里,try子句先执行,接下来会发生什么依赖于执行时是否出现异常。
    如果当try后的语句执行时发生异常,python就跳回到try并执行第一个匹配该异常的except子句,异常处理完毕,控制流就通过整个try语句(除非在处理异常时又引发新的异常)。
    如果在try后的语句里发生了异常,却没有匹配的except子句,异常将被递交到上层的try,或者到程序的最上层(这样将结束程序,并打印缺省的出错信息)。
    如果在try子句执行时没有发生异常,python将执行else语句后的语句(如果有else的话),然后控制流通过整个try语句。            
'''

2、for循环的工作原理

'''
基于for循环就可以不依赖与索引取值

工作原理:
   1.执行 in后对象的dic.__iter__()方法,得到此对象的迭代器对象
   2.执行next(dic.__iter__()),将得到的值赋值给k
  3.重复过程2,直到捕获StopIteration,结束循环
'''
dic={
    'name':'fred',
    'age':19,
    'commany':'haymarker',
}

for k in dic:
    print(k)
'''
迭代器的优缺点:
优点:
    提供了一种不依赖与索引的、通用的取值方法
    节省内存,同一时间在内存中只有一个值(对象元素过大,迭代器可以更节省内存)
缺点:
    针对同一个迭代器对象只能取完一次,不能重复取,不如按照索引或者key取值灵活
    无法预测迭代器对象所包含值的个数
'''

3、生成器

'''
什么是生成器
    在函数内但凡出现yield关键字,再调用函数就不会触发函数体代码的执行了
        generator() 直接调用不会触发函数体代码的执行
    会得到一个返回值,该返回值就是一个生成器对象
        def generator():
            print('this is first')
            yield
        print(generator())
        <generator object generator at 0x04F52870>
    而生成器对象本身就是迭代器
        g.__next__()
        g.__iter__()

为什么要用生成器
    生成器是自定义的迭代器


'''

def generator():
    print('this is first')
    yield
g=generator()
print(g.__next__())
# 既然g是迭代器,那么就可以通过迭代器规则通过next()方法取值
# 即触发g对应的函数体代码的执行,直到碰到一个yield就暂停住,该yield后的值就当做本次next返回值
# 如果yield后面没值,则返回None
print(g.__next__())  # 依次取值,直到遇到抛出StopIteration,说明该对象中的值已经被取完了

'''
总结yield功能:
    1、提供了一种自定义迭代器的方式
    2、可以用于返回值
        yield与return的区别:
            相同点:都可以用于返回值,个数及类型都没有限制
            不同点:yield可以返回多次值,return只能返回一次,整个函数就结束了
    3、函数暂停以及继续执行的状态是由yield保存的
'''

# 练习:自定义生成器,模拟range方法

def my_range(start,stop,step=1):
    while True:
        if start >= stop:
            break
        yield start
        start+=step


for item in my_range(1, 5, 2):
    print(item)

十一、面向过程编程

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

2、定义
面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么
基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式

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

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

5、应用:扩展性要求不高的场景,典型案例如Linux内核。git,httpd
'''
原文地址:https://www.cnblogs.com/lichunke/p/9317366.html