5.python入门-函数

本文介绍了Python函数的定义、调用和修饰器,详细探讨了各种参数类型,包括位置参数、关键字参数、默认参数、可变长度参数、命名关键字参数。此外,还讲解了变量作用域、闭包、递归函数、lambda表达式和生成器的概念和用法,是Python初学者掌握函数的宝贵资料。
摘要由CSDN通过智能技术生成

5.1 函数的定义和调用

1.函数的定义

函数是为了解把会重复使用的代码定义成一个对象,在复用的时候可直接调用。

  • def 函数名(形参) 函数名的命名等同变量命名规则。
  • 函数执行的代码以冒号起始,并且缩进。
  • return函数终止执行,并返回结果,没有return返回None。
def func(n1):   # 函数执行的代码以冒号起始,并且缩进。
		"函数_文档字符串"   # 函数说明文档
        return n1+10,n1  # 返回值可以是任意类型,如有逗号,会转换成元组
        print(n1)     # 遇到return就会立即退出函数(在循环中也会终止函数)
func(10)              # return结果需要传递给变量
res = func(10)    
print(res)             # (20, 10)

2. 函数名调用

  • 调用过程中会执行print,如有return返结果。没有默认是None。
  • help ,查看说明文档。
def func(n1):
        print(n1+10)     # print在函数调用执行过程中输出内容
        print(n1)        
res = func(10)           # 调用函数,会执行打印
print(res)               # 如果函数中没写return,则默认返回None
  • 函数名是可哈希的,可作为集合元素或字典的键等。
  • 函数赋值给一个变量后,这个变量也具有了此函数的功能
def func():
    return 123
cond_dict = {"函数":func} 
num = cond_dict['函数']  
num() # 123
  • 函数名也可当成对象,从另一个函数中返回出来而去构建高阶函数,比如:参数是函数、返回值是函数。
  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)
  1. 函数名做另一个函数的返回值
def plus(num):
    print(num + 100)

def handler():
    print("执行handler函数")
    return plus

result = handler()
data = result(20)  # 120
print(data)
  1. 同时为参数和返回值
def f1():
    print(123)

def f2(arg):
    ret = arg()
    return f1

v1 = f2(f1)   # 在return之前先运行了f1,所以过程中打印了123

v2 = v1()  # f2 返回f1 所以v2现在指向f1()
print(v2) 

3. 函数修饰器

  • 函数修饰器是一种特殊的语法,用于在不修改原始函数定义的情况下,通过@函数修改或增强函数的行为。(批量操作会更有意义)
  • 修饰器本身是一个函数,它接收一个函数对象作为参数,并返回一个新的函数对象或一个可调用对象。
  • 修饰器以 @ 符号开头写在函数定义的上方,多个修饰器可以叠加使用。
def my_decorator(func):
    def wrapper():
        print("Before the function is called.")
        func()
        print("After the function is called.")
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()
say_hello.__name__  # 'wrapper'
  • functools.wraps能 传递.__name__和.doc
import functools

def outer(origin):
    @functools.wraps(origin)   # 传递函数名称和注释
    def inner(*args, **kwargs):
		# 执行前需执行的代码
        res = origin(*args, **kwargs)  # 调用原来的func函数
        # 执行完成需执行的代码
        return res
    return inner


@outer  # func = outer(func) 
def func():
    pass

func.__name__    #  func

5.2 函数的参数

Python 的函数具有非常灵活多样的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。从简到繁的参数形态如下:
形参与实参:当一个函数被调用时,实参的值被赋给对应的形参,并作为函数内部变量来使用。

def greet(name):
    print("Hello, " + name + "!")

greet("Alice")

在这个例子中,name 是 greet() 函数的形参,而 “Alice” 是传递给函数的实参。当函数被调用时,“Alice” 被赋值给 name,并在函数体内输出 “Hello, Alice!”。

1. 位置参数

  • 按照顺序依次传递的参数,在函数内部可以通过对应的参数名来访问。
  • 位置传参的实参个数不多不少,依次传参。

