函数

一.函数的基本使用

接触一个未知的知识都要先从三个方面入手

1、什么是函数

在程序具备某一功能的工具=》函数
事先准备好工具=》函数的定义
遇到应用场景、拿来就用=》函数的调用

分为两大类:

01.内置函数

02.自定义函数

2、为何要用函数

01.代码冗余
02.程序的组织结构不清晰,可读性差
03.扩展性差

3、如何用函数

   函数的使用必须遵循一个原则:
   ​    01. 先定义
   ​        定义语法:
   ​            def 函数名(参1,参2,参3,...):
   ​                """
   ​                文档注释
   ​                """
   ​                代码1
   ​                代码2
   ​                代码3
   ​                ...
   ​                return 值

   ​     02. 后调用
   ​         函数名()

4.定义阶段: 只检测语法,不执行代码

def func():   
print('from func 1')
​        print('from func 2')
​        print('from func 3')
​        print('from func 4')

5. 调用阶段: 开始执行函数体代码

func()

示范一:
def foo():
​      print('from foo')
​      bar()

foo()#'bar'没有被定义(define),所以会报错

示范二:
def bar():
​     print('from bar')

def foo():
​     print('from foo')
​     bar()

foo()#会打印出from foo
​                           from bar

示范三:
定义

def foo():
​     print('from foo')
​     bar()

def bar():
​     print('from bar')

调用
foo()

6.定义函数的三种形式

01.无参函数

def func():
  print('from func')

func()

02.有参函数

def max2(x,y):
  x=1
  y=2
  if x > y:
      print(x)
  else:
      print(y)

max2(1,2)
max2(3,4)

03.空函数

def register():
  pass
7.调用函数的三种形式

01.语句形式

def func():
  print('from func')

func()

02.表达式形式

def max2(x,y):
  if x > y:
      return x
  else:
      return y

res=max2(1000,2000) * 12#此处需要的是返回值所以用return,不然不可以运算
print(res)

03.函数的调用可以当作另外一个函数的参数传入

比较1,2,3的大小

def max2(x,y):
  if x > y:
      return x
  else:
      return y

res=max2(max2(1,2),3)#用函数依次比较大小
print(res)

二.函数的返回值

1.什么是函数的返回值

返回值是函数体代码的运行成果

2.为何要有返回值

需要拿到函数的处理结果做进一步的处理,则函数必须有返回值#如同上方的表达式形式需要用到返回的值的话就需要用到返回值

3.如何用

return 返回值的特点:

       01.返回的值没有类型限制,也没有个数限制
​        I: return或者函数没return:返回值None
​        II: return 值:返回的就是该值本身
​        III: return 值1,值2,值3:返回元组(值1,值2,值3)

def func():
  return 1,1.2,'aaa',[1,2,3]
  pass#占位符,没有作用
res=func()
print(res)

       02.return是函数结束运行的标志,函数内可以有多个return,但只要执行一次函数就立即结束,并且将return后的值当作本次调用的结果返回

def func():
  print('aaaa')
  return 1
  print('bbb')
  return 2
  print('ccc')
  return 3


func()#此处就只会返回 aaa

三.函数参数的使用

函数的参数分为两大类

1.形参:在定义函数是括号内制定的参数(变量名),称之为形参

2.实参:在调用函数时括号内传入的值(变量值),称之为实参

二者的关系:在调用函数时,实参数(变量值)会传给形参(变量名),这种绑定关系在调用函数时生效,调用结束后解除绑定。

函数参数详解

1.形参:

1.1 位置形参:在定义阶段,按照从左到右的顺序依次定义的形参

特点:必须被传值

def func(x,y,z):
  print(x,y,z)

func(1,2,3)#这边需要注意的是必须一一对应,对一个少一个都不可以.

1.2 默认参数:在定义阶段,就已经为莫格形参赋值该形参称之为默认形参

特点:在定义阶段就已经有值,意味着调用阶段可以不用为其传值

注意点:位置形参必须放在默认形参的前面

def func(x,y=2):#要注意这边绝不可以是   func(x=2,y)
  print(x,y)
   
func(1)#这边输出的就是 1   2
func(1,3333333)#这边输出的就是 1 3333333

1.3 形参中*与**的用法

形参中带*: *会将溢出的位置实参存在元组的形式然后赋值给其后变量名

def func(x,y,*args):
  print(x,y,args)

func(1,2,3,4,5,6)#输出   1 2 (3, 4, 5, 6)

形参中带* *: **会将溢出的关键字实参存成字典的格式然后赋值给其后变量名

