Python——函数

第一篇:函数的基本使用

一、定义

具备某一功能的可执行程序,函数必须遵循先定义后调用的原则。

二、定义函数

def 函数名(参数1,参数2,...):
    """文档描述"""
    函数体
    return 值
  1. def: 定义函数的关键字;

  1. 函数名:函数名指向函数内存地址,是对函数体代码的引用。函数的命名应该反映出函数的功能;

  1. 括号:括号内定义参数,参数是可有可无的,且无需指定参数的类型;

  1. 冒号:括号后要加冒号,然后在下一行开始缩进编写函数体的代码;

  1. “””文档描述”””: 描述函数功能,参数介绍等信息的文档,非必要,但是建议加上,从而增强函数的可读性;

  1. 函数体:由语句和表达式组成;

  1. return 值:定义函数的返回值,return是可有可无的。

参数是函数调用者向函数传值的媒介,若函数体代码逻辑依赖外部传来的参数时,则需要定义为有参函数。

def my_min(x,y):
    res=x if x < y else y
    return res

否则定义为无参函数

def interactive():
    user=input('user>>: ').strip()
    pwd=input('password>>: ').strip()
    return (user,pwd)

三、pass

函数体为pass代表什么都不做,称之为空函数。定义空函数通常是有用的,用pass充当占位符,在哦我们构思程序框架体系的时候,通常先定义为空函数。

def auth_user():
    """user authentication function"""
    pass

def download_file():
    """download file function"""
    pass

def upload_file():
    """upload file function"""
    pass

def ls():
    """list contents function"""
    pass

def cd():
    """change directory"""
    pass

四、调用函数与函数返回值

函数的使用分为定义阶段和调用阶段,定义函数的时候只检测语法,不执行函数体代码,函数名+()才表示函数的调用,只有调用函数时才会执行函数体代码。

return后面是函数体代码执行完的返回值,return可以将多个值返回并放到一个元组内,return 是函数执行结束的标志,一个函数可以有多个return 但是只会执行一个return,执行一次函数就会结束并把return 后面定义的值作为本次函数调用的结果返回。

>>> def test(x,y,z):
...     return x,y,z #等同于return (x,y,z)
... 
>>> res=test(1,2,3)
>>> print(res)
(1, 2, 3)

第二篇:函数的参数

一、形参与实参的介绍

1.形参是在定义函数时,函数名后面的括号里声明的参数。形参的本质就是一个变量名,需要被赋值。

2.实参是在调用函数时,函数名后面的括号里传入的值:常量变量表达式或者三者的组合形式。

  1. 在调用有参函数时,实参会赋值给形参。因为在python中变量名和变量值只是单纯的绑定关系,在函数中这种绑定关系会在函数时生效,在函数调用结束后解除绑定。

二、位置参数

位置形参:在定义函数时,按照从左到右的方式依次定义的形参,这样定义的定义的形参都需要被赋值。

位置实参:在调用函数时,按照从左到右的方式依次放入括号内的实参,位置实参必须与位置形参一一对应,为位置形参赋值。

三、关键字实参

在函数调用阶段,以key=value键对形式出现的实参称为关键字实参,这种实参可以不按照位置与形参一一对应传值,也可以与位置实参混合用,但关键字实参必须在位置参数后面而且不可以对一个形参重复赋值。

四、默认形参

在定义函数阶段就已经为形参赋值。默认形参必须在位置形参之后,默认形参的值只能在函数定义阶段被赋值一次,最好是不可变类型。

五、可变长参数(*args和**kwargs 的用法)

可变长度的位置参数

如果在最后一个形参名为*args,在调用函数时,溢出的位置参数都会被*args接收,以元组的形式保存下来赋给该形参。

>>> def foo(x,y,z=1,*args): #在最后一个形参名args前加*号
...     print(x)
...     print(y)
...     print(z)
...     print(args)
... 
>>> foo(1,2,3,4,5,6,7)  #实参1、2、3按位置为形参x、y、z赋值,多余的位置实参4、5、6、7都被*接收,以元组的形式保存下来,赋值给args,即args=(4, 5, 6,7)

