Python教程

python基础之函数

本文主要是介绍python基础之函数,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

一、函数概述

  • 定义:函数是一堆代码的集合体,用于完成某种功能,相当于工具。
  • 原则:先定义,后调用。
def foo():
	bar()
	print('from foo')

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

foo()

# 输出结果:
from bar
from foo

# 注意:此处并不会报错,因为函数在定义阶段只检查语法错误,当调用foo函数的时候,bar函数已经载入到了内存中,所以即使bar的定义在foo后面,调用的时候也能找到bar的函数代码,因此能正常运行。

二、函数应用场景

  • 代码过长时,组织结构不清晰,可读性差,可以通过函数分块;
  • 代码冗余,对于重复性代码,一次定义,多次调用;
  • 使用函数可增强代码的可维护性和扩展性。

三、函数的定义

  • 先定义(大致有三种方式)
    1. 无参函数
    def 函数名():
    	'''函数功能注释'''
    	
    	# 注释
    	函数内代码块
    	
    	return  返回值  # 非必须
    
    1. 有参函数
    def 函数名(参数1, 参数2, ...):
    	'''函数功能注释'''
    	
    	# 注释
    	函数内代码块
    	
    	return  返回值  # 非必须
    
    1. 空函数(一般用于前期搭建框架使用)
    def 函数名():
    	pass
    
  • 后调用
    【调用函数发生的事情】通过函数名找到函数的内存地址,然后加括号就是在触发函数体代码的执行。
    函数名()  # 方式一:无参函数调用
    函数名(参数1,参数2, ...)   # 方式二:有参函数调用
    
  • 应用场景
    1. 直接调用;
    2. 作为表达式的一部分;
    3. 作为返回值;
    4. 作为可变类型的元素;
    5. 作为函数的参数。

四、函数的分类

  • 内置函数:python解释器内已经定义好的函数,可以直接调用;
  • 自定义函数:自己定义的函数,需要自己实现函数的所有功能,才能调用;

五、函数名

  1. 函数名的命名规则跟变量相同;
  2. 函数名可以看做是一个变量,可以被赋值,则该函数将指向新赋值的内存地址,不再指向函数代码块内存地址,因此定义函数时注意函数名不能使用内置函数名,否则内置函数会被覆盖;
# 示例:

# len内置函数用于计算值得长度
v1 = len("武沛齐")
print(v1) # 3

# len重新定义成另外一个函数
def len(a1,a2):
    return a1 + a2

# 以后执行len函数,只能按照重新定义的来使用
v3 = len(1,2)
print(v3)

  1. 函数名可以赋值给其他变量,则被赋值变量会指向函数代码块内存地址,该变量也可代指该函数,通过变量名加括号同样可以执行该函数;
# 示例1:
def func(a1,a2):
    print(a1,a2)
    
func_list = [func,func,func]

func(11,22)
func_list[0](11,22)
func_list[1](33,44)
func_list[2](55,66)

# ***********************

# 示例2:
def func(a1,a2):
    print(a1,a2)

# 执行func函数
func(11,22)

# func重新赋值成一个字符串
func = "武沛齐"

print(func)
  1. 函数名可以作为函数的参数、返回值;
# 示例1:做参数

def plus(num):
    return num + 100

def handler(func):
    res = func(10) # 110
    msg = "执行func,并获取到的结果为:{}".format(res)
    print(msg) # 执行func,并获取到的结果为:110
   
# 执行handler函数,将plus作为参数传递给handler的形式参数func
handler(plus)

# ***********************************************

# 示例2:做返回值

def plus(num):
    return num + 100

def handler():
	print("执行handler函数")
    return plus
    
result = handler()
data = result(20) # 120
print(data)
  1. 函数可被哈希,所以函数名也可以当做集合的元素、字典的键,也可以作为字典、元组、列表、集合的元素。
# 示例

def send_msg(mobile, content):
    """发送短信"""
    pass

