python基础 函数

文章目录

  • 函数的定义和调用
    • 形参和实参
    • 返回值
    • 函数也是对象,内存底层分析
  • 变量的作用域(全局变量和局部变量)
    • 如何打印全局变量和局部变量?
    • 局部变量和全局变量效率测试
  • 参数的传递
    • 传递可变对象
    • 传递不可变对象
    • 浅拷贝和深拷贝
    • 参数的几种类型
      • 位置参数
      • 默认值参数
      • 命名参数
      • 可变参数
    • lambda 表达式和匿名函数
    • eval()函数
    • 递归函数
      • 原理说明
      • 利用递归函数算阶乘
    • 嵌套函数
      • nonlocal 关键字
  • LEGB规则

函数是可重用的程序代码块,可以使用代码的复用,更能实现代码的一致性。
python的函数分为如下几类:

  1. 内置函数:直接就可以调用:list(),len()等
  2. 标准库函数:可以通过Import语句倒入库,然后使用其中定义的函数
  3. 第三方库函数:python舍去提供许多高质量的库
  4. 用户自定义函数:自己定义

函数的定义和调用

语法: def 函数名(参数列表):
‘’‘文档字符串’’’
函数体/若干语句
要点

  1. 使用def 定义函数,然后是空格+函数名称
  2. 参数列表:圆括号是参数列表,多个参数用逗号隔开;没有参数也必须保留空括号。实参数列表必须和形参列表一一对应。
  3. return 返回值:结束函数运行并返回值,如果没有,返回None值

形参和实参

def 后面跟着的那个叫形式参数,调用函数给具体的值就是实际参数。

def printMax(a,b):
    '用于比较两个数的大小'
    if a>b:
        print(a,'大')
    else:
        print(b,'小')
printMax(10,20)
help(printMax.__doc__)

###运行结果###
20 小
No Python documentation found for '用于比较两个数的大小'.
Use help() to get the interactive help utility.
Use help(str) for help on the str class.

其中,a和b都叫做形式参数,10,20叫做实际参数,help可以调用函数定义里面的文字部分,所以函数里面要多说明一下

返回值

return的两个作业:结束运行或者返回值

def test03(a,b):
    "测试返回值的基本用法"
    print('计算的{0}和{1}的和:{2}'.format(a,b,a+b))
    return a+b
test03(10,20)

def test04():
    print('dada')
    return
    print('hello')
test04()

###运行结果###
计算的1020的和:30
dada

看第二个部分,return用于结束,后面的部分不会再运行

函数也是对象,内存底层分析

函数作为一个对象,可以作为参数来传递,也可以作为对象的值来传递。

def test01():
    print('ads')
    
c = test01
c()
print(id(c))
print(id(test01))

c 和 test01的id是一样的。

变量的作用域(全局变量和局部变量)

变量分为全局变量和局部变量:
全局变量:作用域为定义的模块,从定义位置开始直到模块结束。
tips: 尽量避免全局变量的使用,全局变量一般作为常量使用,要改变全局变量应该用global声明一下。
局部变量:在函数体内部声明的变量,全局变量和局部变量有冲突的时候,隐藏全局变量,使用局部变量。

a = 3  # 全局变量:整个文件都可以使用

#局部变量
def test01(b):
    b = 4
    print(b * 10)


print(a)
#b是不存在的,只在函数内部存在的,在外部读取则不存在
print(b)
### 运行结果 ###
3
Traceback (most recent call last):
  File "D:/PycharmProjects/MyTest/Day_5/function.py", line 48, in <module>
    print(b)
NameError: name 'b' is not defined

运行机制:
python基础 函数_第1张图片
当要调动test01的时候,会出现一个栈帧(b),也是一个对象,但是调用完test01,栈帧就没有了

a = 3  # 全局变量


def test01():
    b = 4
    print(b * 10)
    a = 300
    print(a)


# 使用a的时候,隐藏了全局变量,使用了局部变量,若不想改变,应该加global a
test01()
# 仍然打印的是全局变量a
print(a)

####运行结果####
40
300
3

如何打印全局变量和局部变量?

a = 100


def f1(a, b, c):
    print(a, b, c)
    print(locals())  # 打印输出局部变量
    print("#" * 20)
    print(globals())  # 打印输出全局变量
f1(2,3,4)

