函数-入门

Posted chao-sir

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了函数-入门相关的知识,希望对你有一定的参考价值。

函数介绍

函数的作用

解决以下问题:

  • 代码的组织结构不清晰,可读性差
  • 遇到重复的功能只能重复编写实现代码,代码冗余
  • 功能需要扩展时,需要找出所有实现该功能的地方修改之,无法统一管理且维护难度极大

函数的定义

函数的意义就是一个功能模块,用来使用,下次再用的时候只需要拿来就可以使用,减少重复的代码编写的过程,但是用之前需要先造好

函数的分类

内置函数

这种函数是Python自带的,在所有情况下都可以随时调用

自定义函数

自己定制的特殊函数,只有在自己有需要的时候,但是内置函数无法解决问题的时候来进行自定制

定义函数

如何自定义函数

# 语法

def 函数名称(参数1,参数2,参数3,....):
    """注释"""
    执行语句
    return 返回值
# 函数名要能体现具体的功能    


# 例子

def number_sum(a, b):
    c = a + b
    return c
print(number_sum(1, 3))
# 4

函数的使用规则:先定义,再调用

未定义就调用(报错NameError)

# 测试一
print(number_sum(1, 3))
def number_sum(a, b):
    c = a + b
    return c
# NameError: name ‘number_sum‘ is not defined


# 测试二
def a():
    print("from a")
def b():
    print("from b")
    a()
b()

# from b
# from a



# 测试三
def a():
    print("from a")
    b()

def b():
    print("from b")
a()

# 此时会运行吗?

# from a
# from b

‘‘‘
总结:
1、原则是先定义后调用
2、但是要注意调用的位置,区分定义阶段和调用的阶段(何时何地)
‘‘‘


def a():
    print("from a")
    b()
def b():
    print("from b")
#以上是定义阶段

a()
#调用阶段
#在调用阶段时上述函数已经被定义所以不会报错

函数在定义阶段干了什么?

只检测语法,不执行代码
也就说,语法错误在函数定义阶段就会检测出来,而代码的逻辑错误只有在执行时才会知道

定义函数的三种形式

没有参数

# 无参:定义时无参,意味着调用时也无需传入参数

def out():
    print(‘输出‘)
out()
# 输出

有参数

# 有参:定义时有参,意味着调用时则必须传入参数

def out(who):
    print("输出%s" % (who))

out(‘justin‘)
# 输出justin

空函数:设计代码结构

def auth(user, password):
    ‘‘‘
    :param user: 用户名
    :param password: 密码
    :return: 认证结果
    ‘‘‘
    pass
....

#程序的设计立体可见

调用函数以及返回值

调用函数

函数的调用:函数名加括号

  • 先找到名字
  • 根据名字调用代码

函数返回值

  • 无return->None
  • return 1个值->返回1个值
  • return 逗号,分隔多个值->元组
# 什么时候该有返回值?
‘‘‘
调用函数,经过一系列的操作,最后要拿到一个明确的结果,则必须要有返回值
通常有参函数需要有返回值,输入参数,经过计算,得到一个最终的结果
‘‘‘
    
# 什么时候不需要有返回值?
‘‘‘
调用函数,仅仅只是执行一系列的操作,最后不需要得到什么结果,则无需有返回值
通常无参函数不需要有返回值
‘‘‘

调用函数的三种形式

  • 语句形式:函数名()
  • 表达式形式:3*len(‘hello‘)
  • 当中另外一个函数的参数:range(len(‘hello‘))

函数的参数

形参与实参

形参与实参是什么?

    形参(形式参数):指的是在定义函数时,括号内定义的参数,形参其实就变量名
    实参(实际参数):指的是在调用函数时,括号内传入的值,实参其实就变量的值

    #x,y是形参
    def func(x,y): #x=10,y=11
        print(x)
        print(y)

    #10,11是实参
    func(10,11)

注意

  • 实参值(变量的值)与形参(变量名)的绑定关系只在函数调用时才会生效/绑定
  • 实参值(变量的值)与形参(变量名)的绑定关系在函数调用结束后就立刻解除绑定

位置参数