def func(x,y,**kwargs):
  print(x,y,kwargs)

func(1,y=2,z=3,m=1,n=2)#输出   1 2 {'z': 3, 'm': 1, 'n': 2}

2:实参

2.1 位置实参:在调用阶段,按照从左到右的顺序依次传入的值

特点:这种传值方式会与形参一一对应  #就是上面所说的形参一一对应

2.2 关键字实参: 在调用阶段,按照key=value的格式传值

特点:可以完全打乱位置,但仍然能为指定的形参传值

注意:可以混用关键字实参与位置实参,但是  1.同一个形参只能被赋值一次

                                                                         2.位置实参必须跟在关键字实参的前面

2.3 实参中*与**的用法

实参中带*:先将实参打散成位置参数,然后在与形参做对应

def func(x,y,z):
  print(x,y,z)

func(*[1,2,3]) #func(1,2,3)
func(*'hel') #func('h','e','l')

  实参中带**: 先将实参打散成关键字实参,然后在与形参做对应

def func(x,y,z):
  print(x,y,z)

func(**{'x':1,'z':3,'y':2}) #func(z=3,y=2,x=1)

四.函数对象

就是把函数的内存地址当作一种变量值去使用

那么具体体现在哪

1.函数可以被引用

x=10
y=x   #这是我们所熟悉的变量值的使用

def func1():
  print('from func')
# func1=函数的内址
func2=func1
func2()#这边就是可以把函数的内存地址当作变量值使用

2.函数可以作为函数的参数

def func1():
  print('from func')

def bar(xxx):
  print(xxx)
  xxx()
bar(func1)#此时就是把func这函数当成xxx传入bar函数,同时这个xxx()就相当于func(),也就相当于函数的引用.此时输出的的就是 func的内存地址 与 from func

3.函数可以作为函数的返回值

def func1():
  print('from func')

def bar():
  return func1

f=bar()
f()#这里就是func函数作为bar函数的返回值

4可以被存储到容器类型中,也就是当作容器类型的元素

def func1():
  print('from func')

l=[func1,]
print(l)
l[0]()

这边是一个具有一些小功能的简易购物车,函数下面就可以加上你想要写的功能

def register():
  print('注册')

def login():
  print('登陆')

def shopping():
  print('购物')

def pay():
  print('支付')

func_dic={
  '1':register,
  '2':login,
  '3':shopping,
  '4':pay
}

while True:
  print("""
  0 退出
  1 注册
  2 登陆
  3 购物
  4 支付
  """)
  choice=input('>>>: ').strip()
  if choice == '0':break
  if choice in func_dic:
      func_dic[choice]()
  else:
      print('输入错误的指令')

五.函数的嵌套

1.函数嵌套定义:它主要就是在一个函数中定义了另外一个函数。

要注意的是:定义在函数内的函数 只能在函数内使用 外界不能访问。

def outter():
  def inner():
      pass#这就是在oytter函数中定义了一个inner函数

2.函数的嵌套调用:提供了一种解决问题的思路,如果有一个大问题,可以细分成很多小问题,然后单独实现小问题,最后在大的函数中调用小函数,就比较容易解决问题

比如比较4个值的大小就可以

def max2(x,y):
  if x > y:
      return x
  else:
      return y

def max4(a,b,c,d):
  res1=max2(a,b)
  res2=max2(res1,c)
  res3=max2(res2,d)
  return res3
   
a=max4(1,2,3,4)
print(a)

六.名称空间与作用域

1.名称空间:就是存放名字与值内存地址绑定关系的地方

2.名称空间的分类

            内置名称空间:存储解释器自带的一些名称与值的对应关系(python解释器启动时创建 所有代码全部执行完毕 关闭解释器时 销毁)  print len  max  min 等等这些

            全局名称空间:那些数据会存储在全局空间(执行py文件创建全局名称空间 关闭解释器时 销毁)

                    文件级别的文件   只要你的名字的定义是顶着最左边写的就在全局空间

                    除了内置的函数内的 都在全局中

            局部名称空间:只要时函数内的名称就是局部的(调用函数时创建 函数执行完毕就销毁)

名称空间的加载顺序

内置的-->全局的-->局部的

名称的查找顺序

局部的--> 全局的-->内置的

总结:01.查找名字的顺序是从当前位置往外查找

           02.名称关系的嵌套关系是在函数定义阶段就固定死的,与调用关系无关

def outter():
  x=11111111
  def inner():
      print(x)
  return inner


f=outter()


def foo():
  x=222222222222
  f()

