首页 > 技术文章 > python基础之函数

yuan-qi 2021-07-29 16:44 原文

一、函数概述

  • 定义:函数是一堆代码的集合体,用于完成某种功能,相当于工具。
  • 原则:先定义,后调用。
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()
      

【补充】nolocal关键字
其实,还有一个nolocal关键字,用的比较少,此处作为了解即可。nonlocal修饰的变量指向上一级作用域的变量。

name = 'root'


def outer():
    name = "武沛齐"

    def inner():
        nonlocal name
        name = 123

    inner()
    print(name)


outer()
print(name)
name = 'root'


def outer():
    name = 'alex'

    def func():
        name = "武沛齐"

        def inner():
            nonlocal name
            name = 123

        inner()
        print(name)

    func()
    print(name)


outer()
print(name)

name = 'root'


def outer():
    name = 'alex'

    def func():
        nonlocal name
        name = "武沛齐"

        def inner():
            nonlocal name
            name = 123

        inner()
        print(name)

    func()
    print(name)


outer()
print(name)

九、函数嵌套

  • 定义:函数可以定义在局部作用域,这样函数可以被局部作用域和其子作用域中调用。
# 示例:
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) # "大了"
    
  • 推导式
    推导式是Python中提供了一个非常方便的功能,可以让我们通过一行代码实现创建list、dict、tuple、set 的同时初始化一些值。

    • 列表

      num_list = [ i for i in range(10)]
      
      num_list = [ [i,i] for i in range(10)]
      
      num_list = [ [i,i] for i in range(10) if i > 6 ]
      
    • 集合

      num_set = { i for i in range(10)}
      
      num_set = { (i,i,i) for i in range(10)}
      
      num_set = { (i,i,i) for i in range(10) if i>3}
      
    • 字典

      num_dict = { i:i for i in range(10)}
      
      num_dict = { i:(i,11) for i in range(10)}
      
      num_dict = { i:(i,11) for i in range(10) if i>7}
      
    • 元组,不同于其他类型,会得到一个生成器。

      # 不会立即执行内部循环去生成数据,而是得到一个生成器。
      data = (i for i in range(10))
      print(data)
      for item in data:
          print(item)
      

【烧脑面试题】

  def num():
      return [lambda x: i * x for i in range(4)]
  
  
  # 1. num()并获取返回值  [函数,函数,函数,函数] i=3
  # 2. for循环返回值
  # 3. 返回值的每个元素(2)
  result = [m(2) for m in num()]  # [6,6,6,6]
  print(result)
  
def num():
    return (lambda x: i * x for i in range(4))


# 1. num()并获取返回值  生成器对象
# 2. for循环返回值
# 3. 返回值的每个元素(2)
result = [m(2) for m in num()]  # [0,2,4,6 ]
print(result)

十一、生成器函数

  • 定义:生成器是由函数+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 中获取即可。
    
  • 总结:所以,当以后需要我们在内存中创建很多数据时,可以想着用基于生成器来实现一点一点生成(用一点生产一点),以节省内存的开销。

【补充】 yield from(开发中不常用)
在生成器部分我们了解了yield关键字,其在python3.3之后有引入了一个yield from。相当于yield嵌套。执行到foo()时,调到foo函数执行,执行完再回到func继续执行。

def foo():
    yield 2
    yield 2
    yield 2


def func():
    yield 1
    yield 1
    yield 1
    yield from foo()
    yield 1
    yield 1


for item in func():
    print(item)

十二、【内置函数】

image

第一组(5个)

  • abs,绝对值

    v = abs(-8)  # 8
    
  • pow,指数

    v1 = pow(2,3) # 2的3次方  2**3  
    print(v1)  # 8
    
  • sum,求和

    v1 = sum([-11, 22, 33, 44, 55]) # 可以被迭代-for循环
    print(v1)
    
  • divmod,求商和余数

    v1, v2 = divmod(9, 2)
    print(v1, v2) # v1,v2分别接收商和余数
    
  • round,小数点后n位(四舍五入)

    v1 = round(4.11786, 2)
    print(v1) # 4.12
    