2. 关键字参数

用于函数调用,通过“键-值”形式加以指定。可以让函数更加清晰、容易使用。
Python 允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

  • 通过名称来指定传递给函数的参数,不需要按照定义顺序传递。
  • 可以和位置参数混合使用,但必须在位置参数之后。
def print_hello(name,age,sex):
   print('Name:%s Age:%d Sex:%s' %(name,age,sex))
print_hello('male',19,'lili')          # Name:male Age:19 Sex:lili
print_hello('lili',sex='male',age=18)  # Name:lili Age:18 Sex:male 

3. 默认参数

  • 用于定义函数,为参数提供默认值,调用函数时可传可不传该默认参数的值,如果不传就取定义时候的值,传了改变为新的值.
  • 默认参数的值通常应设为不可变类型。默认值是可变类型时,执行函数会修改默认值。
def print_hello(name,age,sex = '女' ):
#sex = '女' 为默认参数
   print('Name:%s Age:%s Sex:%s' %(name,age,sex))
print_hello('Muse',26,'男')

4. 可变长度的参数

  1. 可变长度的位置参数
    在位置参数的最后一个形参名前加*,溢出的位置参数都会被接收,以元组的形式保存下来赋值给该形参
def foo(x,z=1,*args): #在最后一个形参名args前加*号
    print(x,z,args)
foo(1)        # 1 1 ()
foo(1,2)      # 1 2 ()
foo(1,2,3)  # 1 2 (3, )
  1. 可变长度的关键字参数
  • 在最后一个形参名前面加**,调用函数时,溢出的关键字参数都会被接收,以字典的形式保存下来赋值给该形参。
  • 多个形参时,顺序必须是:位置参数、默认参数和可变位置参数、关键字参数。
def func(arg1, arg2=0, *args, **kwargs):
    pass

def func(a1, a2, a3, a4=10, *args, a5=[1,2], **kwargs):
    a5.append(a2)
    return a5,a4,kwargs # 返回的是元组
func(2,4,a5=[2,3],a3=3,a6=6)   # ([2, 3, 4], 10, {'a6': 6})

5. 命名关键字参数

  • 限制必须使用关键字参数传递,也可以设置默认值。
  • 命名关键字参数,在参数nkw 前面加个分隔符 *
  • 命名关键字参数在可变关键字参数之前
  • 即使在调用函数时只有一个参数也必须使用关键字参数传递。
def printinfo(arg1, *, nkw, **kwargs):
    print(arg1)
    print(nkw)
    print(kwargs)

printinfo(70, nkw=10, a=1, b=2)
# 70
# 10
# {'a': 1, 'b': 2}

printinfo(70, 10, a=1, b=2)
# TypeError: printinfo() takes 1 positional argument but 2 were given
# 没有写参数名`nwk`,因此 10 被当成「位置参数」,而原函数只有 1 个位置函数,现在调用了 2 个,因此程序会报错。

6. 解包裹(***

  • 在传递元组时,* 让元祖的每一个元素对应一个位置参数
  • 在传递字典时,** 让字典的每个键值对作为一个关键字参数传递给函数
def func(a1,a2):
    print(a1,a2)
func( *[11,22] )            # 11 22
func( **{"a1":11,"a2":22} ) # 11 22
def func(*args,**kwargs):
    print(args,kwargs)
    print(id(args),id(kwargs))
func( [11,22,33], {"k1":1,"k2":2} )   # ([11, 22, 33], {'k1': 1, 'k2': 2}) {}
func( *[11,22,33], **{"k1":1,"k2":2} ) # (11, 22, 33) {'k1': 1, 'k2': 2}

案例1:数据清洗,去除空格! #?,首字母大写

states = ['   Alabama ', 'Georgia! ', 'Georgia', 'georgia', 'FlOrIda','south    carolina##', 'West virginia? ']
import re 
def remove_punctuation(value):
    return re.sub('[! #? ]', '', value)
clean_ops = [str.strip, remove_punctuation, str.title]

def clean_strings(strings, ops):
    result = []
    for value in strings:
        for function in ops:
              value = function(value)
        result.append(value)
    return result
clean_strings(states,clean_ops)  

案例2 : 批量调用函数。用 字典 调用函数名和用**传递参数

#  send_msg、send_email、send_wechat之前已经定义好的函数,在这里调用
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': "约吗"}},
]

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": "你有新短消息"})