位置即顺序,位置参参数指的就是按照从左到右的顺序依次定义的参数

1、在定义函数时,按照位置定义的形参,称为位置形参

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

#注意:
#位置形参的特性是:在调用函数时必须为其传值,而且多一个不行,少一个也不行

# foo(1,2) 
#TypeError: foo() missing 1 required positional argument: ‘z‘
# foo(1,2,3,4) 
#TypeError: foo() takes 3 positional arguments but 4 were given

2、在调用函数时,按照位置定义的实参,称为位置实参

#注意:位置实参会与形参一一对应
foo(1,3,2)

关键字参数

在调用函数时,按照key=value的形式定义的实参,称为关键字参数

def foo(x,y,z): #x=1,y=2,z=3
    print(x,y,z)

注意:

  1. 相当于指名道姓地为形参传值,意味着即便是不按照顺序定义,仍然能为指定的参数传值
foo(2,1,3)
foo(y=2,x=1,z=3)
foo(z=2,aaaa=1)
  1. 在调用函数时,位置实参与关键字实参可以混合使用,但必须
  • 必须遵循形参的规则
# foo(1,z=3,y=2)
# foo(1,z=3)
  • 不能为同一个形参重复传值
# foo(1,x=1,y=3,z=2)
  • 位置实参必须放到关键字实参的前面
# foo(y=3,z=2,1)

默认参数

在定义阶段,已经为某个形参赋值,那么该形参就称为默认参数

注意:

  1. 定义阶段已经有值,意味着调用阶段可以不传值
# def register(name,age,sex=‘male‘):
#     print(name,age,sex)

# register(‘justin‘,18,)
# register(‘toy‘,73,‘female‘)
# register(‘lxx‘,84,)
# register(‘xxx‘,84)
# register(‘yy‘,84)
  1. 位置形参必须在默认参数的前面
# def func(y=1,x): #错误
#     pass
  1. 默认参数的值只在定义阶段赋值一次,也就是说默认参数的值再定义阶段就固定死了
# m=10
# def foo(x,y=m):
#     print(x,y)
#
# m=‘aaa‘
# foo(1)
# foo(1,11)
  1. 记住:默认参数的值应该设置为不可变类型
def register(name,hobby,l=[]): # name=‘justin‘,hobby=‘play‘
    l.append(hobby) # l=[‘play‘]
    print(name,l) # justin [‘play‘]

register(‘justin‘,‘play‘)  # justin [‘play‘]
register(‘bingo‘,‘read‘) # bingo [‘read‘]
register(‘toy‘,‘music‘) # toy [‘music‘]


register(‘justin‘,‘play‘,[])  # justin [‘play‘]
register(‘bingo‘,‘read‘,[]) # bingo [‘read‘]
register(‘toy‘,‘music‘,[]) # toy [‘music‘]


def register(name,hobby,l=None):
    if l is None:
        l=[]
    l.append(hobby) # l=[‘play‘]
    print(name,l) # lxx [‘play‘]

register(‘lxx‘,‘play‘)  # lxx [‘play‘]
register(‘justin‘,‘read‘) # justin [‘read‘]
register(‘new‘,‘music‘) # new [‘music‘]

应用:

  1. 对于经常需要变化的值,需要将对应的形参定义成位置形参
  2. 对于大多数情况值都一样的情况,需要将对应的形参定义成默认形参

可变长参数(*args,**kwargs)

可变长指的是实参值的个数不固定
而实参有按位置和按关键字两种形式定义,针对这两种形式的可变长,形参对应有两种解决方案来完整地存放它们,分别是*args,**kwargs

1、*args(多传入的变成元组)

  • 情况一
def foo(x, y, *args):
    # *args会把多传入的参数变成一个元组的形式
    print(x, y)
    print(args)
foo(1, 2, 3, 4, 5)

# 输出

# 1 2
# (3,4,5)
  • 情况二
def foo(x, y, *args):
    print(x, y, args)


foo(‘qiaofu‘, 20)


# 输出
# qiaofu 20 () #后面这个()就是args,只是因为没有传值多以为空
  • 情况三
