内容概要

  • 函数阶段
语法结构
定义调用
返回值
参数
名称空间
闭包函数
装饰器(难点)
递归函数、二分法、匿名函数、三元表达式、列表生成式
迭代器、生成器
常见内置函数

函数简介

l1 = [11, 22, 33, 44, 55]
# 统计列表l1中数据值的个数
# print(len(l1))  # 5
'''假设len不准用!!! 自己写代码统计'''
# count = 0
# for i in l1:
#     count += 1
# print(count)
'''很多地方都需要统计一下列表l1中数据值的个数'''
# 上述的四行代码需要反复编写 >>> 重复造轮子 很麻烦
"""
思考
    循环(while、for)
        让一些代码能够在相同的位置反复执行
    函数
        让一些代码能够在不同的位置反复执行
"""
'''函数初识'''
def my_len():
    count = 0
    for i in l1:
        count += 1
    print(count)
  
如何理解函数?
    函数类似于工具 提前准备(定义)好之后 后续可以反复使用(调用)
用函数和不用函数的区别?
    函数的使用主要为了解决代码冗余(程序员的劳动压力)

函数的语法结构

def 函数名(参数1,参数2):
  '''函数注释'''
  函数体代码
  return 返回值

1.def
    是定义函数的关键字
2.函数名
    类似于变量名 最重要的也是见名知意
3.参数
    使用函数的时候外界可以传递给函数体代码的数据(可有 可无)
4.函数注释
    说明函数的功能 参数的概念 函数的用法
5.函数体代码
    整个函数的核心 决定了功能的好坏
6.return
    使用函数之后返回给使用者的信息(可有 可无)

函数的定义与调用

1.函数必须要先定义 后调用
    定义函数的代码一定要先于使用函数的代码之前被解释器解释运行
2.定义函数
    函数在定义阶段只检测语法 不执行代码 执行代码的操作必须是在调用之后
3.调用函数
    函数名加括号(执行函数体代码)

函数的分类

1.内置函数
    解释器已经提前定义好的函数 程序员直接拿来使用即可
    eg:len()
      
2.自定义函数
    无参函数
    函数在定义阶段括号内没有写参数
        eg:
        def my_func():
          print(123)
        调用无参函数不需要额外的条件 直接函数名加括号即可
  有参函数
    函数在定义阶段括号内写了参数
        eg:
        def my_index(a, b):
          print(321)
         调用有参函数需要额外的条件 括号内需要'给东西'
  空函数
    函数体代码什么都没有
        eg:
        def my_outer():
          pass
        主要用于项目前期思路搭建 提示程序员后续需要做的操作

函数的返回值

1.如何理解返回值
    调用函数之后可以返回给调用者的信息
2.如何获取返回值
    变量名 = 函数调用
3.返回值具体操作
    1.函数体代码如果没有return 默认返回None(什么都没有 布尔值为False)
  2.函数体代码如果有return 后面上面都不写的话 返回的也是None
    3.函数体代码如果有return 后面写什么就返回什么
  4.函数体代码如果有return 并且有多个数据值逗号隔开 会自动组织成元组返回
  5.函数体代码执行到return 就会立刻结束该函数(类似于break)

函数参数的重要概念

形式参数
    函数在定义阶段括号内填写的参数称之为形式参数  简称为"形参"
实际参数
    函数在调用阶段括号内填写的参数称之为实际参数  简称为"实参"

形参与实参的关系(重要)
    形参类似于变量名 实参类似于数据值
  函数在调用阶段形参和实参会临时绑定 函数运行结束会立刻断开绑定

def func(a):  # a形参
  print(a)
func(1)  # 1实参

形参的命名尽量见名知意 提示调用者大致应该传什么类型的数据

位置参数

位置形参
    函数定义阶段括号内从左往右依次填写的'变量名'
位置实参
    函数调用阶段括号内从左往右依次填写的'数据值'(数据值、绑了值的变量名)
 
def func(a, b):
    print(a, b)

# func(1, 2)  # 按照位置顺序一一传值
# func(1)  # 少一个不行
# func(1, 2 ,3)  # 多一个不行

关键字参数

关键字实参
	函数调用阶段括号内通过指名道姓的方式传'数据值'(数据值、绑了值的变量名)

