python 函数

函数

函数的优势:

  1. 减少代码的重复性

  2. 使代码可读性更好

 

函数的的结构

def 函数名():

    函数体
  return 返回值

 

return

1.遇到return,函数结束,return下面的(函数内)的代码不会执行。

2.return 会给函数的执行者返回值。

      如果return后面什么都不写,或者函数中没有return,则返回的结果是None

      如果return后面写了一个值,返回给调用者这个值

      如果return后面写了多个结果,,返回给调用者一个tuple(元组),调用者可以直接使用元组的解构获取多个变量。

 

三元运算符

 

c = a if a > b else b #当a>b就把a赋值给c,否则就把b赋值给c

 

 

函数的参数

函数的参数可以从两个角度划分:

  1.形参

    写在函数声明的位置的变量叫形参,形式上的一个完整.表示这个函数需要xxx(上面的是形参)

  2.实参

    在函数调用的时候给函数传递的值.加实参,实际执行的时候给函数传递的信息.表示给函数xxx

 函数的传参就是函数将实际参数交给形式参数的过程.(下面的是实参)

 

实参角度

实参的⾓角度来看参数分为三种:

  1. 位置参数
  2. 关键字参数
  3. 混合参数, 位置参数必须在关键字参数前面

1. 位置参数

  位置参数就是从左至右,实参与形参一一对应。

 

2. 关键字参数

def date(sex, age, hobby):
    print("拿出手机")
    print("打开陌陌")
    print('设置筛选条件:性别: %s,年龄:%s,爱好:%s' %(sex, age, hobby))
    print("找个漂亮的妹子")
    print("问她,约不约啊!")
    print("ok 走起")
date(hobby='唱歌',sex='',age='25~30',)

 

3.  混合参数

  可以把上面两种参数混合着使用. 也就是说在调用函数的时候即可以给出位置参数, 也可以指定关键字参数.

  混合参数一定要记住:关键字参数一定在位置参数后面。

 

 

形参角度

1. 位置参数

  位置参数其实与实参角度的位置参数是一样的,就是按照位置从左至右,一一对应


2. 默认值参数

 

def func(name, age, sex=''):   

    print("录入学生信息")

    print(name, age, sex)   

    print("录入完毕")



func("张强", 18)

 

3.万能参数(动态参数)

动态参数分为两种:动态接受位置参数 *args,动态接收关键字参数**kwargs.

动态接收位置参数:*args

  形参会将实参所有的位置参数接收,放置在一个元组中

动态接收关键字参数: *kwargs

  形参接受所有的关键字参数然后将其转换成一个字典

* 的魔性用法

   函数中分为打散和聚合。

​   函数外可以处理剩余的元素。

​   函数的打散和聚合

*起到的是打散

s1 = 'alex'
l1 = [1, 2, 3, 4]
tu1 = ('武sir', '太白', '女神',)
def func(*args):
    print(args) # ('a', 'l', 'e', 'x', 1, 2, 3, 4, '武sir', '太白', '女神')
func(*s1,*l1,*tu1)

**起到的是打散

dic1 = {'name': '太白', 'age': 18}
dic2 = {'hobby': '喝茶', 'sex': ''}
def func(**kwargs):
    print(kwargs) # {'name': '太白', 'age': 18, 'hobby': '喝茶', 'sex': '男'}
func(**dic1,**dic2)

 

4.形参的第四种参数:仅限关键字参数

def foo(a,b,*args,c,sex=None,**kwargs):
print(a,b)
print(c)
print(sex)
print(args)
print(kwargs)
func(1,2,3,4,5,6,7,sex='女',name='Alex',age=80,c='666')

 

 

注意:必须先声明在位置参数,才能声明关键字参数

综上:在形参的角度来看

  1. 位置参数

  2. 默认认值参数(大多数传进来的参数都是一样的, 一般用默认参数)

  3. 万能参数

  4. 仅限关键字参数

形参角度的所有形参的最终顺序为:*位置参数,*args,默认参数,仅限关键字参数,*kwargs。

 