# 相当于解开*的传值方式
def foo(x, y, *args):
    print(x, y)
    print(args)


foo(1, 2, *[3, 4, 5])


# 1 2
# (3,4,5)

# 或者

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


foo(*[1, 2, 3])

# 1 2 3

2、**kwargs(多余的关键字参数当做字典)

  • 情况一
def foo(x, y, **kwargs):
    # **kwargs会把多传入的参数变成一个dict形式
    print(x, y)
    print(kwargs)


foo(1, y=2, a=1, b=2, c=3)

# 结果
# 1 2
# {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}
  • 情况二
def foo(x, y, **kwargs):
    print(x, y)
    print(kwargs)


foo(1, y=2, **{‘a‘: 1, ‘b‘: 2, ‘c‘: 3})  # 解开**的情况

# 结果
# 1 2
# {‘a‘: 1, ‘b‘: 2, ‘c‘: 3}
  • 情况三
def foo(x, y, *args, **kwargs):
    print(x, y, args, kwargs)


foo(1, 2, 3, 4,name=‘qiaofu‘,age = 20)

# 结果

# 1 2 (3,4) {‘name‘:‘qiaofu‘,‘age‘:20}  #如果不传入键值类型参数那么会输出空的{}
  • 情况四
def foo(x, y, z):
    print(x, y, z)

foo(**{‘z‘: 1, ‘x‘: 2, ‘y‘: 3})

# 结果
# 2 3 1

3、混合使用*args+**kwargs

def foo(x, y):
    print(x, y)


def wrapper(*args, **kwargs):
    print(‘====>‘)
    foo(*args, **kwargs)
    
# 相当于原封不动的传进来

命名关键字参数

*后定义的参数,必须被传值(有默认值的除外),且必须按照关键字实参的形式传递可以保证,传入的参数中一定包含某些关键字

def foo(x, y, *args, a=1, b, **kwargs):
    print(x, y)
    print(args)
    print(a)
    print(b)
    print(kwargs)


foo(1, 2, 3, 4, 5, b=3, c=4, d=5)
# 结果:
# 1
# 2
# (3, 4, 5)
# 1
# 3
# {‘c‘: 4, ‘d‘: 5}

函数练习一

题目

# 1、写函数,用户传入修改的文件名,与要修改的内容,执行函数,完成批了修改操作
# 
# 2、写函数,计算传入字符串中【数字】、【字母】、【空格] 以及 【其他】的个数
# 
# 3、写函数,判断用户传入的对象(字符串、列表、元组)长度是否大于5。
# 
# 4、写函数,检查传入列表的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
# 
# 5、写函数,检查获取传入列表或元组对象的所有奇数位索引对应的元素,并将其作为新列表返回给调用者。
# 
# 6、写函数,检查字典的每一个value的长度,如果大于2,那么仅保留前两个长度的内容,并将新内容返回给调用者。
# 
# dic = {"k1": "v1v1", "k2": [11,22,33,44]}
# 
# PS:字典中的value只能是字符串或列表

答案

# 题目一
def modify_file(filename, old, new):
    import os
    with open(filename, ‘r‘, encoding=‘utf-8‘) as read_f,             open(‘.bak.swap‘, ‘w‘, encoding=‘utf-8‘) as write_f:
        for line in read_f:
            if old in line:
                line = line.replace(old, new)
            write_f.write(line)
    os.remove(filename)
    os.rename(‘.bak.swap‘, filename)


modify_file(‘/Users/jieli/PycharmProjects/爬虫/a.txt‘, ‘alex‘, ‘SB‘)


# 题目二
def check_str(msg):
    res = {
        ‘num‘: 0,
        ‘string‘: 0,
        ‘space‘: 0,
        ‘other‘: 0,
    }
    for s in msg:
        if s.isdigit():
            res[‘num‘] += 1
        elif s.isalpha():
            res[‘string‘] += 1
        elif s.isspace():
            res[‘space‘] += 1
        else:
            res[‘other‘] += 1
    return res


res = check_str(‘hello name:aSB passowrd:alex3714‘)
print(res)


# 题目三

