python基础之函数对象、嵌套、闭包、装饰器、迭代器、生成器、递归、二分法、生成式


一、函数对象

  可以把函数当成变量去用

# 1、可以赋值
# f=func
# print(f,func)
# f()
 
# 2、可以当做参数传给另外一个函数
# def foo(x): # x = func的内存地址
#     # print(x)
#     x()
#
# foo(func) # foo(func的内存地址)
 
# 3、可以当做另外一个函数的返回值
# def foo(x): # x=func的内存地址
#     return x # return func的内存地址
#
# res=foo(func) # foo(func的内存地址)
# print(res) # res=func的内存地址
#
# res()
 
# 4、可以当做容器类型的一个元素
# l=[func,]
# # print(l)
# l[0]()
 
# dic={'k1':func}
# print(dic)
# dic['k1']()

二、函数的嵌套

  函数的嵌套:在调用一个函数的过程中又调用其他函数

  语法:

    def f1():

      def f2():         pass   应用:
# 求圆形的求周长:2*pi*radius
def circle(radius,action=0):

    from math import pi
    def perimiter(radius):
        return 2*pi*radius
 
    # 求圆形的求面积:pi*(radius**2)
    def area(radius):
        return pi*(radius**2)
 
    if action == 0:
        return 2*pi*radius
 
    elif action == 1:
        return area(radius)
 
circle(33,action=0)

三、闭包函数

  闭包函数=名称空间与作用域+函数嵌套+函数对象。"闭"函数指的该函数是内嵌函数。"包"函数指的该函数包含对外层函数作用域名字的引用(不是对全局作用域)

  1.定义:内部函数包含对外部作用域而非全局作用域名字的引用,该内部函数称为闭包函数。函数里套有其他函数的形式,两层以上的

#完整的闭包函数结构,嵌套调用
def func():
    name = 'aaa'
    def inner():
        print(name)
    return inner

f = func()
f()  

  2.应用:两种为函数体传参的方式

#方式一:直接传值
def index(x,y):
     print(x,y)
#方式二:用闭包函数
def
f1(x): # x=3 x=3 def f2(): print(x) #结果为3 return f2 x=f1(3) print(x) #此时x为函数f2的内存地址 x() #调用x,

  3、用于爬虫

import requests
 
# 传参的方案一:
# def get(url):
#     response=requests.get(url)
#     print(len(response.text))
#
# get('https://www.baidu.com')
# get('https://www.cnblogs.com/linhaifeng')
# get('https://zhuanlan.zhihu.com/p/109056932')
 
 
# 传参的方案二:
def outter(url):
    # url='https://www.baidu.com'
    def get():
        response=requests.get(url)
        print(len(response.text))
    return get
 
baidu=outter('https://www.baidu.com')
baidu()
 
cnblogs=outter('https://www.cnblogs.com/linhaifeng')
cnblogs()
 
zhihu=outter('https://zhuanlan.zhihu.com/p/109056932')
zhihu()

四、装饰器

  本质:装饰器的本质:一个闭包函数(可调用的对象)

  功能:装饰器的功能:在不修改原函数及其调用方式的情况下对原函数功能进行扩展(不修改被装饰对象的源代码和调用方式)

无参装饰器:

  1、装饰器固定格式

#无参装饰器固定模板
def
timer(func): def inner(*args,**kwargs): '''执行函数之前要做的''' re = func(*args,**kwargs) '''执行函数之后要做的''' return re return inner

  2、简单的装饰器

"""
1.统计index函数执行的时间
"""
import time
def index():
    time.sleep(3)
    print('吃起吃起!')

def outter(func):  # func = 最原始的index函数的内存地址
    def get_time():
        start = time.time()
        func()                  # func = index函数的内存地址() 直接调用,此时直接调用index函数
        end = time.time()
        print('index run time:%s'%(end-start))
    return get_time
index = outter(index)  #outter(最原始的index函数内存地址) ,相当于给outter传入index函数

# index指向get_time函数的内存地址,调用index就是执行get_time函数
index()

  3、装饰器语法糖

    @ 符号就是装饰器的语法糖。语法糖在书写的时候应该与被装饰对象紧紧挨着 两者之间不要有空格  