函数名的运用

  1. 函数的内存地址

  2. 函数名可以赋值给其他变量

  3. 函数名可以当做容器类的元素

  4. 函数名可以当做函数的参数

  5. 函数名可以作为函数的返回值

小结:函数名是一个特殊的变量,他除了具有变量的功能,还有最主要一个特点就是加上() 就执行,其实他还有一个学名叫第一类对象

 

 

可迭代对象

  在python中,但凡内部含有__iter__方法的对象,都是可迭代对象

 

常见的可迭代对象有:

  str  list   tuple  dic  set  range 文件句柄等

不是可迭代对象的是:

  int,bool

小结

 

从字面意思来说:可迭代对象就是一个可以重复取值的实实在在的东西。

  从专业角度来说:但凡内部含有__iter__方法的对象,都是可迭代对象。

  可迭代对象可以通过判断该对象是否有’__iter__’方法来判断。

 

可迭代对象的优点:

        可以直观的查看里面的数据。

可迭代对象的缺点:

        1. 占用内存。

        2. 可迭代对象不能迭代取值(除去索引,key以外)。

 

迭代器

1. 迭代器的定义

  从字面意思来说迭代器,是一个可以迭代取值的工具,器:在这里当做工具比较合适。

  在python中,内部含有'__Iter__'方法并且含有'__next__'方法的对象就是迭代器。

2. 迭代器

  可迭代对象:str list tuple dict set range 

  迭代器:文件句柄

 

3. 可迭代对象如何转化成迭代器

l1 = [1, 2, 3, 4, 5, 6]
obj = l1.__iter__() 
# 或者 iter(l1)print(obj) 
# <list_iterator object at 0x000002057FE1A3C8>

 

4.迭代器取值

  迭代器是利用__next__()进行取值:

l1 = [1, 2, 3,]
obj = l1.__iter__()  # 或者 iter(l1)
# print(obj)  # <list_iterator object at 0x000002057FE1A3C8>
ret = obj.__next__()

 

5. while模拟for的内部循环机制

l1 = [1, 2, 3, 4, 5, 6]
# 1 将可迭代对象转化成迭代器
obj = iter(l1)
# 2,利用while循环,next进行取值
while 1:
    # 3,利用异常处理终止循环
    try:
        print(next(obj))
    except StopIteration:
        break

 

小结

迭代器的优点:

                节省内存。
                    迭代器在内存中相当于只占一个数据的空间:因为每次取值都上一条数据会在内存释放,加载当前的此条数据。

                惰性机制。
                    next一次,取一个值,绝不过多取值。

 

迭代器的缺点:

            不能直观的查看里面的数据。

            取值时不走回头路,只能一直向下取值。

 

可迭代对象与迭代器对比

    可迭代对象:

    是一个私有的方法比较多,操作灵活(比如列表,字典的增删改查,字符串的常用操作方法等),比较直观,但是占用内存,而且不能直接通过循环迭代取值的这么一个数据集。

    应用:当你侧重于对于数据可以灵活处理,并且内存空间足够,将数据集设置为可迭代对象是明确的选择。

    迭代器:

    是一个非常节省内存,可以记录取值位置,可以直接通过循环+next方法取值,但是不直观,操作方法比较单一的数据集。

    应用:当你的数据量过大,大到足以撑爆你的内存或者你以节省内存为首选因素时,将数据集设置为迭代器是一个不错的选择。(可参考为什么python把文件句柄设置成迭代器)。

 

生成器

生成器是需要我们自己用python代码构建的工具

 

1. 生成器的构建方式

  在python中有三种方式来创建生成器:

    1. 通过生成器函数

    2. 通过生成器推导式

    3. python内置函数或者模块提供(其实1,3两种本质上差不多,都是通过函数的形式生成,只不过1是自己写的生成器函数,3是python提供的生成器函数而已)

2. 生成器函数

  将函数中的return换成yield,这样func就不是函数了,而是一个生成器函数

def func():
​
    print(11)
​
    yield 22