foo()#此处输出的就是11111111,定义阶段就固定死了所以与foo()下面的x无关

 

3作用域(作用范围)    

域 指的是区域 范围的的意思
全局的名称空间和内置的名称空间 在使用上没什么区别
局部的和全局的内置的 就区别了 局部定义的只能在局部使用
给三个空间划分范围

全局的和内置可以划分为同一个范围
global 表示的全局范围 就是所谓的全局作用域

局部的单独划分为一个范围
local 局部作用域

globals()
locals()

七.闭包函数

闭函数:该函数一定是定义在函数内的函数

包函数:爱函数包含对外层函数作用域名字的引用

age = 20
# 如下就是一个闭包函数
def fun1():
  age = 18
  def inner():
      print("hello")
      print(age)#这是对上面age进行了引用
  # 在返回这个内部的函数时 不是单纯的返回函数 还把函数中访问到的局部名称一起打包了
  # 相当于将内部函数与 访问的数据打包在一起了 闭包这个名字就是这么得来的
  return inner
f = fun1() # f 就是innerr
f()

函数的作用域在定义时就固定了 与调用的位置没有关系

为函数传值有二种方案:

1 直接传参

def f(x):
  print(x)

f(10)
f(11)

2 闭包函数形势下传参

def outter(x):
  def f():
      print(x)
  return f

f=outter(10)
f()

八.装饰器

1  装饰器指的是为被装饰对象添加新功能的工具

    装饰器本身可以是任意可调用对象

    被装饰对象本身也可以是任意可以调用对象

2 装饰器要遵循开放封闭原则(对修改封闭,对扩展开放)

装饰器的实现原则  

      01  不能修改被装饰对象的源代码

      02  不能修改被装饰对象的调用方式

装饰器的目的

      就是在遵循原则01和02的前提为被装饰对象添加新功能

装饰器的模板是

#不写这个也完全没有问题,只是更加严谨一点
from functools import wraps#导入装饰器,为wraper添加新功能,使被装饰对象的一些内置属性赋值给wrapper

def outter(func):
  #加在内层函数最上方
  @wraps(func)#是func下面所有的杠杠开头(_name_ ,_doc_等)的赋值给wrapper
  def wrapper(*args,**kwargs):
      res=func(*args,**kwargs)
      return res
  return wrapper
   
   

统计运行时间装饰器


import time

def outter(func):
  # func=最原始那个index的内存地址
  def wrapper(*args,**kwargs):
      start=time.time()
      res=func(*args,**kwargs) #最原始那个index的内存地址()
      stop=time.time()
      print('run time is %s' %(stop -start))
      return res
  return wrapper

#装饰器的语法糖,相当于index=outter(index)
@outter #index=outter(index) #inex=outter(最原始那个index的内存地址) # index=wrapper的内存地址
def index():
  time.sleep(1)
  print('welcome to index page')
  return 1234

@outter #home=outter(home)
def home(name):
  time.sleep(1)
  print('welcome %s to home page' %name)


res=index() #wrapper的内存地址()
# print('返回值',res)

home('egon')

认证功能装饰器

import time

def auth(func):
  def wrapper(*args,**kwargs):
      name=input('name>>>: ').strip()
      pwd=input('pwd>>>: ').strip()
      if name == 'egon' and pwd == '123':
          print('login successfull')
          res=func(*args,**kwargs)
          return res
      else:
          print('user or pwd error')
  return wrapper



@auth
def index():
  time.sleep(1)
  print('welcome to index page')
  return 1234

res=index()
print(res)

 

3.叠加多个装饰器

01.加载顺序(outter函数的调用顺序):自下而上

02.执行顺序(wrapper函数的执行顺序):自上而下

def outter1(func1): #func1=wrapper2的内存地址
  print('加载了outter1')
  def wrapper1(*args,**kwargs):
      print('执行了wrapper1')
      res1=func1(*args,**kwargs)
      return res1
  return wrapper1

def outter2(func2): #func2=wrapper3的内存地址
  print('加载了outter2')
  def wrapper2(*args,**kwargs): 噩
      print('执行了wrapper2')
      res2=func2(*args,**kwargs)
      return res2
  return wrapper2

def outter3(func3): # func3=最原始的那个index的内存地址
  print('加载了outter3')
  def wrapper3(*args,**kwargs):
      print('执行了wrapper3')
      res3=func3(*args,**kwargs)
      return res3
  return wrapper3