1
2
3
(4, 5, 6, 7)

如果事先生成一个列表也是可以传递给*args的,需要这样传:

这里其实经历了这样一个过程,*L是将列表里的元素打散,*就相当于起了一个打散的作用,传递给*args,然后*args再将这些参数返回成一个元组。如果L前不加*就是一个普通列表型参数。

>>> def foo(x,y,*args):
...     print(x)
...     print(y)
...     print(args)
... 
>>> L=[3,4,5]
>>> foo(1,2,*L) # *L就相当于位置参数3,4,5, foo(1,2,*L)就等同于foo(1,2,3,4,5)
1
2
(3, 4, 5)

可变长度的关键字参数

如果在最后一个形参名前加号,那么在调用函数时,溢出的关键字参数,都会被接收,以字典的形式保存下来赋值给该形参

>>> def foo(x,**kwargs): #在最后一个参数kwargs前加**
...     print(x)        
...     print(kwargs)   
... 
>>> foo(y=2,x=1,z=3) #溢出的关键字实参y=2,z=3都被**接收,以字典的形式保存下来,赋值给kwargs
1
{'z': 3, 'y': 2}

如果我们事先生成了一个字典,仍然是可以传值给**kwargs的

>>> def foo(x,y,**kwargs):
...     print(x)
...     print(y)
...     print(kwargs)
... 
>>> dic={'a':1,'b':2} 
>>> foo(1,2,**dic) #**dic就相当于关键字参数a=1,b=2,foo(1,2,**dic)等同foo(1,2,a=1,b=2)
1
2
{'a': 1, 'b': 2}

注意:如果在传入dic时没有加**,那dic就只是一个普通的位置参数了。如果形参为常规参数(位置或默认),实参仍可以是**的形式

>>> def foo(x,y,z=3):
...     print(x)
...     print(y)
...     print(z)
... 
>>> foo(**{'x':1,'y':2}) #等同于foo(y=2,x=1)
1
2
3

命名关键字参数

需要在定义形参时,用作为一个分隔符号,*号之后的形参称为命名关键字参数。对于这类参数,在函数调用时,必须按照key=value的形式为其传值,且必须被传值

>>> def register(name,age,*,sex,height): #sex,height为命名关键字参数
...     pass
... 
>>> register('lili',18,sex='male',height='1.8m') #正确使用
>>> register('lili',18,'male','1.8m') # TypeError:未使用关键字的形式为sex和height传值
>>> register('lili',18,height='1.8m') # TypeError没有为命名关键字参数height传值。

命名关键字参数也可以有默认值,从而简化调用

>>> def register(name,age,*,sex='male',height):
...     print('Name:%s,Age:%s,Sex:%s,Height:%s' %(name,age,sex,height))
... 
>>> register('lili',18,height='1.8m')
Name:lili,Age:18,Sex:male,Height:1.8m

需要强调的是:sex不是默认参数,height也不是位置参数,因为二者均在*后,所以都是命名关键字参数,形参sex=’male’属于命名关键字参数的默认值,因而即便是放到形参height之前也不会有问题。另外,如果形参中已经有一个args了,命名关键字参数就不再需要一个单独的*作为分隔符号了

>>> def register(name,age,*args,sex='male',height):
...   print('Name:%s,Age:%s,Args:%s,Sex:%s,Height:%s' %(name,age,args,sex,height))
... 
>>> register('lili',18,1,2,3,height='1.8m') #sex与height仍为命名关键字参数
Name:lili,Age:18,Args:(1, 2, 3),Sex:male,Height:1.8m

组合使用

所有参数可任意组合使用,但定义顺序必须是:位置参数、默认参数、*args、命名关键字参数、**kwargs。

可变参数args与关键字参数kwargs通常是组合在一起使用的,如果一个函数的形参为args与kwargs,那么代表该函数可以接收任何形式、任意长度的参数

在该函数内部还可以把接收到的参数传给另外一个函数

>>> def func(x,y,z):
...     print(x,y,z)
... 
>>> def wrapper(*args,**kwargs):
...     func(*args,**kwargs)
...
>>> wrapper(1,z=3,y=2)
1 2 3