​
ret = func()
​
print(ret)
​
# 运行结果:
<generator object func at 0x000001A575163888>  # 获得生成器对象

 

生成器的本质就是迭代器.迭代器如何取值,生成器就如何取值。所以我们可以直接执行next()来执行以下生成器

  

def func():
​
     print("111")
​
     yield 222
​
gener = func() # 这个时候函数不会执⾏. ⽽是获取到⽣成器
​
ret = gener.__next__() # 这个时候函数才会执⾏
print(ret)  # 并且yield会将func生产出来的数据 222 给了 ret。  
​
结果:
​
111222

yield与return的区别:

        return一般在函数中只设置一个,他的作用是终止函数,并且给函数的执行者返回值。

        yield在生成器函数中可设置多个,他并不会终止函数,next会获取对应yield生成的元素。

 

yield 与 yield from的区别:

  yield 有返回值 也可以接受返回值,yield是直接yield的是可迭代对象

  yield from 调用生成器 ,yield from是将可迭代对象中的元素一个一个yield出来

  

推导式

li = []
​
for i in range(10):
​
    li.append(i)
​
print(li)

 

推导式写法

ls = [i for i in range(10)]
​
print(ls)

列表推导式分为两种模式:

    1.循环模式:[变量(加工的变量) for 变量 in iterable]

    2.筛选模式: [变量(加工的变量) for 变量 in iterable if 条件]

当然还有多层循环的,这个我们一会就会讲到,那么我们先来看循环模式。

循环模式

从python1期到python100期写入列表lst
lst = [f'python{i}' % i for i in range(1,19)]
​
print(lst)

 

 筛选模式

筛选模式就是在上面的基础上加上一个判断条件,将满足条件的变量留到列表中。

过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母

l = ['wusir', 'laonanhai', 'aa', 'b', 'taibai']
# print([i.upper() for i in l if len(i) > 3])

 

 生成器表达式

生成器表达式和列表推导式的语法上一模一样,只是把[]换成()就行了

比如将十以内所有数的平方放到一个生成器表达式中


gen = (i**2 for i in range(10))
print(gen)
# 结果: <generator object <genexpr> at 0x0000026046CAEBF8>

 

 生成器表达式也可以进行筛选

# 获取1-100内能被3整除的数
gen = (i for i in range(1,100) if i % 3 == 0)
for num in gen:
    print(num)

 

生成器表达式和列表推导式的区别:

1. 列表推导式比较耗内存,所有数据一次性加载到内存。而.生成器表达式遵循迭代器协议,逐个产生元素。

   2. 得到的值不一样,列表推导式得到的是一个列表.生成器表达式获取的是一个生成器

   3. 列表推导式一目了然,生成器表达式只是一个内存地址。

 

匿名函数

普通版

def func(a,b):
    return a+b
print(func(3,4))

匿名函数

func = lambda a,b: a+b
print(func(3, 4))  # 7

语法:

  函数名 = lambda 参数:返回值

 

  1)此函数不是没有名字,他是有名字的,他的名字就是你给其设置的变量,比如func.

  2)lambda 是定义匿名函数的关键字,相当于函数的def.

  3)lambda 后面直接加形参,形参加多少都可以,只要用逗号隔开就行。

 

func = lambda a,b,*args,sex= 'alex',c,**kwargs: kwargs
print(func(3, 4,c=666,name='alex'))  # {'name': 'alex'}
# 所有类型的形参都可以加,但是一般使用匿名函数只是加位置参数,其他的用不到。

  4)返回值在冒号之后设置,返回值和正常的函数一样,可以是任意数据类型。

  5)匿名函数不管多复杂.只能写一行.且逻辑结束后直接返回数据

 

闭包

闭包的定义:

  1. 闭包是嵌套在函数中的函数。

  2. 闭包必须是内层函数对外层函数的变量(非全局变量)的引用。

闭包的作用:保存局部信息不被销毁,保证数据的安全性。

闭包的应用

  1. 可以保存一些非全局变量但是不易被销毁、改变的数据。

  2. 装饰器。

 