def func(a, b):
    print(a, b)
# func(a=1, b=2)
# func(b=1, a=2)  # 指名道姓的传 可以不考虑位置顺序
# func(b=1, 2)  # 位置参数一定要写在关键字参数的前面
# func(1, a=2)  # 一个形参不能同时传两个实参
name = 'jason'
age = 18
func(name, age)

默认参数

默认形参
	函数在定义阶段就已经给括号内形参赋值了

def register(name, age, gender='male'):
    print(name, age, gender)


# register('jason', 18, 'male')
# register('tony', 38, 'male')
# register('kevin', 28, 'female')

register('jason', 18)
register('tony', 38)
register('kevin', 28)
register('lili', 18, 'female')

"""
无论是形参还是实参 都应该遵守
	越短(简单)的越靠前 越长(复杂)的越靠后
"""

可变长参数

*
	在形参中可以接收多余的位置实参 并组织成元组的形式赋值给符号后面的变量名
**
	在形参中可以接收多余的关键字实参 并组织成字典的形式赋值给符号后面的变量名
"""
*和**后面的变量名理论上怎么写都行 但是为了统一 建议使用
	*args **kwargs
"""

* 
	在实参中可以将容器类型中所有的数据值for循环出来一个个传给函数
** 
	在实参中可以将字典的kv键值对变成关键字参数传给函数

命名关键字参数(了解)

def index(name, age, *args, gender, **kwargs):
    print(gender)
'''命名关键字参数:传值必须采用关键字参数的形式'''
index('jason', 18, gender='male')

名称空间

专门用于存储变量名与数据值绑定关系的地方

1.名称空间的分类
	1.内置名称空间
  2.全局名称空间
  3.局部名称空间

2.名称空间生命周期
	1.内置名称空间
  	解释器启动就自动创建 里面存放了解释器级别程序员可以直接调用的所有名字
    	eg:  print()  len()  
 	2.全局名称空间
  	py文件级别的代码运行创建 py文件运行结束关闭
 	3.局部名称空间
  	函数体代码运行会临时创建 运行结束直接关闭

3.名称空间的查找顺序
	'''一定要先明确自己在哪'''
  1.如果在全局
  	全局>>>内置
  2.如果在局部
  	局部>>>全局>>>内置

global与nonlocal关键字

name = 'jason'
def func():
    # 如果在局部名称空间中想要修改全局名称空间里面的名字
    global name
    name = 'kevin'
    print('func局部名称空间里面的name', name)
func()
print(name)


def func1():
    x = 1
    def func2():
        # 局部修改上一层局部
        # nonlocal x
        x = 2
        print(x)
    func2()
    print(x)
func1()
"""
只要涉及到变量名与值绑定关系的时候 一定要注意到底是在哪个名称空间中操作
"""

函数名的多种用法

1.函数名可以当做函数的实参传入
2.函数名可以当做变量名参与赋值
3.函数名可以当做函数的返回值
4.函数名可以当做容器类型中的数据值

def register():
    print('注册功能')
def login():
    print('登录功能')
def transfer():
    print('转账功能')
def withdraw():
    print('提现功能')
def shopping():
    print('购物功能')
func_dic = {
    '1':register,
    '2':login,
    '3':transfer,
    '4':withdraw,
    '5':shopping
}
while True:
    print("""
    1.注册
    2.登录
    3.转账
    4.提现
    5.购物
    """)
    func_id = input('请输入您想要执行的功能>>>:').strip()
    if func_id in func_dic:
        func_name = func_dic.get(func_id)
        func_name()
    else:
        print('输入不合法')

闭包函数

定义在函数体内部的函数 并且使用到了外部函数名称空间中的名字

def func1():
    name = 'jason'
    def func2():
        print(name)

闭包函数其实是给函数体代码传参的第二种方式(参数、闭包)
def outer():
    username = 'jason'
    password = '123'
    def auth_user():  # 闭包函数
        if username == 'jason' and password == '123':
            print('登录成功')
        else:
            print('登录失败')
    auth_user()
outer()

def outer(username, password):
    # username = 'jason'
    # password = '123'
    def auth_user():  # 闭包函数
        if username == 'jason' and password == '123':
            print('登录成功')
        else:
            print('登录失败')
    auth_user()