####运行结果####
2 3 4
{'c': 4, 'b': 3, 'a': 2}
####################
{'__name__': '__main__', '__doc__': '\n@author: Di

局部变量和全局变量效率测试

所以在有全局变量的时候速度要慢的多,在有大量代码的时候,才考虑这种,否则会降低代码的可读性


# 使用全局变量 math.sqrt 和 不适用全局变量 math.sqrt的时间
import time
import math


def test01():
    start = time.time()
    for i in range(100000):
        math.sqrt(30)
    end = time.time()
    print('有全局变量耗时:{0}'.format((end - start)))


def test02():
    # 记得把函数赋给某一个变量的时候,不要加括号!
    b = math.sqrt
    start = time.time()
    for i in range(100000):
        b(30)
    end = time.time()
    print('没有全局变量耗时:{0}'.format((end - start)))


test01()
test02()


####运行结果####
有全局变量耗时:0.011970281600952148
没有全局变量耗时:0.008986711502075195

参数的传递

函数参数传递的本质上就是:从形参到实参的赋值操作。
可变对象:字典,列表,集合,自定义可变对象
不可变对象:数字,字符串,元组,function

传递可变对象

参数传递的实质:对于可变对象来说,不生成新的对象,直接在原来的值上进行修改。

b = [10, 20]
print(id(b))


def f2(m):
    print('m', id(m))
    m.append(30)


f2(b)
print(b)
###运行结果###
2482609933832
m 2482609933832
[10, 20, 30]

传递不可变对象

传递不可变对象:生成一个新的对象对其进行修改

# 传递不可变对象:生成一个新的对象对其进行修改
a = 100
print(id(a))


def f01(m):
    m *= 20
    print("m", m, id(m))


f01(a)
print(a, id(a))

运行结果

1347319968
m 2000 2012825890512
100 1347319968

浅拷贝和深拷贝

浅拷贝(copy):不拷贝子对象的内容,只是拷贝子对象的引用
深拷贝(deepcopy):会连子对象的内存全部拷贝一份,对子对象的修改不会影响源对象

浅拷贝

# 浅拷贝和深拷贝
import copy

a = [10, 20, [4, 5, 6]]
b = copy.copy(a)
print('a:', a)
print('b:', b)
b.append(30)
b[2].append(7)
print('浅拷贝.......')
print('a:', a)
print('b:', b)

###运行结果###
a: [10, 20, [4, 5, 6]]
b: [10, 20, [4, 5, 6]]
浅拷贝.......
a: [10, 20, [4, 5, 6, 7]]
b: [10, 20, [4, 5, 6, 7], 30]

运行机制讲解
python基础 函数_第2张图片
浅拷贝的话只拷贝“父系”:也就是蓝色部分,所以在b的尾巴后面增加了30,a就不会有。但是如果要增加b没有拷贝的部分,也就是“子系”,就相当于对a进行修改。

深拷贝

import copy

a = [10, 20, [4, 5, 6]]
b = copy.deepcopy(a)
print('a:', a)
print('b:', b)
b.append(30)
b[2].append(7)
print('深拷贝.......')
print('a:', a)
print('b:', b)

####运行结果####
a: [10, 20, [4, 5, 6]]
b: [10, 20, [4, 5, 6]]
深拷贝.......
a: [10, 20, [4, 5, 6]]
b: [10, 20, [4, 5, 6, 7], 30]

运行机制讲解
python基础 函数_第3张图片
对b进行的操作,b的元素全都有,所以就不对a进行修改了。

传递不可变对象时候,如果发生拷贝,是浅拷贝。也就是说一旦对下面的a里面的嵌套里的嵌套进行修改,那么a也会发生相应改变。
对于不可变对象来说,没有嵌套里面的嵌套,那么就相当于重新生成一个新的对象。

a = (10, 20, [5, 6])
print('a:', id(a))


def test01(m):
    print('m:', id(m))
    m[2][0] = 888
    print(m)
    print('m:', id(m))


test01(a)
print(a)

####运行结果####
a: 2180368644280
m: 2180368644280
(10, 20, [888, 6])
m: 2180368644280
(10, 20, [888, 6])

参数的几种类型

位置参数

通过位置顺序传递,形参和实参一一对应的关系。

默认值参数

我们可以为某些参数设置默认值,这样这些参数在传递的时候就是可选的。
!!默认参数必须位于普通参数后面!!

def f1(a, b, c=10, d=20):
    print(a, b, c, d)


f1(29, 34)
f1(80, 0, 102, 3)
####运行结果####
29 34 10 20
80 0 102 3

命名参数

def f1(a, b, c=10, d=20):
    print(a, b, c, d)


f1(c=10, d=30, a=1, b=3)
###运行结果###
1 3 10 30

可变参数

可变参数指的是数量可变。

  1. *param(一个星号),将多个参数收集到一个元组里面
  2. ** param(两个星号),将多个参数收集到一个 字典里面
def f1(a, b, *c):
    print(a, b, c)


f1(10, 20, 30, 40)


def f2(a, b, **c):
    print(a, b, c)


f2(8, 9, name='Di', age=18)
####运行结果####
10 20 (30, 40)
8 9 {'name': 'Di', 'age': 18}

lambda 表达式和匿名函数

  1. 语法: lambda arg1,arg2,arg3… : <表达式>
# 用lambda创建一个函数
f = lambda a, b, c: a + b + c


# 用普通方式创建一个函数
def test01(a, b, c):
    print("*****")
    return a + b + c


print(f(1, 2, 3))
# 用列表创建函数
g = [lambda a: a * 2, lambda b: b * 3, lambda c: c * 4]
print(g[0](6), g[1](5), g[2](4))

# 用列表创建函数
g1 = [test01, test01]
print(g1[0](3, 4, 5))
####运行结果####
6
12 15 16
*****
12

eval()函数

语法:eval(source[,globals[,locals]])
source : 一个python 表达式或函数compile()返回的代码对象
globals: 可选,必须是dictionary
locals : 可选。任意映射对象

# 测试eval()函数

# 执行代码:eval(''),eval里面加的是字符串
eval("print('afwfww')")  # 注意内部和外部的引号不能一样
a = 10
b = 20
print(eval('a+b'))

dic1 = dict(a=100, b=101)
d = eval('a+b', dic1)
print(d)

###运行结果###
afwfww
30
201

递归函数

原理说明

递归函数指的是自己调用自己的函数,在函数体内部直接或间接地调用自己。递归类似于大家中学数学学习过的"数学归纳法",每个递归函数必须包含两个部分:

  1. 终止条件:表示递归什么时候结束,一般用于返回值,不再调用自己
  2. 递归步骤:把第n步和第n-1步相关联。
# 测试递归函数的基本原理
def test01():
    print('test01')
    test01()


def test02():
    print('test02')


test01()
# 测试递归函数的基本原理
def test01(n):
    print('test01')
    if n == 0:
        print('over')
    else:
        test01(n - 1)
    print("******************")


test01(6)

运行结果

test01
test01
test01
test01
test01
test01
test01
over
******************
******************
******************
******************
******************
******************
******************

原理说明

利用递归函数算阶乘

def factorial(n):
    if n == 1:
        return 1
    else:
       return n * factorial(n - 1)


print(factorial(6))

嵌套函数

# 嵌套函数使用方法:打印名字
# 第一个参数传入任意非空就表示是“中国人”
def printName(isChinese, Name, FamilyName):
    def innerfunction(a, b):
        print('{0},{1}'.format(a, b))

    if isChinese:
        innerfunction(FamilyName, Name)
    else:
        innerfunction(Name, FamilyName)


printName('ad', 'Di', 'Huang')

```**运行结果**

```python
Huang,Di

nonlocal 关键字

外部函数有一个变量,内部函数使用外部的变量,就使用nonlocal,跟global有一点像。

def outer():
    b = 10

    def inner():
        nonlocal b
        print("inner:", b)
        b = 20

    inner()
    print("outer:",b)

outer()

运行结果

inner: 10
outer: 20

LEGB规则

按照下列规则进行查找的:Local→ Enclosed → Global → Built in
Local :代表内部函数
Enclosed:代表外部函数
Global : 代表全局变量
Built in:代表Python 为自己保留的特殊名称(python内部有这个变量)
如果都没有找到这些变量,那么就会报错:Name Error

# 测试LEGB
# str = 'global str'


def outer():
    # str = 'outer'

    def inner():
        # str = 'inner'
        print(str)

    inner()


outer()

运行结果

<class 'str'>

上述结果是最后在built in 里面找到了,也就是python本身就有这个变量

你可能感兴趣的:(python基础)