@outter1 # outter1(wrapper2的内存地址)======>index=wrapper1的内存地址
@outter2 # outter2(wrapper3的内存地址)======>wrapper2的内存地址
@outter3 # outter3(最原始的那个index的内存地址)===>wrapper3的内存地址
def index():
  print('from index')

print('======================================================')
index()

'''
加载了outter3
加载了outter2
加载了outter1
======================================================
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
'''

4.有参装饰器

有参认证功能装饰器加使用

import time

current_user={'user':None}#定义一个当前的文件名

def auth(engine='file'):
  def outter(func):
      def wrapper(*args,**kwargs):
          if current_user['user'] is not None:#判断当前文件名字是不是为空,如果不是空就没必要在进行认证了
              res=func(*args,**kwargs)
              return res

          user=input('username>>>: ').strip()
          pwd=input('password>>>: ').strip()

          if engine == 'file':
              # 基于文件的认证
              if user == 'egon' and pwd == '123':
                  print('login successfull')
                  current_user['user']=user#认证之后它会存到当前文件去
                  res=func(*args,**kwargs)
                  return res
              else:
                  print('user or password error')
          elif engine == 'mysql':
              # 基于mysql的认证
              print('基于mysql的认证')
          elif engine == 'ldap':
              # 基于ldap的认证
              print('基于ldap的认证')
          else:
              print('不知道engine')
      return wrapper
  return outter

@auth('ldap') #@outter #index=outter(index) # index=wrapper
def index():
  time.sleep(1)
  print('from index')

@auth('mysql') #@outter # home=outter(home) #home=wrapper
def home(name):
  print('welcome %s' %name)

index()
home('egon')

一.三元表达式

就像通常我们比较2个值大小

def max2(x,y):
  if x > y:
      return x
  else:
      return y

res=max2(10,20)
print(res)

现在直接可以用
res=x if x > y else y
print(res)

二.生成式

优点:方便,改变了编程习惯,可称之为声明式编程

#列表生成式
l=[]
for i in range(10):
  if i > 4:
      l.append(i**2)#我们一开始用的方法


l=[i**2 for i in range(10) if i > 4]#列表生成式
print(l)

# 字典生成式
res={i:i**2 for i in range(10) if i > 3}
print(res)


三.匿名函数

它主要是之定义了一个函数的内存地址,主要用于临时使用一次的场景

func=lambda x,y:x+y
print(func)#<function <lambda> at 0x0000028897231E18>
print(func(1,2))#3

res=(lambda x,y:x+y)(1,2)
print(res)#3

l=[4,2,3]
l_new=sorted(l)#默认从小到大排序
#l_new=sorted(l,reverse=True)#默认从大到小排序
print(l_new)

四.迭代器和生成器

1.什么是迭代?

迭代是一个重复的过程,但是每次重复都是基于上一次重复的结果而继续

#下列循环知识单纯的重复
while True:
  print(1)

# 基于索引的迭代取值
l=['a','b','c']
i=0

while i < len(l):
  print(l[i])
  i+=1

迭代器就是迭代取值的工具

2.为什么要用迭代器

  优点:01 提供一种不依赖索引的迭代取值方法

             02 更节省内存

  缺点:01 不如按照索引的取值方式灵活

             02 取值一次性的,只能往后取,无法预测值的个数

3.如何用迭代器

  可迭代的对象:str\list\tuple\dict\set\文件对象

            但凡内置有iter方法的对象都称之为可迭代对象

  迭代器对象:文件处理

            即内置有iter方法又内置有next方法的对象都称之为   迭代器对象

 

调用可迭代对象下iter方法,会有一个返回值,该返回值就是内置的迭代器对象

字典的迭代器,不依赖索引取值
d={'k1':111,'k2':222,'k3':333}
iter_d=d.__iter__()

try:#检测代码
  print(iter_d.__next__())
  print(iter_d.__next__())
  print(iter_d.__next__())
  print(iter_d.__next__())#一个一个的取值,并不知道里面又几个值,所以要try来盯这段代码
except StopIteration:#捕捉异常,结束循环
  print('取值完毕')
   
   
   
d={'k1':111,'k2':222,'k3':333}
# d={1,2,3,4,5}
# d=[1,2,3,4]

iter_d=d.__iter__()#把可迭代对象装换为迭代器对象
while True:
  try:
      v=iter_d.__next__()#需要去掉迭代器对象__next__
      print(v)
  except StopIteration:#需要自己检测迭代器异常
      break
#所以这就比较麻烦
所以这里用到for循环
for k in d:
  print(k)
 