outer('jason', '123')

装饰器

装饰器不是新的知识点而是由上面所有的函数知识点整合到一起的产物

装饰器的作用
	在不改变被装饰对象(函数)源代码和调用方式的基础之上给被装饰对象(函数)添加新的功能

装饰器推导流程
1.预备知识
	import time
  # print(time.time())  # 时间戳
  # time.sleep(3)  # 让代码原地阻塞三秒
  # print('睡醒了')

  # 统计代码的执行时间
  start_time = time.time()
  time.sleep(1)
  print('hello jason')
  end_time = time.time() - start_time
  print(end_time)
2.统计某个函数的执行时间
3.推导流程(尝试理解 实在不行问题不大)
import time

def func():
    time.sleep(1)
    print('from func')
    return 'func'
def index():
    time.sleep(3)
    print('from index')
    return 'index'
def home(a,b):
    time.sleep(1)
    print('from home')
    return 'home'
# def get_time(func_name):
#     start_time = time.time()
#     func_name()
#     end_time = time.time() - start_time
#     print(end_time)
# 1.如何让函数统计兼容
# get_time(index)
# get_time(func)
# 2.闭包函数
def outer(func_name):  # func_name=index函数
    def get_time(*args, **kwargs):  # get(1, 2) args=(1,2) kwargs={}
        start_time = time.time()
        res = func_name(*args, **kwargs)  # home(*(1,2), **{})  home(1, 2)
        end_time = time.time() - start_time
        print(end_time)
        return res
    return get_time
# index = outer(index)
# index()
# func = outer(func)  # get_time
# func()  # get_time()
home = outer(home)
res = home(1, 2)
print(res)

装饰器模板及语法糖

import time

from  functools  import wraps
def outer(func_name):
    @wraps(func_name)  # 让装饰器的伪装更加不易被察觉(平时也可以不写 不影响代码执行)
    def inner(*args, **kwargs):
        print('执行被装饰对象之前可以做的操作')
        res = func_name(*args, **kwargs)
        print('执行被装饰对象之后可以做的操作')
        return res
    return inner

@outer  # 装饰器语法糖(将紧挨着该语法糖的名字自动传入)    home = outer(home)
def home():
    time.sleep(1)
    print('from home')
@outer
def index():
    time.sleep(2)
    print('from index')

print(home)
print(index)

递归函数

1.递归
	函数直接或者间接调用了自己 目的是为了获得答案
  递归有两个核心步骤
  	递推
    	一步步查找一个具体的结果
    回溯
    	根据结果反推出真正的数据
	eg:
    有 A B C D E五人 我们想知道A的年龄 但是A不说
    A说我比B大两岁 我们问B B也不说 B说我比C大两岁...E说我18岁

2.编写递归函数一定要有两个条件
	1.必须要有明确的结束条件
  2.每次的递推的结果一定要比上一次简单

3.递归函数实战
	1.猜年龄
  	def get_age(n):
        if n == 1:
            return 18
        return get_age(n - 1) + 2
    res = get_age(5)
    print(res)
  2.打印纯数字
  	ll = [1,[2,[3,[4,[5,[6,[7,[8,[9,[10,]]]]]]]]]]
    # 循环列表并打印出列表中所有的数字
    """
    1.循环列表
    2.判断数据值是否是数字 如果是则打印
    3.如果不是则继续循环小列表
    4.判断数据值是否是数字 如果是则打印
    5.如果不是则继续循环小小列表
    6.判断数据值是否是数字 如果是则打印
    7.如果不是则继续循环小小小列表
    """
    def get_num(l1):
        for i in l1:
            if isinstance(i, int):
                print(i)
            else:
                get_num(i)
    get_num(ll)

算法之二分法

何为算法?
	算法其实就是针对某些问题的解决方法
  	eg:有一瓶可乐易拉罐需要打开
      	1.拉上面环
        2.一阳指戳
        3.剪刀剪