第二组(4个)

  • min,最小值

    v1 = min(11, 2, 3, 4, 5, 56)
    print(v1) # 2
    
    v2 = min([11, 22, 33, 44, 55]) # 迭代的类型(for循环)
    print(v2)
    
    v3 = min([-11, 2, 33, 44, 55], key=lambda x: abs(x))
    print(v3) # 2
    
  • max,最大值

    v1 = max(11, 2, 3, 4, 5, 56)
    print(v1)
    
    v2 = max([11, 22, 33, 44, 55])
    print(v2)
    
    v3 = max([-11, 22, 33, 44, 55], key=lambda x: x * 10)
    print(v3) # 55
    
  • all,是否全部为True

    v1 = all(   [11,22,44,""]   ) # False
    
  • any,是否存在True

    v2 = any([11,22,44,""]) # True
    

第三组(3个)

  • bin,4种进制整数转二进制
  • oct,4种进制整数转八进制
  • hex,4种进制整数转十六进制

第四组(2个)

  • ord,获取字符对应的unicode码点(十进制)

    v1 = ord("武")
    print(v1, hex(v1))
    
  • chr,根据码点(十进制)获取对应字符

    v1 = chr(27494)
    print(v1)
    

第五组(9个)

  • int,可接收4种进制整数或字符串

    int(0x7e)
    int('0x7e', 16)  # 传入非十进制字符串时,必须指定进制的数值。
    
  • float,可接收的参数:

    (1)4种进制整数;
    (2)boo值,True则为1.0,False则为0.0;
    (3)十进制数字的字符串,float('32'), float('3.2')
    
  • str,unicode编码

    (1)接收4中进制整数,返回十进制字符串
    (2)接收其他类型,直接返回原格式字符串形式。
    
  • bytes,utf-8、gbk编码

    v1 = "武沛齐"  # str类型
    
    v2 = v1.encode('utf-8')  # bytes类型
    
    v3 = bytes(v1,encoding="utf-8") # bytes类型
    
  • bool

      None、0、空字符串、空列表、空字典、空集合、空元组返回False,其他返回True。
    
  • list

  • dict

  • tuple

  • set

第六组(13个)

  • len

  • print

  • input

  • open

  • type,获取数据类型

    v1 = "123"
    
    if type(v1) == str:
        pass
    else:
        pass
    
  • range

    range(10)
    range(1, 10, 2) # 步长
    range(10, 1, -1) # 逆向
    
  • enumerate

    v1 = ["武沛齐", "alex", 'root']
    
    for num, value in enumerate(v1, 1):
        print(num, value)
    
  • id,返回内存地址

  • hash,哈希函数

    v1 = hash("武沛齐")
    
  • help,帮助信息

    • pycharm,不用
    • 终端,使用
  • zip,拉链函数

    v1 = [11, 22, 33, 44, 55, 66]
    v2 = [55, 66, 77, 88]
    v3 = [10, 20, 30, 40, 50]
        
    result = zip(v1, v2, v3)
    for item in result:
        print(item)
    
  • callable,是否可执行,后面是否可以加括号。

    v1 = "武沛齐"
    v2 = lambda x: x
    def v3():
        pass
    
    
    print( callable(v1) ) # False
    print(callable(v2))
    print(callable(v3))
    
  • sorted,排序

    v1 = sorted([11,22,33,44,55])
    
    info = {
        "wupeiqi": {
            'id': 10,
            'age': 119
        },
        "root": {
            'id': 20,
            'age': 29
        },
        "seven": {
            'id': 9,
            'age': 9
        },
        "admin": {
            'id': 11,
            'age': 139
        },
    }
    
    result = sorted(info.items(), key=lambda x: x[1]['id'])
    print(result)
    
    data_list = [
        '1-5 编译器和解释器.mp4',
        '1-17 今日作业.mp4',
        '1-9 Python解释器种类.mp4',
        '1-16 今日总结.mp4',
        '1-2 课堂笔记的创建.mp4',
        '1-15 Pycharm使用和破解(win系统).mp4',
        '1-12 python解释器的安装(mac系统).mp4',
        '1-13 python解释器的安装(win系统).mp4',
        '1-8 Python介绍.mp4', '1-7 编程语言的分类.mp4',
        '1-3 常见计算机基本概念.mp4',
        '1-14 Pycharm使用和破解(mac系统).mp4',
        '1-10 CPython解释器版本.mp4',
        '1-1 今日概要.mp4',
        '1-6 学习编程本质上的三件事.mp4',
        '1-18 作业答案和讲解.mp4',
        '1-4 编程语言.mp4',
        '1-11 环境搭建说明.mp4'
    ]
    result = sorted(data_list, key=lambda x: int(x.split(' ')[0].split("-")[-1]) )
    print(result)
    