for循环的底层原理:
1. 调用in后面那个值/对象的__iter__方法,拿到一个迭代器对象iter_obj
2. 调用迭代器对象iter_obj.__next__()将得到的返回值赋值变量名k,循环往复直到取值完毕抛出异常StopIteration
3. 捕捉异常结束循环
       

4.生成器

01 生成器其实就是一种自定义的迭代器

如何得到生成器?

但凡函数内出现yield关键字,再去调用函数不会立即执行函数体代码,会得到一个返回值,该返回值就是生成器对象,即自定义的迭代器.

def func():
  print('first')
  yield 1
  print('second')
  yield 2
  print('third')
  yield 3


g=func()

res1=next(g)
print(res1)#第一次打印,然后暂停

res2=next(g)
print(res2)#第二次打印,然后暂停

res3=next(g)
print(res3)#第三次打印,然后暂停

next(g)#没有值会出现StopIteration

5.生成器表达式

这个和列表表达式不一样的就是将中括号改为小括号

g=(i for i in range(10) if i > 5)
print(next(g))#这里需要注意的是一次只能打印一个数就会暂停,由于是生成器

读取文件中字符的个数
with open('a.txt',mode='rt',encoding='utf-8') as f:
  #print(len(f.read()))#这样一次性全读出来占用内存
  #res=sum[len(line) for line in f]#行数过多的话还是会占用内存

  res = sum(len(line) for line in f)#生成器里面是不是值,所以不论多少并不占用内存
  print(res)

 

 

小练习:

自己生成一个my_range,使之跟默认range有相同的效果

def my_range(start,stop,step=1):
  while start < stop:
      yield start
      start+=step

#range(1,5,2) # 1 3
for i in my_range(1,5,2):
  print(i)

五.函数的递归调用与二分法

1.函数的递归调用:

      在调用一个函数的过程又直接或者间接地调用该函数本身,称之为递归调用

      递归调用必须满足2个条件:

                 01 每进入下一次递归调用,问题的规模都应该有所减少

                 02 递归必须又一个明确的结束条件

     递归有两个明确的阶段

                 01 回溯

                 02 递推

就比方说想知道一个人的年龄的话,问他他说比另外一个人大2岁,以此往复,直到第五个人说出了自己的年龄18岁,那么我们可以知道
age(5)=age(4)+2
age(4)=age(3)+2
age(3)=age(2)+2
age(2)=age(1)+2
age(1)=18#那么这个过程中重上往下推理的过程就叫回溯,然后拿到最后一个人的年龄推导出第一个的年龄就叫逆推

age(n)=age(n-1)+2 # n > 1
age(1)=18         # n = 1


def age(n):
  if n == 1:
      return 18
  return age(n-1)+2

print(age(5))


#这边是一个列表,要求将当中的所有值都取出来
#递归函数的运用
l=[1,[2,[3,[4,[5,[6,[7,[8,[9,]]]]]]]]]

def func(list1):
  for item in list1:#循环从list1里面取值
      if type(item) is not list:#判定是不是列表,不是的话打印
          print(item)
      else:
          # 如果是列表,应该继续走这个函数,直到全部取空这个列表
          func(item)

func(l)

2.二分法(算法)

比如有这样nums=[3,5,7,11,13,23,24,76,103,111,201,202,250,303,341]一个列表,需要取判断一个值在不在这个列表当中

比如我们find_num=202,最开始想到的就是取出这个了列表中所有的值,看有没有和202相等的就可以了

for num in nums:
  if num == find_num:
      print('find it')
      break
else:
  print('not exists')
  #find it
  #这样就可以发现202是在这个列表中的
   
但是这样你会发现要是这个列表里面的元素非常多,这样会很占用内存
我们可以想象是不是可以从中间开始找呢,一直切2部分找,这样是不是更合理一点嗯
nums=[3,5,7,11,13,23,24,76,103,111,201,202,250,303,341]


def binary_search(list1,find_num):先定义一个binary_search函数
  print(list1)
  if len(list1) == 0:判断一下列表是不是空列表,列表一直中间切片,如果到空列表还没找到的话,就不存在
      print('not exist')
      return
  mid_index=len(list1) // 2#取出中间的一个数等待和你想找的值比大小
  if find_num > list1[mid_index]:#如果找的值比中间的值大的话,那么就可以从中间这个值向后切片重新分割寻找
      binary_search(list1[mid_index + 1:],find_num)
  elif find_num < list1[mid_index]:#同上,如果小于则反之
      binary_search(list1[:mid_index],find_num)
  else:
      print('find it')#如果刚好等于的话就直接找到这个值了

binary_search(nums,203)