二分法
	算法里面最简单的一种 甚至都称不上是算法
  l1 = [11, 23, 44, 55, 59, 78, 98, 123, 432, 564, 765, 876, 999, 1000, 3456, 7689, 9999]

  def get_num(l1, target_num):
      if len(l1) == 0:
          print('很抱歉 你要找的数字不存在')
          return
      # 1.先获取中间索引
      middle_index = len(l1) // 2
      # 2.判断要查找的数与中间索引对应的数谁大谁小
      if target_num > l1[middle_index]:
          # 3.说明要查找的数只可能出现在列表的右边一半
          right_l1 = l1[middle_index + 1:]
          # 3.1.接着二分
          print(right_l1)
          get_num(right_l1, target_num)
      elif target_num < l1[middle_index]:
          # 4.说明要查找的数只可能出现在列表的左边一半
          left_l1 = l1[:middle_index]
          print(left_l1)
          # 4.1.接着二分
          get_num(left_l1, target_num)
      else:
          print('恭喜你 找到了', target_num)


  get_num(l1, 98)

函数小知识点补充

'''主要都是为了减少代码编写 提高开发效率'''
1.三元表达式
	数据值1 if 条件 else 数据值2
  如果条件成立则使用数据值1、不成立则使用数据值2
  ps:主要用于二选一的情况  
2.列表生成式
	[条件成立执行操作并保留 for i in 可迭代对象 if 条件]
3.匿名函数
	lambda 形参:返回值
  ps:主要配合一些内置函数一起使用 简化代码
  l1 = [1,2,3,4,5]
  def index(x):
      return x + 1
  res = map(index, l1)
  res1 = map(lambda x:x+1, l1)
  print(list(res), list(res1))

迭代器

1.如何理解迭代?
	版本更新 每一次得带都必须依赖于上一次的结果
2.什么是可迭代对象
	内置有__iter__方法的都可以称之为可迭代对象
3.属于可迭代对象的有
	字符串 列表 字典 元组 集合 文件对象
4.可迭代对象调用双下iter方法就会变成迭代器对象
	内置有__iter__和__next__方法的称之为迭代器对象
5.迭代器对象调用双下next方法就是在迭代取值
	for循环内部采用的就是该原理
"""
迭代器对象可以节省内存空间 并且 提供了一种不依赖于索引取值的方式
"""

for循环底层原理

for 变量名 in 可迭代对象:
  for循环体代码
1.将in后面的可迭代对象调用双下iter变成迭代器对象
	迭代器对象无限制再调用双下iter还是迭代器对象本身
2.让生成的迭代器对象调用双下next迭代取值
3.一旦取值完毕双下next方法会报错 for循环会自动捕获并处理

生成器

生成器其实就是迭代器 只不过可以让用户自定义 之前的迭代器都是解释器自动提供的

函数体代码中如果出现了关键字yield 那么该函数加括号调用会变成生成器
def func():
    print('from func')
    yield 111
    print('from func')
    yield 222
    print('from func')
    yield 333

res = func()
print(res.__next__())
print(res.__next__())
print(res.__next__())

异常处理

"""
当我们自己也不知道某些代码会不会报错 就可以使用异常处理
	eg: 网络爬虫爬取网站数据 
				正常情况下可以获取 但是有时候会出现极端情况(断网、停电)
"""
1.通过自己编写代码来处理bug
2.异常的分类
	语法错误
  	不被允许的 如果出现应该立刻改正 否则可能丢饭碗       
  逻辑错误
  	是被允许的 如果出现了可以有修改的时间 属于正常工作内容
3.异常的结构
		1.错误的位置
  	2.错误的类型 
    3.错误的提示
4.常见错误类型
	NameError
  IndexError
  KeyError
  TypeError
5.异常处理
	try:
    	可能出错的代码
  except 错误类型 as e:  # e就是错误的提示信息
    	错误类型对应的解决措施
  1.except可以写多个相当于提前定义好不同错误的不同处理策略
  2.如果所有的报错都想采用相同的处理策略 可以使用万能异常
  		Exception、BaseException
 	3.else
  	当try监测的代码没有报错的情况下 会走else的子代码
  4.finally
  	无论被检测的代码是否有错 最后都会走finally子代码
6.主动报错 
		raise 错误类型(错误提示)
7.断言
		assert isinstance(name, list)# 预测name是列表类型 如果不是则报错
"""
异常处理能尽量少用就少用 try检测的代码越少越好
"""