十三、函数相关的坑

函数定义中存在默认值参数时,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] 

【补充】深浅拷贝

  • 浅拷贝

    • 不可变类型,不拷贝。

      import copy
      
      v1 = "武沛齐"
      print(id(v1)) # 140652260947312
      
      v2 = copy.copy(v1) 
      print(id(v2)) # 140652260947312
      

      按理说拷贝v1之后,v2的内存地址应该不同,但由于python内部优化机制,内存地址是相同的,因为对不可变类型而言,如果以后修改值,会重新创建一份数据,不会影响原数据,所以,不拷贝也无妨。

    • 可变类型,只拷贝第一层。

      import copy
      
      v1 = ["武沛齐", "root", [44, 55]]
      print(id(v1))  # 140405837216896
      print(id(v1[2]))  # 140405837214592
      
      v2 = copy.copy(v1)
      print(id(v2))  # 140405837214784
      print(id(v2[2]))  # 140405837214592
      

image

  • 深拷贝

    • 不可变类型,不拷贝

      import copy
      
      v1 = "武沛齐"
      print(id(v1))  # 140188538697072
      
      v2 = copy.deepcopy(v1)
      print(id(v2))  # 140188538697072
      

      特殊的元组:

      • 元组元素中无可变类型,不拷贝

        import copy
        
        v1 = ("武沛齐", "root")
        print(id(v1))  # 140243298961984
        
        v2 = copy.deepcopy(v1)
        print(id(v2))  # 140243298961984
        
      • 元素元素中有可变类型,找到所有【可变类型】或【含有可变类型的元组】 均拷贝一份

        import copy
        
        v1 = ("武沛齐", "root", [11, [44, 55], (11, 22), (11, [], 22), 33])
        v2 = copy.deepcopy(v1)
        
        print(id(v1))  # 140391475456384
        print(id(v2))  # 140391475456640
        
        print(id(v1[2]))  # 140352552779008
        print(id(v2[2]))  # 140352552920448
        
        print(id(v1[2][1]))  # 140642999940480
        print(id(v2[2][1]))  # 140643000088832
        
        print(id(v1[2][2]))  # 140467039914560
        print(id(v2[2][2]))  # 140467039914560
        
        print(id(v1[2][3]))  # 140675479841152
        print(id(v2[2][3]))  # 140675480454784
        
    • 可变类型,找到所有层级的 【可变类型】或【含有可变类型的元组】 均拷贝一份

      import copy
      
      v1 = ["武沛齐", "root", [11, [44, 55], (11, 22), (11, [], 22), 33]]
      v2 = copy.deepcopy(v1)
      
      print(id(v1))  # 140391475456384
      print(id(v2))  # 140391475456640
      
      print(id(v1[2]))  # 140352552779008
      print(id(v2[2]))  # 140352552920448
      
      print(id(v1[2][1]))  # 140642999940480
      print(id(v2[2][1]))  # 140643000088832
      
      print(id(v1[2][2]))  # 140467039914560
      print(id(v2[2][2]))  # 140467039914560
      
      print(id(v1[2][3]))  # 140675479841152
      print(id(v2[2][3]))  # 140675480454784
      
      import copy
      
      v1 = ["武沛齐", "root", [44, 55]]
      v2 = copy.deepcopy(v1)
      
      print(id(v1))  # 140405837216896
      print(id(v2))  # 140405837214784
      
      
      print(id(v1[2]))  # 140563140392256
      print(id(v2[2]))  # 140563140535744
      

image

image

image


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

image

推荐阅读