5.3 变量作用域

  • Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
  • 函数为本地命名空间,执行结束就会销毁引用计数器为0的变量。

1.全局变量和局部变量

定义

  • 定义在函数内部的变量拥有局部作用域,该变量称为局部变量。只能在其被声明的函数内部访问。
  • 定义在函数外部的变量拥有全局作用域,该变量称为全局变量。可以在整个程序范围内访问。

变量访问和修改

  • 函数内调用变量时,优先当前作用域找,在找上一级。
  • 内部作用域变量可以用return传递到外部作用域。
  • 在局部作用域对全局变量只能进行读取和修改内部元素(可变类型)
  • 当内部作用域想修改外部作用域的变量时,要用globalnonlocal关键字。
    • global 变量 能修改全局变量。
    • nonlocal 变量 先修改上一层变量。
name = 'root'
def outer():
    #  global把outer函数内 name设置为全局变量。
    global name 
    name = 'bo'
    def func():
        name = "simba"
        def inner():
            # nonlocal 调用上一层变量赋值
            nonlocal name
            name = 123
        def inner2():
            name = 123
        inner2()
        # inner2执行结束,会销毁了函数内name变量
        print(name)    # simba
        inner()     
        print(name)   # 123 
    func() 
    # func执行结束,会销毁了函数内name变量        
    print(name)        # bo

outer()            
print(name)            # bo

2. 函数嵌套

  • 函数嵌套时,传参的本质是传递的内存地址。
  • 当对象指向可变类型时,数据会改变。
 # 默认参数会在内部指向一个内存地址。
def func(a1, a2=[1, 2]):
    a2.append(a1)
    return a2
v1 = func(10)
print(v1)               # [1, 2, 10]

v2 = func(20) 
print(v2)               # [1, 2, 10, 20]

v3 = func(30, [11, 22])
print(v3)               # [11, 22, 30]

v4 = func(40)
print(v4)               # [1, 2, 10, 20, 40]
#  重新打印时的深坑   
print(v1) #   v1 = a2   # [1, 2, 10, 20, 40]
print(v2) #   V2 = a2     本质上与V1指向了同一个内存地址
print(v3)               # [11, 22, 30]
print(v4)               # [1, 2, 10, 20, 40]

3. 深浅拷贝

  • 可变类型不想一致变化,需要用 copy.deepcopy(),深拷贝来拷贝可变类型或含有可变类型的元组。
import copy

v1 = ("时间", "root", [11, [44, 55], (11, 22), (11, [], 22), 33])
v2 = copy.deepcopy(v1)

# 不可变类型 ,不拷贝
print(id(v1[0]))  # 2092678016720
print(id(v2[0]))  # 2092678016720

print(id(v1[2][2]))  # 2092688076224
print(id(v2[2][2]))  # 2092688076224

# 可变类型,或者含有可变类型的元组,会拷贝。

print(id(v1))  # 2092674968512
print(id(v2))  # 2092674831360

print(id(v1[2]))  # 140352552779008
print(id(v2[2]))  # 140352552920448

print(id(v1[2][3]))  # 140675479841152
print(id(v2[2][3]))  # 140675480454784

4. 闭包

  • 是一种特殊的内嵌函数。能够封装数据,在需要的时候提取。
  • 如果在一个内部函数里对外层非全局作用域的变量进行引用,那么内部函数就被认为是闭包。
  • 通过闭包可以访问外层非全局作用域的变量,这个作用域称为 闭包作用域
  • 闭包的返回值通常是函数。