def send_email(to_email, subject, content):
    """发送图片"""
    pass

def send_wechat(user_id, content):
    """发送微信"""
    pass

func_list = [
    {"name": send_msg, "params": {'mobile': "15131255089", "content": "你有新短消息"}},
    {"name": send_email, "params": {'to_email': "wupeiqi@live.com", "subject": "报警消息", "content": "硬盘容量不够用了"}},
    {"name": send_wechat, "params": {'user_id': 1, 'content': "约吗"}},
]

#  {"name": send_msg, "params": {'mobile': "15131255089", "content": "你有新短消息"}},
for item in func_list:
    func = item['name'] # send_msg
    param_dict = item['params'] # {'mobile': "15131255089", "content": "你有新短消息"}
    func(**param_dict) # send_msg(**{'mobile': "15131255089", "content": "你有新短消息"})

六、函数的返回值

  1. return:函数结束的标志,即函数体运行到return时会立刻终止函数的执行。
  2. 返回值分类:
    • return:返回None
    • return None : 返回None
    • 不写return:返回None
    • return a :返回变量a的内存地址。(a可以是变量、具体的数值、函数名等任意类型)
    • return a, b : 返回元组(a, b)

七、函数的参数

1. 函数的参数分为形参和实参:

  • 形参:函数定义时在函数名后面的括号内定义的变量;
  • 实参:函数被调用时传入的变量或值。

2. 形参的类型:

  • 普通形参:函数定义中只是定义一个变量名,调用函数时必须传值;
  • 默认值形参:函数定义时不但定义了变量名,同时还赋值,调用函数时可以不传该参数;
  • 动态形参:
    • *args:只接受任意多的位置实参,将传入的所有位置实参组成一个元组,传递给args,因此args的值必定是一个空值或元组;
    • **kwargs:只接受任意多的关键字实参,将传入的关键字实参以字典的形式传递给kwargs,因此kwargs必定是一个空值或字典。

3. 实参的类型:

  • 位置参数:对应普通形参和默认值形参,顺序必须一一对应;
  • 关键字参数:位置可以不固定。
def func(*arg, **kwargs):
	print(arg, kwargs)

func(3,4,5,x=66,y=77)

# 输出结果:
>>>  (3, 4, 5) {'x':66, 'y':77}

【注意】函数调用时,传参也可以使用 * 和 ** 。 * 后面可以是列表、元组或集合,传值时会将其解压成位置实参传递,** 后面为字典,传值时会将其解压成关键字实参传递。

def func(a, b, c, x, y):
	print(a, b, c, x, y)
	
func(*(1,2,3), **{'x':4, 'y':5})

# 输出结果:
>>>  1, 2, 3, 4, 5

4. 形参的顺序:

  • 普通参数必须放在最前面;
  • **kwargs必须放在最后面;
  • 动态参数必须在普通参数后面;
  • **kwargs必须在*args后面;
  • 默认值参数与*args的位置可以互换。
    实参的顺序:
    形参的顺序确定后,实参的顺序跟形参保持一致。
def func(a, b, *args, c=9, **kwargs):
    print(a, b, c, args, kwargs)

func(1, 2, 99, 77, x = 4, y = 5)

#输出结果:
>>>  1 2 9 (99, 77) {'x': 4, 'y': 5}
def func(a, b, c=9, *args, **kwargs):
    print(a, b, c, args, kwargs)

func(1, 2, 99, 77, x = 4, y = 5)

#输出结果:
>>>  1 2 99 (77,) {'x': 4, 'y': 5}

5. 传参

函数执行传参时,传递的是内存地址。优点:

  • 节省内存。不用将实参复制一份再传值;
  • 对于可变类型(列表、字典、集合)并且函数中修改元素的内容,所有的使用该变量的地方都会修改;如果函数内重新赋值,则不影响外面的可变类型的值;【参看后面坑1】
  • 对于不可变类型(例如字符串),无法修改内部元素,只能重新赋值;