"""
1.统计index函数执行的时间
"""
import time
def outter(func):  # func = 最原始的index函数的内存地址
    def get_time():
        start = time.time()
        func()                  # func = index函数的内存地址() 直接调用,此时直接调用index函数
        end = time.time()
        print('index run time:%s'%(end-start))
    return get_time

@outter       #相当于index = outter(index)
def index():
    time.sleep(3)
    print('吃起吃起')
index()          #此时调用被装饰器装饰的函数。结果输出就会给函数加上装饰器功能。

  4、叠加多个装饰器

    装饰器在加载的时候  :自下而上
    装饰器在执行的时候 : 函数调用前的,自上而下,函数调用后的,自下而上

def deco1(func1): # func1 = wrapper2的内存地址
    def wrapper1(*args,**kwargs):
        print('正在运行1')
        res1=func1(*args,**kwargs)
        print('正在运行7')
        return res1
    return wrapper1

def deco2(func2): # func2 = wrapper3的内存地址
    def wrapper2(*args,**kwargs):
        print('正在运行2')
        res2=func2(*args,**kwargs)
        print('正在运行6')
        return res2
    return wrapper2

def deco3(func3):
        def wrapper3(*args,**kwargs):
            print('正在运行3')
            res3=func3(*args,**kwargs)
            print('正在运行5')
            return res3
        return wrapper3



# 加载顺序自下而上(了解)
@deco1
@deco2
@deco3
def index(x,y):
    print('正在运行4')
    print('from index %s:%s' %(x,y))
index(1,2) # wrapper1(1,2)

'''执行结果
正在运行1
正在运行2
正在运行3
正在运行4
from index 1:2
正在运行5
正在运行6
正在运行7
'''

有参装饰器

  1、固定格式

def 有参装饰器名字(x,y,z):    (括号里面为要传的参数)
    def outter(func): 
        def wrapper(*args, **kwargs):     #括号接收参数
            res = func(*args, **kwargs)      
            return res
        return wrapper
    return outter
 
@有参装饰器(1,y=2,z=3)
def 被装饰对象():
    pass

  2、语法糖

def auth(db_type):
    def deco(func):
        def wrapper(*args, **kwargs):         #因为下面func需要使用参数,所以这里需要有
            name = input('your name>>>: ').strip()
            pwd = input('your password>>>: ').strip()

            if db_type == 'file':
                print('基于文件的验证')
                if name == 'aaa' and pwd == '123':
                    res = func(*args, **kwargs)  # index(1,2)    #与*args, **kwargs接收参数一样,但实质来自index的参数
                    return res
                else:
                    print('user or password error')
            elif db_type == 'mysql':
                print('基于mysql的验证')
            elif db_type == 'ldap':
                print('基于ldap的验证')
            else:
                print('不支持该db_type')
        return wrapper
    return deco


@auth(db_type='file')  # @deco # index=deco(index) # index=wrapper
def index(x, y):
    print('index->>%s:%s' % (x, y))


@auth(db_type='mysql')  # @deco # home=deco(home) # home=wrapper
def home(name):
    print('home->>%s' % name)


@auth(db_type='ldap')  # 账号密码的来源是ldap
def transfer():
    print('transfer')

index(1, 2)
'''
执行结果
your name>>>: aaa
your password>>>: 123
基于文件的验证
index->>1:2
'''
#home('aaa')
#transfer()

装饰器之偷梁换柱

#偷梁换柱,即将原函数名指向的内存地址偷梁换柱成wrapper函数
#        所以应该将wrapper做的跟原函数一样才行
from functools import wraps
 