def funX(x):
    def funY(y):
        return x * y
    return funY

i = funX(8)
print(type(i))  # <class 'function'>
print(i(5))  # 40
def func(name):
    list = []
    def inner():
        list.append(name)
        return list
    return inner

v1 = func('bobo')
v2 = func('simba')   # 单独生成一个内存区域,不会替代V1得到的值。
print(v1())  
print(v2())  
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

【例子】 如果要修改闭包作用域中的变量则需要 nonlocal 关键字

def outer():
    num = 10
    def inner():
        nonlocal num  # nonlocal关键字声明
        num = 100
        print(num)
    inner()
    print(num)
outer()
# 100
# 100

5. 递归函数

  • 如果一个函数在内部调用自身本身,这个函数就是递归函数。
  • Python默认递归层数为 100 , 设置递归的层数,
import sys
sys.setrecursionlimit(1000)

遍历字典的值中的子字典

data = {
    "time": "2016-08-05T13:13:05",
    "some_id": "ID1234",
    "grp1": {"fld1": 1, "fld2": 2, },
    "xxx2": {"fld3": 0, "fld4": 0.4, },
    "fld20":{"fld5": 10,"fld11": 12},
    "fld6": 11,
    "fld7": 7,
    "fld46": 8
}


def select(data,fields):
    result = {}   # 字典为可变序列,需要每次重新定义为空,清空上一次运行select1函数的结果
    field_lst = fields.split('|')
    def select1(data,field_lst):        
        for key in data:
            if key in field_lst:
                result[key] = data[key]
            if type(data[key]) == dict:
                select1(data[key], field_lst)
        return result
    return select1(data,field_lst)   

fields = 'fld2|fld7|fld29'
A = select(data, fields)
print(A)  # {'fld2': 2, 'fld7': 7}

fields = 'fld2|fld20|fld11'
B = select(data, fields)   
print(B)   # {'fld2': 2, 'fld20': {'fld5': 10, 'fld11': 12}, 'fld11': 12}

【例子】斐波那契数列 f(n)=f(n-1)+f(n-2), f(0)=0 f(1)=1 用递归效率低

# 利用循环
i = 0
j = 1
lst = list([i, j])
for k in range(2, 11):
    k = i + j
    lst.append(k)
    i = j
    j = k
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

# 利用递归
def recur_fibo(n):
    if n <= 1:
        return n
    return recur_fibo(n - 1) + recur_fibo(n - 2)


lst = list()
for k in range(11):
    lst.append(recur_fibo(k))
print(lst)  
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

5.4 lambda表达式

1. 匿名函数定义

lambda argument_list: expression

  • argument_list - 函数参数,它们可以是位置参数、默认参数、关键字参数,和正规函数里的参数类型一样。
  • expression 中没有 return 语句,因为 lambda 不需要它来返回,表达式本身结果就是返回值。
  • 匿名函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
foo = lambda a1,a2: a1 + a2 + 100
foo(12)

data = [lambda:10 for i in range(3)]
data[2]() # 10

func = lambda *args: sum(args)
print(func(1, 2, 3, 4, 5))  # 15

案例: 不同字母的数量排序

strings = ['foo', 'card', 'bar', 'aaaa', 'abab']
strings.sort(key=lambda x: len(set(list(x)))) # ['aaaa', 'foo', 'abab', 'bar', 'card']

2. 匿名函数的应用

匿名函数 常常应用于函数式编程的高阶函数,主要有两种形式:

  • 参数是函数 (filter, map)
  • 返回值是函数 (closure)

如,在 filtermap函数中的应用:

  • filter(function, iterable) 过滤序列,过滤掉不符合条件的元素,返回一个迭代器对象,如果要转换为列表,可以使用 list() 来转换。

【例子】

odd = lambda x: x % 2 == 1
templist = filter(odd, [1, 2, 3, 4, 5, 6, 7, 8, 9])
print(list(templist))  # [1, 3, 5, 7, 9]
  • map(function, *iterables) 根据提供的函数对指定序列做映射。