装饰器

开放封闭原则

装饰器定义:

  在不改变原被装饰的函数的源代码以及调用方式下,为其添加额外的功能。

 

  语法糖:@index = index = test_time(index)
  用途:
    登录认证,打印日志,访问记录

 

最简单的装饰器函数传参:

def warpper(s):  #s = func1
    def inner(*args,**kwargs):     #4-聚合接受inner的实参大壮
        print(666)                 #执行func1函数前操作
        ret = s(*args,**kwargs)    #5给打散实参执行func1函数,
        return ret                #6将func1,return执行结果传给inner
    return inner   #2-返回inner给warpper

@warpper  #1-语法糖:func1 = warpper(func1)
def func1(a):
    return f"{a}欢迎登录"
print(func1("大壮"))   #3-func1 = inner('大壮')  7 print结果

装饰器传参:装饰器嵌套三层函数,最外层函数接受装饰器实参,可以对形参进行操作判断

def wrapper(n):  
    def wrapper1(f):
        def inner(*args,**kwargs):
            name = input("输入用户名")
            pwd  = input("输入密码")
            with open(n,encoding='utf-8') as f1:   #n等于语法糖传过来的值
                for i in f1:
                    j,k = i.strip().split('|')
                    if name == j and pwd == k:
                        ret = f(*args,**kwargs)
                        return ret
        return inner
    return wrapper1

@wrapper('qq') #将qq传给最外层wrapper形参
def qq():
    print('成功访问qq')

@wrapper('weixin')
def tiktok():
    print('成功访问微信')
qq()
tiktok()

两个装饰器,装饰一个函数:先执行@wraper1 执行完的结果是@wraper2的形参。

如果两个装饰器内层函数一样,调用函数时先走最上面装饰器

def wrapper1(func1):  # func1 = f原函数
    def inner1():
        print('wrapper1 ,before func')  # 2
        func1()
        print('wrapper1 ,after func')  # 4
    return inner1

def wrapper2(func2):  # func2 == inner1
    def inner2():
        print('wrapper2 ,before func')  # 1
        func2()   # inner1
        print('wrapper2 ,after func')  # 5
    return inner2

@wrapper2  # f = wrapper2(f) 里面的f == inner1  外面的f == inner2
@wrapper1  # f = wrapper1(f) 里面的f == func1  外面的 f == inner1
def f():
    print('in f')  # 3
f()  # inner2()

 

 

递归函数

递归的最大深度1000层 : 为了节省内存空间,不要让用户无限使用内存空间

 

1.递归要尽量控制次数,如果需要很多层递归才能解决问题,不适合用递归解决

2.循环和递归的关系 递归不是万能的 递归比起循环来说更占用内存

 

报错提示: RecursionError

 

修改递归的最大深度

import sys
    sys.setrecursionlimit(1000000000)

 

# 你的递归函数 必须要停下来
# 递归函数是怎么停下来的?递归3次结束整个函数
count = 0
def func():        # func1
    global count
    count += 1     # 1
    print(count)
    if count == 3: return
    func()
    print(456)
func()

def func():        # func2
    global count
    count += 1     # 2
    print(count)
    if count == 3: return
    func()
    print(456)


def func():        # func3
    global count
    count += 1     # 3
    print(count)
    if count == 3:return
    func()
    print(456)
结果
1
2
3
456
456
后面的两个函数没有执行没有func()
# 一个递归函数要想结束,必须在函数内写一个return,并且return的条件必须是一个可达到的条件
# 并不是函数中有return,return的结果就一定能够在调用函数的外层接收到

 

 

偏函数

就是当函数的参数太多的时候,需要简化,使用(functools.partial)可以创建一个新的函数,这个函数可以固定住原来的参数的部分参数,从而在调用的时候更加的简单。


functolls.partial的作用就是把一个函数的某些参数设置为默认值,然后返回一个新的函数,然后再调用这个函数。

 

posted @ 2019-08-12 12:51  驰念  阅读(314)  评论(0编辑  收藏  举报