八、名称空间和作用域

名称空间

  1. 名称空间:存放名字的地方,是对栈区的划分。
    • 分类:
      • 内置名称空间:存放python解释器内置的名字,解释器启动时产生,关闭则销毁;
      • 全局名称空间:只要不是函数内定义的、也不是内置的,剩下的都是全局名称空间的名字,因此,for、while、if语句块中定义的变量都属于全局名称空间,全局有效,语句块执行完毕后,变量不会被销毁;python文件执行则产生,运行完毕后销毁;
      • 局部名称空间:函数内的名字,调用函数时存活,函数调用完毕后则销毁(函数嵌套时有不销毁的特殊情况)。
    • 加载顺序:
      内置 --> 全局 --> 局部
    • 销毁顺序
      局部 --> 全局 --> 内置
    • 查找顺序:从当前所在的位置向外层一层一层查找
      查找优先级:
      局部 --> 全局 --> 内置
      名称空间的“嵌套”关系是以函数定义阶段为准,与调用位置无关。
      示例:
      image

作用域

  1. 作用域:名字的作用范围。python是以函数为作用域,所以在函数内创建的所有数据,可以此函数中被使用,无法在其他函数中被使用。
    • 变量作用域:
      • 全局变量:全局作用域中创建的变量,可全局使用,也可以在局部作用域中被使用,一般都使用大写字母命名;
      • 局部变量:在函数内创建的变量称为局部变量,局部变量只能在局部作用域中被使用。
    • 局部作用域中使用全局作用域的变量:局部作用域中只能对全局变量进行读取和修改内部元素(可变类型)的操作,无法对全局变量进行重新赋值,如果非要重新赋值,需要在局部作用域用global关键字修饰该变量,才能重新赋值。示例如下:
      COUNTRY = "中国"
      CITY_LIST = ["北京","上海","深圳"]
      
      def download():
      	url = "http://www.xxx.com"
      
      	global CITY_LIST
      	CITY_LIST =  ["河北","河南","山西"]
      	print(CITY_LIST)
      
      	global COUNTRY
      	COUNTRY = "中华人民共和国"
      	print(COUNTRY)
      
      def upload():
      	file_name = "rose.zip"
      	print(COUNTRY)
      	print(CITY_LIST)
      
      download()
      upload()
      

九、函数嵌套

  • 定义:函数可以定义在局部作用域,这样函数可以被局部作用域和其子作用域中调用。
# 示例:
def func():
    print("沙河高晓松")
    
def handler():
    print("昌平吴彦祖")
    def inner():
        print("朝阳大妈")
	inner()
    func()
    print("海淀网友")

handler()

  • 应用场景
    其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套。
  • 函数嵌套作用域分析方法
    • 优先在自己的作用域找,自己没有就去上级作用域。
    • 在作用域中寻找值时,要确保此次此刻值是什么。
    • 分析函数的执行,并确定函数作用域链。(函数嵌套)

闭包

  • 闭包
    • 定义:闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上 闭包是基于函数嵌套搞出来一个中特殊嵌套)
    • 应用场景
      • 场景一:封装数据防止污染全局。
        def func(age):
        	name = "武沛齐"
        
        	def f1():
        		print(name, age)
        
        	def f2():
        		print(name, age)
        
        	def f3():
        		print(name, age)
        
        	f1()
        	f2()
        	f3()
        
        func(123)
        
      • 场景二:封装数据封到一个包里,使用时再取。
        案例如下:
        def task(arg):
        	def inner():
        		print(arg)
        	return inner
        
        inner_func_list = []
        for val in [11,22,33]:
        	inner_func_list.append( task(val) )
        
        inner_func_list[0]() # 11
        inner_func_list[1]() # 22
        inner_func_list[2]() # 33
        