【例子】

m1 = map(lambda x: x ** 2, [1, 2, 3, 4, 5])
print(list(m1))  
# [1, 4, 9, 16, 25]

m2 = map(lambda x, y: x + y, [1, 3, 5, 7, 9], [2, 4, 6, 8, 10])
print(list(m2))  
# [3, 7, 11, 15, 19]

自己定义高阶函数。

def apply_to_list(fun, some_list):
    return fun(some_list)

lst = [1, 2, 3, 4, 5]
print(apply_to_list(lambda x: sum(x) / len(x), lst))
# 3.0

3. 柯里化

柯里化:将一个接受多个参数的函数转换为只接受一个参数的函数

def add_numbers(x, y):
  return x + y

add_five = lambda y: add_numbers(5, y)

也可以使用自带的标准库functools.partial方法。

from functools import partial

# 定义一个接受两个参数的函数 add
def add_numbers(x, y):
    return x + y

# 使用 partial 函数创建一个只接受一个参数的函数 sub
sub = partial(add_numbers, 10)

# 调用 sub 函数并传入参数 5,结果为 15
print(sub(5)) # 输出 15

5.5 生成器

  • 使用了 yield 的函数被称为生成器,生成器是一个返回迭代器的函数,只能用于迭代操作。
  • 在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。也可以用from连用。
  • 生成器可以用来处理大量的数据,用一个取一个,而不是一次性生成所有结果。
def func():    
    text2 =  yield 1    
    print(f'暂停了,V2才开始执行,text2为{text2}')

    text3 =  yield 'v2'    
    print(f'send值传递到上一个yield,也就是text3={text3}')
    yield  'v3  第三次执行'    
    print('v4,没有返回值,默认返回None')    
    yield  
    print("StopIteration")    
data = func()                
v1 = next(data)  
# 不在继续执行,返回生成器对象。记录在函数中的执行位置
print(v1)            # 1
# 下次执行next时,会从上一次的位置基础上再继续向下执行。
v2 = next(data)      # 打印 :暂停了,V2才开始执行,text2为None
print(v2)            # v2
v3 = data.send("v3")  
# send值传递到上一个yield,也就是text3=v3
print(v3)            # v3  第三次执行
v4 = next(data)   
print(v4)           # 打印   v4,没有返回值,默认返回None
                    #  None
v5 =next(data)      # StopIteration 
# 结束或中途遇到return,程序爆:StopIteration 错误
# from连用
def foo():
    yield 2
    yield 3


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


for item in func():
    print(item)  # 打印1,2,3,4

【例子】生成n以前的斐波那契数列数列

def libs(n):
    a = 0
    b = 1
    while True:
        yield a
        a, b = b, a + b
        if a > n:
            return
fib = libs(100)
print(next(fib))
print(next(fib))
print(next(fib))
list(fib)
# 0
# 1
# 1
# [2, 3, 5, 8, 13, 21, 34, 55, 89]
    

【例子】生成30个随机数验证码,用一个取一个

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(30)
n1 = next(data_list)
print(n1)  # 用完会报错

n = 30
data_list = (random.randint(1000, 9999) for i in range(n))
n1 = next(data_list)
print(n1)
  • 用元组推导式简化生成器函数
def _make_gen():
    for x in range(100):
        yield x ** 2
gen = _make_gen()

gen = (x ** 2 for x in range(100))  

元组生成器避免作用域的影响

num1 = [lambda x: i * x for i in range(4)]
[m(2) for m in num1]  # [6, 6, 6, 6]

num2 = (lambda x: i * x for i in range(4))
[m(2) for m in num2] # [0, 2, 4, 6]


sum(x ** 2 for x in range(100)) # 328350
dict((i, i **2) for i in range(3,5)) # { 3: 9, 4: 16}

注:内容主体来自阿里天池AI训练营,增加的部分来自于看其他的书籍和报名的付费课程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值