按照上述写法,在为函数wrapper传参时,其实遵循的是函数func的参数规则,调用函数wrapper的过程分析如下:

  1. 位置实参1被*接收,以元组的形式保存下来,赋值给args,即args=(1,),关键字实参z=3,y=2被**接收,以字典的形式保存下来,赋值给kwargs,即kwargs={‘y’: 2, ‘z’: 3}

  1. 执行func(args,kwargs),即func(*(1,),** {‘y’: 2, ‘z’: 3}),等同于func(1,z=3,y=2)

第三篇:名称空间与作用域

一、名称空间

名称空间即存放名字与对象印射/绑定关系的地方。可以理解为存放变量值与变量名绑定关系。

内置名称空间

伴随python解释器的启动/关闭而产生/回收,因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内置函数名。

全局名称空间

伴随python文件的执行/关闭而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于全局名称空间。

局部名称空间

伴随函数调用结束而产生回收大的名字都放于局部名称空间,函数的形参,函数内定义的名字都会存放于该名称空间中。

名称空间的加载顺序

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

二、作用域

全据作用域和局部作用域

将三个名称空间划分为两个区域:全局作用域包括内置名称空间,全局名称空间;局部作用域包括局部名称空间

作用域与名字查找的优先级

1.在局部作用域查找名字时:局部名称空间==》全局名称空间==》》内置名称空间==》抛出异常

2.在全局作用域查找名字时:全局名称空间==》》内置名称空间==》抛出异常

3.在内嵌函数内查找名字时:自己局部作用域==》》一层层找外部嵌套函数定义的作用域==》》全局作用域

x=1
def outer():
    x=2
    def inner(): # 函数名inner属于outer这一层作用域的名字
        x=3
        print('inner x:%s' %x)

    inner()
    print('outer x:%s' %x)

outer() 
#结果为
inner x:3
outer x:2

4,一个函数无论内部嵌套多少层函数,都可以查看到全局作用域的名字,若要在函数内修改全局名称空间中名字的值,当值为不可变类型时,则要用global关键字

x=1
def foo():
    global x #声明x为全局名称空间的名字
    x=2
foo()
print(x) #结果为2

当实参的值为可变类型时,函数体内对该值的修改将直接反应到原值,

num_list=[1,2,3]
def foo(nums):
    nums.append(5)

foo(num_list)
print(num_list)
#结果为
[1, 2, 3, 5]

5.对于嵌套多层的函数,使用nonlocal关键字可以将名字声明为来自外部嵌套函数定义的作用域(注意:非全局作用域中的名字),nonlocal x会从当前函数的外层函数开始一层层去查找名字x,若是一直到最外层函数都找不到,则会抛出异常。

def  f1():
    x=2
    def f2():
        nonlocal x
        x=3
    f2() #调用f2(),修改f1作用域中名字x的值
    print(x) #在f1作用域查看x

f1()

#结果
3

第四篇:函数对象与闭包

一、函数对象

函数名可以被引用:可以将函数名赋值给一个变量

函数名可以作为容器的元素

函数名可以作为参数传入另一个函数

函数名可以作为另一个函数的返回值

二、闭包函数

基于函数对象的概念,可以将函数调到任意位置去调用,但作用域的关系是在定义函数时就已经被确定了的,与函数的调用位置无关。函数被当作数据处理时,始终以自带的作用域为准。

若内嵌函数包含对外部函数作用域(非全局作用域)中变量的引用,那么该内嵌函数就称为闭包函数。

x=1

def f1():
    def f2():
        print(x)

    return f2

def f3():
    x=3
    f2=f1() #调用f1()返回函数f2
    f2() #需要按照函数定义时的作用关系去执行,与调用位置无关

f3() #结果为1
x=1
def outer():
    x=2
    def inner():
        print(x)
    return inner

func=outer()
func() # 结果为2

可以通过函数的closure属性,查看闭包函数所有包裹的外部变量

>>> func.__closure__
(<cell at 0x10212af78: int object at 0x10028cca0>,)
>>> func.__closure__[0].cell_contents
2