装饰器

  • 装饰器
    • 定义:装饰器,在不修改原函数内容的前提下,通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义)。
    • 实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
    • 实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
    • 适用场景:多个函数统一在执行前后自定义一些功能。
    • 示例:基于装饰器实现的伪代码:
      from flask import Flask
      
      app = Flask(__name__)
      
      def auth(func):
      	def inner(*args, **kwargs):
      	# 在此处,判断如果用户是否已经登录,已登录则继续往下,未登录则自动跳转到登录页面。
      		return func(*args, **kwargs)
      
      	return inner
      
      
      @auth
      def index():
      return "首页"
      
      
      @auth
      def info():
      return "用户中心"
      
      
      @auth
      def order():
      return "订单中心"
      
      
      def login():
      return "登录页面"
      
      
      app.add_url_rule("/index/", view_func=index, endpoint='index')
      app.add_url_rule("/info/", view_func=info, endpoint='info')
      app.add_url_rule("/order/", view_func=order, endpoint='order')
      app.add_url_rule("/login/", view_func=login, endpoint='login')
      
      app.run()
      
      
    • 重要补充:functools:其实,一般情况下大家不用functools也可以实现装饰器的基本功能,但后期在项目开发时,不加functools会出错(内部会读取__name__,且__name__重名的话就报错),所以在此大家就要规范起来自己的写法。
    • 【装饰器模板】 实际使用时套用该格式即可:
      import functools
      
      
      def auth(func):
      	@functools.wraps(func)
      	def inner(*args, **kwargs):
      		"""原函数执行前需要执行的代码"""
      		res = func(*args, **kwargs)  # 执行原函数
      		"""原函数执行后需要执行的代码"""
      		return res
      
      	return inner
      

十、匿名函数和三元运算

  • 定义:匿名函数,则是基于lambda表达式实现定义一个可以没有名字的函数。
  • 格式:lambda 参数:函数体
    1. 参数,支持任意参数。
      lambda x: 函数体
      lambda x1,x2: 函数体
      lambda *args, **kwargs: 函数体
      
    2. 函数体,只能支持单行的代码。
    3. 返回值,默认将函数体单行代码执行的结果返回给函数的执行这。
      func = lambda x: x + 100
      
      v1 = func(10)
      print(v1) # 110
      
  • 简单的函数,可以基于lambda表达式实现。
    def func(data):
    	return data.replace("苍老师","***")
    
    func= lambda data: data.replace("苍老师","***")
    
  • 【扩展】三元运算
    • 格式:结果 = 条件成立时 if 条件 else 不成立
    • 示例:匿名函数和三元运算的综合运用:
    func = lambda x: "大了" if x > 66 else "小了"
    
    v1 = func(1)
    print(v1) # "小了"
    
    v2 = func(100)
    print(v2) # "大了"
    