def check_len(obj):
      if len(obj) > 5:
            print(‘ture‘)

else:
print(‘false‘)
obj = input()
check_len(obj)


# 题目四
def func1(seq):
    if len(seq) > 2:
        seq = seq[0:2]
    return seq


print(func1([1, 2, 3, 4]))


# 题目五
def func2(seq):
    return seq[::2]


print(func2([1, 2, 3, 4, 5, 6, 7]))


# 题目六
def func3(dic):
    d = {}
    for k, v in dic.items():
        if len(v) > 2:
            d[k] = v[0:2]
    return d


print(func3({‘k1‘: ‘abcdef‘, ‘k2‘: [1, 2, 3, 4], ‘k3‘: (‘a‘, ‘b‘, ‘c‘)}))

函数对象

函数可以当作数据传递

  1. 可以被引用
  2. 可以当作参数传递
  3. 返回值可以是函数
  4. 可以当作容器类型的元素

优雅的取代多分支的if

def foo():
    print(‘foo‘)

def bar():
    print(‘bar‘)

dic={
    ‘foo‘:foo,  #如果值是函数名不加引号,否则需要加引号表示字符串
    ‘bar‘:bar,
}
while True:
    choice=input(‘>>: ‘).strip()
    if choice in dic:
        dic[choice]()

嵌套函数

函数的嵌套调用

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

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

函数的嵌套定义

def f1():
    def f2():
        def f3():
            print(‘from f3‘)
        f3()
    f2()

f1()
f3() #报错,为何?名称空间与作用域

名称空间和作用域

什么是名称空间

名称空间:存放名字的地方,三种名称空间,(之前遗留的问题x=1,1存放于内存中,那名字x存放在哪里呢?名称空间正是存放名字x与1绑定关系的地方)

名称空间的加载顺序

# cmd中运行
python test.py
  1. python解释器先启动,因而首先加载的是:内置名称空间
  2. 执行test.py文件,然后以文件为基础,加载全局名称空间
  3. 在执行文件的过程中如果调用函数,则临时产生局部名称空间

名字的查找顺序

局部名称空间--->全局名称空间--->内置名称空间
需要注意的是:在全局无法查看局部的,在局部可以查看全局的,如下示例

# max=1
def f1():
    # max=2
    def f2():
        # max=3
        print(max)
    f2()
f1()
print(max) 

作用域

1、作用域即范围

  • 全局范围(内置名称空间与全局名称空间属于该范围):全局存活,全局有效
  • 局部范围(局部名称空间属于该范围):临时存活,局部有效

2、作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关,如下

x=1
def f1():
    def f2():
        print(x)
    return f2
x=100
def f3(func):
    x=2
    func()
x=10000
f3(f1())

3、查看作用域:globals(),locals()

# LEGB 代表名字查找顺序: 
#       locals -> enclosing function -> globals -> __builtins__

# locals 是函数内的名字空间,包括局部变量和形参

# enclosing 外部嵌套函数的名字空间(闭包中常见)

# globals 全局变量,函数定义所在模块的名字空间

# builtins 内置模块的名字空间

global与nonlocal关键字

global适用于函数内部修改全局变量的值
nonlocal适用于嵌套函数中内部函数修改外部变量的值
如果没有使用以上关键字,对全局变量或者外部变量进行修改,python会默认将全局变量隐藏起来

# 例1:

def outside():
    var = 5
    def inside():
        var = 3
        print(var)
    inside()
outside()

# 3


# 例2:

def outside():
    var = 5
    def inside():
        # inside函数改变了var所以python将var隐藏了起来,这里的print找不到var因而报错。
        print(var)
        var = 3
    inside()
outside()

# 例1不会显示报错,但是例2会
# UnboundLocalError: local variable ‘var‘ referenced before assignment

高阶函数

变量可以指向函数,函数的参数能接受变量,那么一个函数就可以接受另一个函数作为参数,这种函数就称为高阶函数

ef f(something):
    return something

def add(x,y,z):
    return f(x)+f(y)

res = add(3, -6,abs)
print(res)

# -3