“闭”代表函数是内部的,“包”代表函数外’包裹’着对外层作用域的引用。因而无论在何处调用闭包函数,使用的仍然是包裹在其外层的变量。

第五篇:装饰器

定义

在不改变被装饰对象的源代码和调用方式的基础之上,为被装饰对象添加新的功能。

装饰器的实现

无参装饰器的实现

如果想为下述函数添加统计其执行时间的功能

import time

def index():
    time.sleep(3)
    print('Welcome to the index page’)
    return 200

index() #函数执行

遵循不修改被装饰对象源代码的原则,我们想到的解决方法可能是这样

start_time=time.time()
index() #函数执行
stop_time=time.time()
print('run time is %s' %(stop_time-start_time))

考虑到还有可能要统计其他函数的执行时间,于是我们将其做成一个单独的工具,函数体需要外部传入被装饰的函数从而进行调用,我们可以使用参数的形式传入

def wrapper(func): # 通过参数接收外部的值
    start_time=time.time()
    res=func()
    stop_time=time.time()
    print('run time is %s' %(stop_time-start_time))
    return res

但之后函数的调用方式都需要统一改成

wrapper(index)
wrapper(其他函数)

这便违反了不能修改被装饰对象调用方式的原则,于是我们换一种为函数体传值的方式,即将值包给函数,如下

def timer(func):
    def wrapper(): # 引用外部作用域的变量func
        start_time=time.time()
        res=func()
        stop_time=time.time()
        print('run time is %s' %(stop_time-start_time))
  
return res
    return wrapper

这样我们便可以在不修改被装饰函数源代码和调用方式的前提下为其加上统计时间的功能,只不过需要事先执行一次timer将被装饰的函数传入,返回一个闭包函数wrapper重新赋值给变量名 /函数名index,如下

index=timer(index)  #得到index=wrapper,wrapper携带对外作用域的引用:func=原始的index
index() # 执行的是wrapper(),在wrapper的函数体内再执行最原始的index

至此我们便实现了一个无参装饰器timer,可以在不修改被装饰对象index源代码和调用方式的前提下为其加上新功能。但我们忽略了若被装饰的函数是一个有参函数,便会抛出异常

def home(name):
    time.sleep(5)
    print('Welcome to the home page',name)

home=timer(home)
home('egon')

#抛出异常
TypeError: wrapper() takes 0 positional arguments but 1 was given

之所以会抛出异常,是因为home(‘egon’)调用的其实是wrapper(‘egon’),而函数wrapper没有参数。wrapper函数接收的参数其实是给最原始的func用的,为了能满足被装饰函数参数的所有情况,便用上*args+**kwargs组合(见4.3小节),于是修正装饰器timer如下

def timer(func):
    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

此时我们就可以用timer来装饰带参数或不带参数的函数了,但是为了简洁而优雅地使用装饰器,Python提供了专门的装饰器语法来取代index=timer(index)的形式,需要在被装饰对象的正上方单独一行添加@timer,当解释器解释到@timer时就会调用timer函数,且把它正下方的函数名当做实参传入,然后将返回的结果重新赋值给原函数名

@timer # index=timer(index)
def index():
    time.sleep(3)
    print('Welcome to the index page')
    return 200
@timer # index=timer(home)
          def home(name):
    time.sleep(5)
    print('Welcome to the home page’,name)

如果我们有多个装饰器,可以叠加多个

@deco3
@deco2
@deco1
def index():
    pass

叠加多个装饰器也无特殊之处,上述代码语义如下:

index=deco3(deco2(deco1(index)))

有参装饰器的实现

了解无参装饰器的实现原理后,我们可以再实现一个用来为被装饰对象添加认证功能的装饰器,实现的基本形式如下

def deco(func):
    def wrapper(*args,**kwargs):
        编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
    return wrapper

如果我们想提供多种不同的认证方式以供选择,单从wrapper函数的实现角度改写如下

  def deco(func):
        def wrapper(*args,**kwargs):
            if driver == 'file':
                编写基于文件的认证,认证通过则执行res=func(*args,**kwargs),并返回res
            elif driver == 'mysql':
                编写基于mysql认证,认证通过则执行res=func(*args,**kwargs),并返回res
        return wrapper

函数wrapper需要一个driver参数,而函数deco与wrapper的参数都有其特定的功能,不能用来接受其他类别的参数,可以在deco的外部再包一层函数auth,用来专门接受额外的参数,这样便保证了在auth函数内无论多少层都可以引用到

def auth(driver):
    def deco(func):
        ……
    return deco

此时我们就实现了一个有参装饰器,使用方式如下

# 先调用auth_type(driver='file'),得到@deco,deco是一个闭包函数,包含了对外部作用域名字driver的引用,@deco的语法意义与无参装饰器一样
@auth(driver='file')
def index():
   pass

@auth(driver='mysql')
def home():
   pass

可以使用help(函数名)来查看函数的文档注释,本质就是查看函数的doc属性,但对于被装饰之后的函数,查看文档注释

@timer
def home(name):
    '''
    home page function
    :param name: str
    :return: None
    '''
    time.sleep(5)
    print('Welcome to the home page',name)

print(help(home))
'''
打印结果:

Help on function wrapper in module __main__:

wrapper(*args, **kwargs)

None
'''

在被装饰之后home=wrapper,查看home.name也可以发现home的函数名确实是wrapper,想要保留原函数的文档和函数名属性,需要修正装饰器

def timer(func):
    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
    wrapper.__doc__=func.__doc__
    wrapper.__name__=func.__name__
    return wrapper

按照上述方式来实现保留原函数属性过于麻烦,functools模块下提供一个装饰器wraps专门用来帮我们实现这件事,用法如下

from functools import wraps

def timer(func):
    @wraps(func)
    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

第六篇:迭代器

迭代是什么

迭代是一个重复反馈的过程,每一次开始都依赖于上一次的结果。

可迭代对象

字符串、列表、元组、字典、集合、打开的文件

迭代器对象

可迭代对象调用iter方法,使其具有iter方法和next方法,执行next方法会计算出迭代器中的下一个值

s={1,2,3} # 可迭代对象s
i=iter(s)  # 本质就是在调用s.__iter__(),返回s的迭代器对象i,
print(next(i))# 本质就是在调用i.__next__()
print(next(i))
print(next(i))

for循环的内部原理

goods=['mac','lenovo','acer','dell','sony']
i=iter(goods) #每次都需要重新获取一个迭代器对象
while True:
    try:
        print(next(i))
    except StopIteration: #捕捉异常终止循环
        break

第七篇:生成器

yield

当函数中存在yield关键字,在函数还没有执行的运行的时候只是一个普通的函数,当函数运行的时候就是一个生成器。

def a():
    print('a')
    yield 1
    print('b')
    yield 2
    print('c')
    yield 3
    print('d')
    yield 4
b = a()

此时b就是一个生成器,调用它的next方法,会运行函数体代码到 yield 1 位置挂起,再调用一次next方法会运行函数体代码到yield 2位置挂起。

print(b.__next__())
print(b.__next__())
print(b.__next__())
print(b.__next__())
===》
a
1
b
2
c
3
d
4

生成器具有iter和next方法其本质就是一个迭代器。

生成器案例:实现range

range(1, 10)
for i in range(1, 10):
    print(i)
    
for i in my_range(1, 10):
    print(i)
    
    
def my_range(start, stop=None, step=1):
    if start < stop:
        if not stop:
            stop = start
            start=0
        while start < stop:  # 0<stop
            yield start
            start += step
    else:
        return '好好传值,别乱来'

# for i in my_range(1, 10):
for i in my_range(50, 10):
    print(i)

yield与return的对比

yield
    1. 可以有返回值
    2. 函数遇到yield不会结束,只会'停住'
    3. yield关键字把函数变成了生成器,支持迭代取值了
return
    1. 可以有返回值
    2. 遇到return关键字直接结束函数运行

生成器表达式


    g = (add(n, i) for i in g)
    """
        第一次循环:
            g = (add(1, i) for i in g)
        第二次循环:
            g = (add(n, i) for i in (add(n, i) for i in g)) #这里注意g,还有n值的变化
    """
res = list(g)
print(res)

# A. res=[10,11,12,13]
# B. res=[11,12,13,14]
# C. res=[20,21,22,23]
# D. res=[21,22,23,24]



def add(n, i):  # 普通函数 返回两个数的和  求和函数
    return n + i

def test():  # 有yield,是生成器1
    for i in range(4):
        yield i

g = test()  # 激活生成器1  # [0, 1, 2, 3]
print(g)
for n in [1, 10]:
    g = (add(n, i) for i in g)  # 新生成器2

    """
        第一次循环:n=1
            g = (add(n, i) for i in g)  # 重新将一个生成器赋值给g
            但是没有调用,所以没有执行
            如果循环只有一次,直接赋值,
            list(g)的输出结果是:[1,2,3,4]
        第二次循环:
            g = (add(n, i) for i in (add(n, i) for i in g))
            '''
            可以将上述语句写成:
            g1 = test()
            g2 = (add(n, i) for i1 in g1)
            g = (add(n, i) for i2 in g2)
            '''
            但是没有调用,所以没有执行
            如果循环有两次,赋值n=10, i1=[0,1,2,3],i2=[10,11,12,13]
            list(g)的输出结果是:[20,21,22,23]
        第三次循环
            g = (add(n, i) for i in (add(n, i) for i in (add(n, i) for i in g)))
            '''
            可以将上述语句写成:
            g1 = test()
            g2 = (add(n, i) for i1 in g1)
            g3 = (add(n, i) for i2 in g2)
            g = (add(n, i) for i3 in g3)
            '''
            但是没有调用,所以没有执行
            如果循环有三次,赋值n=20, i1=[0,1,2,3],i2=[20,21,22,23],i3=[40,41,42,43]
            list(g)的输出结果是:[60,61,62,63]
    """
    '''循环结束之后,才有调用语句,才开始赋值'''
res = list(g)  # 激活新生成器2
print(res)

三元表达式

结果 if 条件 else 条件不成功之后的结果
    使用场景:结果二选一的情况
"""

a = 1
b = 10

c = 10
d = 20
res=a if a > b else (c if c > d else ( 'bbb' if d > c else 'aaa'))
print(res)

# is_beautiful=True
# res = '漂亮' if is_beautiful else '不漂亮'
# print(res)

cmd = input('请选择是否输入:(y/n)')
res='继续' if cmd == 'y' else '不继续'
print(res)

二分法

# 算法:做事的方法, 为了提高效率

# 二分法的使用
# eg:
l = [1, 22, 44 ,10, 3, 45, 66, 88,101, 20, 30 ,40]
# 判断这个列表中是否有20这个数字
'''二分法的原则:1、 列表中得数字必须要有序,不管是升序还是降序都可以,如果没有顺序就不能使用二分法'''
# 代码实现二分法
target_num = 100

def my_half(target_num, l):
    if len(l) == 0:
        print('不好依稀,没找到')
        return
    middle_index = len(l) // 2 # 小数 # 6
    if target_num > l[middle_index]:
        l_right=l[middle_index+1:] # l[7]
        print(l_right)
        my_half(target_num, l_right)
    elif target_num < l[middle_index]:
        l_left=l[:middle_index]
        print(l_left)
        my_half(target_num, l_left)
    else:
        print('找到了')

my_half(target_num, l)

列表生成式

name_list = ['kevin', 'jack', 'ly', 'tony']
# 需求是:把列表中得名字都添加一个后缀:_NB

# 1.传统做法:
new_list = []
for name in name_list:
    res = '%s_NB' % name
new_list.append(res)

print(new_list)

# 需求:除了jack不加,其他都加,如果是jack直接去掉
name_list = ['kevin', 'jack', 'ly', 'tony']
# new_list = []
# for name in name_list:
#     if name == 'jack':
#         continue
#     else:
#         res = '%s_NB' % name
#         new_list.append(res)
# print(new_list)

# 列表生成式的使用
# res = ['%s_NB' % name for name in name_list if name != 'jack']

'''特殊用法'''
res = ['%s_NB' % name if name != 'jack' else '666' for name in name_list]
print(res)

'''补充:'''
# count=0
# for i in l1:
#     print(count, i)
#     count+=1

# 记忆
"""
enumerate:使用for循环的时候,可以解压赋值出来两个值,一个是索引,一个是元素
            start:控制的是起始位置,默认是从0开始
"""

字典生成式

res = {i:j for i, j in enumerate(l1)}
print(res)

集合生成式

res = {i for i in l1}
print(res)

第八篇:匿名函数

匿名函数的使用和格式

res=lambda x:x**2  # lambda 形参:返回值
res(3)

def index(x):
    return x**2
index(3)

'''匿名函数一般不会单独使用'''
# 会配合一些方法使用
map()

l = [1,2,3,4,5]

def index(x):
    return x+2
res=map(index, l) # for循环,直接打印map是打印不出结果的
print(list(res))  # 转一下列表

配合匿名函数使用的方法

1. map(函数名, 要遍历的数据)  # 内部本质就是for循环,再记住两个参数的位置和作用
# 2. zip 拉链
l = [11, 22, 33, 44, 55, 66]
ll1 = ['name', 'age', 'hobby', 'aaa', 'b', 'ccc', 'ddd', 'fff']
ll2 = ['nam2e', 'age1', 'hobby1', 'aaa1', 'b', 'ccc', 'ddd', 'fff']
ll3 = ['name1', 'age2', 'hobby2', 'aaa2', ]

# d1 = {'username': 'tony'}
# d2 = {'username1': 'kevin'}
# [(11,'name'), (22, 'age'), (33, 'hobby')]

# lst = []
# for i in range(len(l)):
#     lst.append((l[i], ll1[i]))
# print(lst)

res = zip(l, ll1, ll2, ll3)  # <zip object at 0x000001646C22FE00> [(11, 'name'), (22, 'age'), (33, 'hobby')]
print(list(res))


# 3. max
# l = [1, 2, 10, 30, 5, 6, 7, 8]
# 求出列表中最大值
# print(max(l))

# 求出最小值
# print(min(l))

#
d = {
    'Aevin': 10000,
    'zack': 2000,
    'zony': 30000,
    'Fank': 5000000000
}
'''
    A-Z:65-90
    a:97
'''
'''如果是字典,比较的是key,说白了就是你暴露什么,就按照什么比较'''
def index(key):
    return d[key]
'''如果传了key参数,就是返回什么值就按照什么值比较'''
print(max(d, key=index))
print(max(d, key=lambda key:d[key]))

print(min(d, key=index))
print(min(d, key=lambda key:d[key]))

# 4. 过滤
l = [1, 10, 20, 55, 66, 77]
# ll = []
# for i in l:
#     if i > 20:
#         ll.append(i)
# print(ll)

def index(x):
    return x > 20
# res=filter(index, l)
res=filter(lambda x:x>20, l)
print(list(res))  # [55, 66, 77]

第九篇:捕捉异常

1. 什么是异常?
    # 异常就是错误发生的信号,如果此信号不做处理,那么,从本行开始之后的代码都不能正常执行了
2. 异常
    2.1 Traceback
    2.2 XXXError
        # 错误的类型
    2.3   XXXError冒号后面的内容,报错的详细原因,我们主要看的也是这部分,大致定位错误的原因 
3. 异常的种类
    1. 语法错误
        # 是坚决不允许的,遇到之后要立刻改正,否则,代码不能运行
    2. 逻辑错误
        # 是可以被允许的,但是,我们写逻辑的时候要尽可能的避免逻辑错误的发生
        
4. 常见的错误类型
    NameError
    IndexError
    KeyError
    ValueError
    ZeroDivisionError
    ...
5. 如何捕捉异常
    try:
          被监测的代码
    except 错误的类型1 as e:
        错误处理,e:错误的原因
    except 错误的类型2 as e:
        错误处理,e:错误的原因
    except 错误的类型3 as e:
        错误处理,e:错误的原因
    except 错误的类型4 as e:
        错误处理,e:错误的原因
        
 '''万能异常'''
try:
    # print(username)
    # print(1/ 0)
    # l = [1,2,3,4]
    # print(l[6])
    d = {'a':1, 'b':2}
    print(d['c'])  #KeyError
except ZeroDivisionError as e:
    print('')
except NameError as e:
    print(123)
except IndexError as e:
    print(123)
except Exception as e:
    print(e) # name 'username' is not defined
    
    
    
"""
    try except异常捕捉需要注意
        1. try里面被监测的代码尽量少
        2. 在明显没有错误的代码不要被捕捉
"""

第十篇:函数递归

一 函数递归调用介绍

函数不仅可以嵌套定义,还可以嵌套调用,即在调用一个函数的过程中,函数内部又调用另一个函数,而函数的递归调用指的是在调用一个函数的过程中又直接或间接地调用该函数本身

在调用f1的过程中,又调用f1,这就是直接调用函数f1本身

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

在调用f1的过程中,又调用f2,而在调用f2的过程中又调用f1,这就是间接调用函数f1本身

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

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

从上图可以看出,两种情况下的递归调用都是一个无限循环的过程,但在python对函数的递归调用的深度做了限制,因而并不会像大家所想的那样进入无限循环,会抛出异常,要避免出现这种情况,就必须让递归调用在满足某个特定条件下终止。

提示:

#1. 可以使用sys.getrecursionlimit()去查看递归深度,默认值为1000,虽然可以使用
sys.setrecursionlimit()去设定该值,但仍受限于主机操作系统栈大小的限制

#2. python不是一门函数式编程语言,无法对递归进行尾递归优化。

二 回溯与递推

下面我们用一个浅显的例子,为了让读者阐释递归的原理和使用:

某公司四个员工坐在一起,问第四个人薪水,他说比第三个人多1000,问第三个人薪水,第他说比第二个人多1000,问第二个人薪水,他说比第一个人多1000,最后第一人说自己每月5000,请问第四个人的薪水是多少?

思路解析:

要知道第四个人的月薪,就必须知道第三个人的,第三个人的又取决于第二个人的,第二个人的又取决于第一个人的,而且每一个员工都比前一个多一千,数学表达式即:

salary(4)=salary(3)+1000
salary(3)=salary(2)+1000
salary(2)=salary(1)+1000
salary(1)=5000

总结为:
salary(n)=salary(n-1)+1000 (n>1)
salary(1)=5000 (n=1)

很明显这是一个递归的过程,可以将该过程分为两个阶段:回溯和递推。

在回溯阶段,要求第n个员工的薪水,需要回溯得到(n-1)个员工的薪水,以此类推,直到得到第一个员工的薪水,此时,salary(1)已知,因而不必再向前回溯了。然后进入递推阶段:从第一个员工的薪水可以推算出第二个员工的薪水(6000),从第二个员工的薪水可以推算出第三个员工的薪水(7000),以此类推,一直推算出第第四个员工的薪水(8000)为止,递归结束。需要注意的一点是,递归一定要有一个结束条件,这里n=1就是结束条件。

代码实现:

def salary(n):
    if n==1:
        return 5000
    return salary(n-1)+1000

s=salary(4)
print(s)

执行结果:

8000

程序分析:

在未满足n==1的条件时,一直进行递归调用,即一直回溯,见图4.3的左半部分。而在满足n==1的条件时,终止递归调用,即结束回溯,从而进入递推阶段,依次推导直到得到最终的结果。

递归本质就是在做重复的事情,所以理论上递归可以解决的问题循环也都可以解决,只不过在某些情况下,使用递归会更容易实现,比如有一个嵌套多层的列表,要求打印出所有的元素,代码实现如下

items=[[1,2],3,[4,[5,[6,7]]]]
def foo(items):
    for i in items:
        if isinstance(i,list): #满足未遍历完items以及if判断成立的条件时,一直进行递归调用
            foo(i) 
        else:
            print(i,end=' ')
    
foo(items) #打印结果1 2 3 4 5 6 7

使用递归,我们只需要分析出要重复执行的代码逻辑,然后提取进入下一次递归调用的条件或者说递归结束的条件即可,代码实现起来简洁清晰