十一、生成器函数

  • 定义:生成器是由函数+yield关键字创造出来的写法,在特定情况下,用他可以帮助我们节省内存。

    • 生成器函数,但函数中有yield存在时,这个函数就是生产生成器函数。
    • 生成器对象,执行生成器函数时,会返回一个生成器对象。
    • 生成器的特点是,记录在函数中的执行位置,下次执行next时,会从上一次的位置基础上再继续向下执行。
    def func():
    	print(111)
    	yield 1
    
    	print(222)
    	yield 2
    
    	print(333)
    	yield 3
    
    	print(444)
    
    data = func()
    
    # 执行生成器函数func,返回的是生成器对象。
    # 注意:执行生成器函数时,函数内部代码不会立刻执行。
    
  • 生成器对象的调用

    • 使用 next() 函数
    def func():
    	print(111)
    	yield 1
    
    	print(222)
    	yield 2
    
    	print(333)
    	yield 3
    
    	print(444)
    
    data = func()
    
    v1 = next(data)
    print(v1)
    
    v2 = next(data)
    print(v2)
    
    v3 = next(data)
    print(v3)
    
    v4 = next(data)
    print(v4)  # 结束或中途遇到return,程序爆:StopIteration 错误
    
    • 使用 for 循环
     data = func()
    
     for item in data:
       print(item)
    
    • 使用生成器对象的send()函数【几乎用不到】
    def func():
       print(111)
       v1 = yield 1
       print(v1)
    
       print(222)
       v2 = yield 2
       print(v2)
    
       print(333)
       v3 = yield 3
       print(v3)
    
       print(444)
    
    
    data = func()
    
    n1 = data.send(None)
    print(n1)
    
    n2 = data.send(666)
    print(n2)
    
    n3 = data.send(777)
    print(n3)
    
    n4 = data.send(888)
    print(n4)
    
  • 应用场景举例:

    • 假设要让你生成 300w个随机的4位数,并打印出来。

      • 在内存中一次性创建300w个
      • 动态创建,用一个创建一个。
    import random
    
    val = random.randint(1000, 9999)
    print(val)
    
    import random
    
    data_list = []
    for i in range(300000000):
        val = random.randint(1000, 9999)
    	data_list.append(val)
        
    # 再使用时,去 data_list 中获取即可。
    # ...
    
    import random
    
    
    def gen_random_num(max_count):
        counter = 0
        while counter < max_count:
            yield random.randint(1000, 9999)
            counter += 1
    
    
    data_list = gen_random_num(3000000)
    # 再使用时,去 data_list 中获取即可。
    
  • 总结:所以,当以后需要我们在内存中创建很多数据时,可以想着用基于生成器来实现一点一点生成(用一点生产一点),以节省内存的开销。

十二、函数相关的坑

函数定义中存在默认值参数时,python在创建函数(未执行)时,如果发现函数有默认值参数时,则会在函数内部创建一块区域并维护这个默认值(使用a代指)。

函数被调用过程中:

  • 对于默认值为不可变类型,调用过程不会修改这个维护的默认值;
  • 对于默认值为可变类型,如果调用未给该默认值参数传值,则使用a,如果函数内部修改这个值而非重新赋值的情况下,则这个值会被修改,下次不传参调用函数时,使用的是修改后的a,因此,后面每次这样的调用都可能修改a的值,需特别注意。如果调用时给默认值参数传值,则不会影响 a 的值。
    示例:
 # 内存中创建空间存储 [1, 2] ,假设内存地址为:10000001
 def func(a1, a2=[1, 2]):
     a2.append(a1)
     return a2
 
 # 未给默认值参数传值,a1=10
 # a2 -> 10000001
 # v1 -> 10000001
 # 执行完成后,10000001地址的值为 [1, 2, 10]
 v1 = func(10)
 
 
 # 未给默认值参数传值,a1=20
 # a2 -> 10000001
 # v2 -> 10000001
 # 执行完成后,10000001地址的值为 [1, 2, 10, 20]
 v2 = func(20)
 
 # 给默认值参数传值[11, 22],假设内存地址为11111110,a1=30
 # a2 -> 11111110   [11,22,30]
 # v3 -> 11111110
 # 执行完成后,11111110 值为[11, 22, 30] , 10000001 值仍为[1, 2, 10, 20]
 v3 = func(30, [11, 22])
 
 # 未给默认值参数传值,a1=40
 # a2 -> 10000001
 # v4 -> 10000001
 # 执行完成后,10000001 值为[1, 2, 10, 20, 40]
 v4 = func(40)
 
 # 执行到此处,v1、v2、v4都指向10000001的内存地址,因此值都为 
 # [1, 2, 10, 20, 40],v3指向新开辟的内存地址 11111110,因此值为 [11,22,30]。
 print(v1) # [1, 2, 10, 20, 40]
 print(v2) # [1, 2, 10, 20, 40]
 print(v3) # [11,22,30]
 print(v4) # [1, 2, 10, 20, 40] 

python函数思维导图:
清晰版请下载:python基础-思维导图-函数

image

这篇关于python基础之函数的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!