def outter(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """这个是主页功能"""
        res = func(*args, **kwargs) # res=index(1,2)
        return res
 
    # 手动将原函数的属性赋值给wrapper函数
    # 1、函数wrapper.__name__ = 原函数.__name__
    # 2、函数wrapper.__doc__ = 原函数.__doc__
    # wrapper.__name__ = func.__name__
    # wrapper.__doc__ = func.__doc__
 
    return wrapper
 
 
 
@outter # index=outter(index)
def index(x,y):
    """这个是主页功能"""
    print(x,y)
 
 
print(index.__name__)
print(index.__doc__) #help(index)实际实行wrapper函数

五、迭代器

  定义:迭代器指的是迭代取值的工具,迭代是一个重复的过程,每次重复都是基于上一次的结果而继续的,单纯的重复并不是迭代。迭代器是用来迭代取值的工具,而涉及到把多个值循环取出来的类型。有:列表、字符串、元组、字典、集合、打开文件。

  1、可迭代的对象:但凡内置有__iter__方法的都称之为可迭代的对象。列表、字符串、元组、字典、集合、打开文件。

# s1=''
# # s1.__iter__()
#
# l=[]
# # l.__iter__()
#
# t=(1,)
# # t.__iter__()
#
# d={'a':1}
# # d.__iter__()
#
# set1={1,2,3}
# # set1.__iter__()
#
# with open('a.txt',mode='w') as f:
#     # f.__iter__()
#     pass

  2、调用可迭代对象下的__iter__方法会将其转换成迭代器对象

d = {'a': 1, 'b': 2, 'c': 3}
d_iterator = d.__iter__()
print(d_iterator)           #迭代器对象   

print(d_iterator.__next__())         #a,.__next__()是对迭代器对象取值
print(d_iterator.__next__())         #b
print(d_iterator.__next__())         #c
print(d_iterator.__next__())         #没值了,抛出异常StopIteration

    循环取值,自动退出

# l=[1,2,3,4,5]
# l_iterator=l.__iter__()
#
# while True:
#     try:
#         print(l_iterator.__next__())
#     except StopIteration:
#         break

  3、可迭代对象与迭代器区别

    3.1 可迭代对象("可以转换成迭代器的对象"):内置有__iter__方法对象,如列表、字符串、元组、字典、集合、打开文件。可称为可迭代对象       可迭代对象.__iter__():     得到迭代器对象       3.2 迭代器对象:内置有__next__方法并且内置有__iter__方法的对象,文件对象可为迭代器对象       迭代器对象.__next__():得到迭代器的下一个值       迭代器对象.__iter__():得到迭代器的本身。与没调用一样
 dic={'a':1,'b':2,'c':3}   #可迭代对象

dic_iterator=dic.__iter__()    #迭代器对象
print(dic_iterator is dic_iterator.__iter__().__iter__().__iter__())

  4、for循环的工作原理

    for循环可以称之为叫迭代器循环

# 1、d.__iter__()得到一个迭代器对象
# 2、迭代器对象.__next__()拿到一个返回值,然后将该返回值赋值给k
# 3、循环往复步骤2,直到抛出StopIteration异常for循环会捕捉异常然后结束循环
# for k in d:
#     print(k)
 
 
# with open('a.txt',mode='rt',encoding='utf-8') as f:
#     for line in f: # f.__iter__()
#         print(line)
 

迭代器总结:

#迭代器优缺点总结
# 优点:
# I、为序列和非序列类型提供了一种统一的迭代取值方式。
# II、惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
 
# 6.2 缺点:
# I、除非取尽,否则无法获取迭代器的长度
# II、只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

六、生成器

  人为自定义实现迭代的功能就叫生成器 ,即自定义的迭代器。

 1、关键字:yield  

    函数内如果有yield关键字,那么加括号执行函数的时候不会触发函数体代码的运行 ,但在程序中遇到yield会暂停运行,等待调入参数 。将yield后的值当做本次调用的结果返回

def func():
    print('one')
    yield  12   #程序调用函数执行到 yeild 后就会终止,将值12作为结果返回。后面代码不会执行
    print('two')
    yield
    print('three')

res=func()
print(res.__next__())        #此时取到yeild返回的值,如果无值,返回None
'''
结果
one
12
'''

  例子:

#实现自定义range原理
def my_range(start, stop, step):
    # print('start...')
    while start < stop:
        yield start
        start += step
    # print('end....')


# g=my_range(1,5,2) # 1 3代用my_range相当于循环执行next方法取值,
# print(next(g))
# print(next(g))
# print(next(g))

for n in my_range(1, 7, 2):
    print(n)
'''
结果为:1 3 5
'''

总结:有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值

  2、生成器表达式

    把列表解析的 [] 换成 () 得到的就是生成器表达式

res=[i for i in range(1,10) if i!=4]
print(res)
#[1, 2, 3, 5, 6, 7, 8, 9]

res1=(i for i in range(1,10000) if i!=4) #生成器表达式
print(res1)
# next=res.__iter__()
print(res1.__next__())
print(res1.__next__())
print(res1.__next__())
print(res1.__next__())# 必须通过触发—next—才会触发,有几个就取几个值

生成器例子:

def demo():
    for i in range(4):
        yield i              # i=[0,1,2,3]  # 遇到—next--才会触发执行 

g=demo()

g1=(i for i in g)  #  for 循环取值赋值给g1,已经取完i中的内存地址
g2=(i for i in g1)

print(list(g1))
print(list(g2))

结果:
>>>[0,1,2,3]
>>>[]
def add(n,i):
    return n+i
def test():
    for i in range(4):
        yield i
g=test()

for n in [1,10]:
    g=(add(n,i) for i in g)
    # 第一次for循环g=(add(n,i) for i in test())
    # 第二次for循环g=(add(n,i) for i in (add(n,i) for i in test()))
print(n)      #10
print(g.__next__())      #此时g对应值第一个值为20
res=list(g)    #再次循环
print(res)          #[20, 21, 22, 23]

"""
for i in (add(10,i) for i in test()):  会执行所有的生成器内部的代码
    add(n,i)
"""

七、递归

  如果函数中包含了对其自身的调用,该函数就是递归的;递归(Recursion),在数学与计算机科学中,是指在函数的定义中使用函数自身的方法;

  1、递归的实现

# 非递归实现阶乘
def factorial(n):
    res = 1
    for i in range(2, n+1):
        res *= i
    return res

print(factorial(5))
# 120

# 递归实现阶乘
def factorial(n):
    if n == 0 or n == 1:
        return 1
    else:
        return (n * factorial(n - 1))             #此时调用了factorial函数

print(factorial(5))
# 120

  2、递归函数:在函数体内再调用函数本身(简单的说就是多次甚至无限的循环)但不能超过递归最大深度,递归的最大深度——997

    实现过程:1:回溯:就是一次次重复的过程,重复的过程必须在逐渐的降低问题的复查程度,直到找到一个最终的结束条件。2:递归:就是拿到条件后 ,一次次往回推导的过程

def age(n):
    if n==1: # 第一个开始回溯的人
        return 18 # 结束的条件
    else:
        return age(n-1)+2 #递归的规律
res=age(10)
print(res)

  七.二、算法之二分法:所谓的算法就是提高效率的方法,    二分法:就是容器内必须有一定的大小顺序,以中间数为基准寻找规律,分段比较左右对比

l=[1,4,8,9,14,25,48,58,124,236,485,489,520,1234,4526,7852]
def func(l,aim):
    mid=(len(l)-1)//2 # 先取列表的中间值
    if l:
        if aim>l[mid]:
            func(l[mid+1:],aim) # 按索引的方式取值
        elif aim<l[mid]:
            func(l[:mid],aim)
        elif aim==l[mid]:
            print(aim)
        else:
            print('找不到')

func(l,124)   # 124是要找的数
func(l,8)    #  8是分段寻找 前后取出在对比

  七.三、三元表达式

    固定格式

       值1 if 条件 else 值2
                条件成立 值1
                条件不成立 值2

#平常想法
def my_max(x,y):
    if x>y:
        return x
    else:
        return y
res=my_max(1,9)
print(res)


#用三元表达式
x=1
y=9
res=x if x>y else y
print(res)

>>9

  七.四 列表表达式

l=['tank','nick','oscar','topa','kevin','jamse']
k=[]
for name in l:
    k .append('%s_boy'%name) # k.append(name+'boy')但不推荐使用,pycharm里效率极低
print(k)
>>['tank_boy', 'nick_boy', 'oscar_boy', 'topa_boy', 'kevin_boy', 'jamse_boy']

#用列表生成式
l=['tank','nick','oscar','topa','kevin','jamse']

res=['%s_boy'%name for name in l]
print(res)
#结果同上

  七.五字典生成式

    内部都是基于 for  循环   以 key:vlaue 键值对的形式生成列表 list,关键字 enumerate

#d = {key: value for (key, value) in iterable}
d1 = {'x': 1, 'y': 2, 'z': 3}
d2 = {k: v for (k, v) in d1.items ()}
print (d2)