‘‘‘
# 只需要满足以下任意一个条件都可以称这个函数为高阶函数
# 1.接受一个或多个函数作为输出
# 2.return返回另一个函数
‘‘‘

递归

递归调用的定义

递归调用是函数嵌套调用的一种特殊形式,函数在调用时,直接或间接调用了自身,就是递归调用

直接调用本身

def f1():
    print(‘from f1‘)
    f1()
f1()

间接调用本身

def f1():
    print(‘from f1‘)
    f2()

def f2():
    print(‘from f2‘)
    f1()
f1()

  1. 调用函数会产生局部的名称空间,占用内存,因为上述这种调用会无需调用本身,python解释器的内存管理机制为了防止其无限制占用内存,对函数的递归调用做了最大的层级限制

  2. 可以修改递归最大深度

import sys
sys.getrecursionlimit()
sys.setrecursionlimit(2000)

def f1(n):
    print(‘from f1‘,n)
    f1(n+1)
f1(1)

‘‘‘
虽然可以设置,但是因为不是尾递归,仍然要保存栈,内存大小一定,不可能
无限递归,而且无限制地递归调用本身是毫无意义的,递归应该分为两个明确
的阶段,回溯与递推
‘‘‘

递推、回溯

1、递归调用应该包含两个明确的阶段:回溯,递推

  1. 回溯:

从外向里一层一层递归调用下去,回溯阶段必须要有一个明确地结束条件,每进入下一次递归时,问题的规模都应该有所减少(否则,单纯地重复调用自身是毫无意义的)

  1. 递推:

从里向外一层一层结束递归

2、示例

# salary(5)=salary(4)+300
# salary(4)=salary(3)+300
# salary(3)=salary(2)+300
# salary(2)=salary(1)+300
# salary(1)=100
#
# salary(n)=salary(n-1)+300     n>1
# salary(1) =100                n=1

def salary(n):
    if n == 1:
        return 100
    return salary(n-1)+300

print(salary(5)) 

递归效率低(无尾递归优化)

python中的递归:

python中的递归效率低,需要在进入下一次递归时保留当前的状态,
在其他语言中可以有解决方法:尾递归优化,即在函数的最后一步(而非最后一行)调用自己,但是python又没有尾递归,且对递归层级做了限制

总结递归的使用

  1. 必须有一个明确的结束条件
  2. 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
  3. 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)

二分法(递归应用)

想从一个按照从小到大排列的数字列表中找到指定的数字,遍历的效率太低,用二分法(算法的一种,算法是解决问题的方法)可以极大低缩小问题规模

类似in的效果

l=[1,2,10,30,33,99,101,200,301,311,402,403,500,900,1000] #从小到大排列的数字列表

def search(n,l):
    print(l)
    if len(l) == 0:
        print(‘not exists‘)
        return
    mid_index=len(l) // 2
    if n > l[mid_index]:
        #in the right
        l=l[mid_index+1:]
        search(n,l)
    elif n < l[mid_index]:
        #in the left
        l=l[:mid_index]
        search(n,l)
    else:
        print(‘find it‘)


search(3,l)

类似l.index(30)的效果

l=[1,2,10,30,33,99,101,200,301,402]

def search(num,l,start=0,stop=len(l)-1):
    if start <= stop:
        mid=start+(stop-start)//2
        print(‘start:[%s] stop:[%s] mid:[%s] mid_val:[%s]‘ %(start,stop,mid,l[mid]))
        if num > l[mid]:
            start=mid+1
        elif num < l[mid]:
            stop=mid-1
        else:
            print(‘find it‘,mid)
            return
        search(num,l,start,stop)
    else: #如果stop > start则意味着列表实际上已经全部切完,即切为空
        print(‘not exists‘)
        return

search(301,l)

以上是关于函数-入门的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——声明函数

VSCode自定义代码片段8——声明函数

Atom编辑器入门到精通 Atom使用进阶

Cg入门20:Fragment shader - 片段级模型动态变色(实现汽车动态换漆)

Cg入门19:Fragment shader - 片段级模型动态变色

使用从循环内的代码片段中提取的函数避免代码冗余/计算开销