[Python-4]Python函数和内存分析

目录

一、Python函数的分类

1. 内置函数

2. 标准库函数

3. 第三方库函数

4. 用户自定义函数

二、函数的定义和调用

三、文档字符串(函数的注释) 

四、返回值 

五、函数的内存分析

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

 1. 全局变量

2. 局部变量 

七、参数的传递

1.  传递可变对象的引用 

2. 传递不可变对象的引用

3. 传递不可变对象包含的子对象是可变的情况 

八、浅拷贝和深拷贝

 九、参数的几种类型

 1. 位置参数

2. 默认值参数

3. 命名参数

4. 可变参数

5. 强制命名参数

十、lambda表达式和匿名函数

十一、eval()函数

十二、递归函数 

十三、嵌套函数(内部函数)

十四、nonlocal和global关键字

十五、LEGB规则


 

一、Python函数的分类

1. 内置函数

        我们前面使用的str()list()len()等这些都是内置函数,我们可以拿来直接使用。

2. 标准库函数

        我们可以通过import语句导入库,然后使用其中定义的函数

3. 第三方库函数

        Python社区也提供了很多库。下载安装这些库后,也是通过import语句导入,然后可以使用这些第三方库的函数

4. 用户自定义函数

用户自己定义的函数,显然也是开发中适应用户自身需求定义的函数

二、函数的定义和调用

         Python中,定义函数的语法如下:

def  函数名 ([参数列表]) :
    '''文档字符串'''
    函数体/若干语句

         简单定义一个函数:

def add(a,b,c):
    '''完成三个数的加法,并返回他们的和'''
    sum = a+b+c
    print("{0}、{1}、{2}三个数的和是:{3}".format(a,b,c,sum))
    return sum
​
add(10,20,30)
add(30,40,50)
  • 我们使用def来定义函数,然后就是一个空格和函数名称;

    • Python执行def时,会创建一个函数对象,并绑定到函数名变量上。
  • 参数列表

    • 圆括号内是形式参数列表,有多个参数则使用逗号隔开
    • 定义时的形式参数不需要声明类型,也不需要指定函数返回值类型
    • 调用时的实际参数必须与形参列表一一对应
  • return返回值

    • 如果函数体中包含return句,则结束函数执行并返回值;
    • 如果函数体中不包含return语句则返回None
  • 调用函数之前,必须要先定义函数,即先调用def创建函数对象

    • 内置函数对象会自动创建
    • 标准库和第三方库函数,通过import导入模块时,会执行模块中的def语句

三、文档字符串(函数的注释) 

定义一个函数,实现两个数的比较,并返回较大的值 

def  printMax(a,b):
    '''实现两个数的比较,并返回较大的值'''
    if a>b:
        print(a,'较大值')
        return a
    else:
        print(b,'较大值')
        return b
​
​
printMax(10,20)
printMax(30,5)

测试文档字符串的使用

def print_star(n):
    '''
    根据传入的n,打印多个星号
    :param n: 传入的数字
    :return:  n个星号拼接的字符串
    '''
    s = "*"*n
    print(s)
    return s

help(print_star)
print(print_star.__doc__)
  • 调用help(函数名) 可打印输出函数的文档字符串。
  • 也可以通过函数名.__doc__直接获取到函数的文档字符串,自己进行打印。

四、返回值 

  • 如果函数体中包含return语句,则结束函数执行并返回值
  • 如果函数体中不包含return语句,则返回None
  • 返回多个值,使用列表、元组、字典、集合将多个值“存起来”即可

定义一个打印n个星号的无返回值的函数 

def print_star(n):
    print("*"*n)
​
print_star(5)

定义一个返回两个数平均值的函数

def my_avg(a,b):
    return (a+b)/2
​
#如下是函数的调用
c = my_avg(20,30)
print(c)

返回一个列表

def printShape(n):
    s1 = "#"*n
    s2 = "$"*n
    return [s1,s2]
​
​
s = printShape(5)
print(s)

五、函数的内存分析

def print_star(n):                 
    print("*"*n)
​
print(print_star)
print(id(print_star))
​
c = print_star
​
c(3)
  • 显然,我们可以看出变量cprint_star都是指向了同一个函数对象。因此,执行c(3)和执行print_star(3)的效果是完全一致的。
  • Python中,圆括号意味着调用函数。在没有圆括号的情况下,Python会把函数当做普通对象。

    zhengshu = int
    zhengshu("234")

    将内置函数对象int()赋值给了变量zhengshu,这样zhengshuint都是指向了同一个内置函数对象。

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

 1. 全局变量

  • 函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。
  • 全局变量降低了函数的通用性和可读性。应尽量避免全局变量的使用
  • 要在函数内改变全局变量的值,使用global声明一下

2. 局部变量 

  • 函数体中(包含形式参数)声明的变量。
  • 局部变量的引用比全局变量快,优先考虑使用
  • 如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量

全局变量的作用域测试 

a = 100         #全局变量
def f1():
    global a    #如果要在函数内改变全局变量的值,增加global关键字声明
    print(a)    #打印全局变量a的值    
    a = 300      
    
f1()
f1()
print(a)
100
300
300

输出局部变量和全局变量

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__': None, '__package__': None, 
'__loader__': <class '_frozen_importlib.BuiltinImporter'>, 
'__spec__': None, '__annotations__': {}, 
'__builtins__': <module 'builtins' (built-in)>, 
'__file__': 'E:\\PythonExec\\if_test01.py', 'a': 100, 
'f1': <function f1 at 0x0000000002BB8620>}

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

import time
​
a = 1000
def test01():
    start = time.time()
    global a
    for i in range(100000000):
        a += 1
    end = time.time()
    print("耗时{0}".format((end-start)))
​
def test02():
    c = 1000
    start = time.time()
    for i in range(100000000):
        c += 1
    end = time.time()
    print("耗时{0}".format((end-start)))
​
test01()
test02()
print(globals())
#运行结果:

耗时5.278882026672363 
耗时3.6103720664978027

七、参数的传递

        函数的参数传递本质上就是:从实参到形参的赋值操作。Python中“一切皆对象”,所有的赋值操作都是“引用的赋值”。所以,Python中参数的传递都是“引用传递”,不是“值传递”。

具体操作时分为两类:

  • 对“可变对象”进行“写操作”,直接作用于原对象本身
  • 对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填充这块空间
  •  可变对象有:字典、列表、集合、自定义的对象等
  • 不可变对象有:数字、字符串、元组、function等

1.  传递可变对象的引用 

参数传递:传递可变对象的引用 

b = [10,20]
def f2(m):
    print("m:",id(m))       #b和m是同一个对象
    m.append(30)    #由于m是可变对象,不创建对象拷贝,直接修改这个对象
​
f2(b)
print("b:",id(b))
print(b)
执行结果:
m: 45765960
b: 45765960
[10, 20, 30]

2. 传递不可变对象的引用

        传递参数是不可变对象(例如:intfloat、字符串、元组、布尔值),实际传递的还是对象的引用。在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。         

参数传递:传递不可变对象的引用 

a = 100
def f1(n):
    print("n:",id(n))        #传递进来的是a对象的地址
    n = n+200            #由于a是不可变对象,因此创建新的对象n
    print("n:",id(n))    #n已经变成了新的对象
    print(n)
f1(a)
print("a:",id(a))
执行结果:
n: 1663816464
n: 46608592
300
a: 1663816464

3. 传递不可变对象包含的子对象是可变的情况 

#传递不可变对象时。不可变对象里面包含的子对象是可变的。则方法内修改了这个可变对象,源对象也发生了变化。

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: 41611632

m: 41611632

(10, 20, [888, 6])

m: 41611632

(10, 20, [888, 6])

八、浅拷贝和深拷贝

  • 浅拷贝:拷贝对象,但不拷贝子对象的内容,只是拷贝子对象的引用。
  • 深拷贝:拷贝对象,并且会连子对象的内存也全部(递归)拷贝一份,对子对象的修改不会影响源对象
    #测试浅拷贝和深拷贝
    import copy
    ​def testCopy():
        '''测试浅拷贝'''
        a = [10, 20, [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)
    ​
    def testDeepCopy():
        '''测试深拷贝'''
         a = [10, 20, [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)
    ​
    testCopy()
    print("*************")
    testDeepCopy()

    运行结果:

    a [10, 20, [5, 6]]

    b [10, 20, [5, 6]]

    浅拷贝......

    a [10, 20, [5, 6, 7]]

    b [10, 20, [5, 6, 7], 30]

    a [10, 20, [5, 6]]

    b [10, 20, [5, 6]]

    深拷贝......

    a [10, 20, [5, 6]]

    b [10, 20, [5, 6, 7], 30]

 九、参数的几种类

 1. 位置参数

        函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为:“位置参数”。

测试位置参数

def f1(a,b,c):
    print(a,b,c)
​
f1(2,3,4)
f1(2,3)     #报错,位置参数不匹配
执行结果:

2 3 4
Traceback (most recent call last):
  File "E:\PythonExec\if_test01.py", line 5, in <module>
    f1(2,3)
TypeError: f1() missing 1 required positional argument: 'c

2. 默认值参数

        我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。默认值参数放到位置参数后面。

测试默认值参数

def f1(a,b,c=10,d=20):   #默认值参数必须位于普通位置参数后面
    print(a,b,c,d)
​
f1(8,9)
f1(8,9,19)
f1(8,9,19,29)
执行结果:

8 9 10 20
8 9 19 20
8 9 19 29

3. 命名参数

        也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”

def f1(a,b,c):
    print(a,b,c)
​
f1(8,9,19)          #位置参数
f1(c=10,a=20,b=30)  #命名参数
执行结果:

8 9 19
20 30 10

4. 可变参数

可变参数指的是“可变数量的参数”。分两种情况:

  1. *param(一个星号),将多个参数收集到一个“元组”对象中。
  2. **param(两个星号),将多个参数收集到一个“字典”对象中。

测试可变参数处理(元组、字典两种方式) 

def f1(a,b,*c):
    print(a,b,c)
​
f1(8,9,19,20)
​
​
def f2(a,b,**c):
    print(a,b,c)
​
f2(8,9,name='gaoqi',age=18)
​
​
def  f3(a,b,*c,**d):
    print(a,b,c,d)
​
f3(8,9,20,30,name='gaoqi',age=18)
执行结果:

8 9 (19, 20)
8 9 {'name': 'gaoqi', 'age': 18}
8 9 (20, 30) {'name': 'gaoqi', 'age': 18}

5. 强制命名参数

        在带星号的“可变参数”后面增加新的参数,必须在调用的时候“强制命名参数”。

def f1(*a,b,c):
    print(a,b,c)
​
​
#f1(2,3,4)   #会报错。由于a是可变参数,将2,3,4全部收集。造成b和c没有赋值。
​
f1(2,b=3,c=4) 
执行结果:

(2,) 3 4

十、lambda表达式和匿名函数

    lambda表达式可以用来声明匿名函数lambda函数是一种简单的、在同一行中定义函数的方法。lambda函数实际生成了一个函数对象。

    lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。

lambda表达式的基本语法如下:

lambda  arg1,arg2,arg3... :  <表达式>

arg1 arg2 arg3为函数的参数。<表达式>相当于函数体。运算结果是:表达式的运算结果。 

lambda表达式使用 

f = lambda a,b,c:a+b+c
print(f)
print(f(2,3,4))
​
g = [lambda a:a*2,lambda b:b*3,lambda c:c*4]
print(g[0](6),g[1](7),g[2](8))
执行结果:

<function <lambda> at 0x0000000002BB8620>
9
12 21 32

十一、eval()函数

功能:将字符串str当成有效的表达式来求值并返回计算结果。

语法: eval(source[, globals[, locals]]) -> value

参数: 

  1. source一个Python表达式或函数compile()返回的代码对象
  2. globals可选。必须是dictionary
  3. locals可选。任意映射对象
#测试eval()函数
​
s = "print('abcde')"
eval(s)
​
a = 10
b = 20
c = eval("a+b")
print(c)
​
dict1 = dict(a=100,b=200)
​
d = eval("a+b",dict1)
print(d)

         eval函数会将字符串当做语句来执行,因此会被注入安全隐患。比如:字符串中含有删除文件的语句。那就麻烦大了。因此,使用时候,要慎重!!!

十二、递归函数 

  • 递归(recursion)是一种常见的算法思路,很多算法中都会用到。如:深度优先搜索(DFS:Depth First Search)等。

  • 递归的基本思想就是“自己调用自己”

        递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。每个递归函数必须包含两个部分: 

  1. 终止条件表示递归什么时候结束。一般用于返回值,不再调用自己。

  2. 递归步骤把第n步的值和第n-1步相关联。

递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数据时,谨慎使用。 

一个简单的递归程序: 

def my_recursion(n):
    print("start:" + str(n))
    if n == 1:
        print("recursion over!")
    else:
        my_recursion(n - 1)
​
    print("end:" + str(n))
​
my_recursion(3)

 运算结果:

start:3 

start:2 

start:1 

recursion over! 

end:1 

end:2 

end:3

 使用递归函数计算阶乘(factorial) 

def factorial(n):
    if n==1:
        return 1
    return n*factorial(n-1)
​
print(factorial(5))

十三、嵌套函数(内部函数)

嵌套函数:在函数内部定义的函数! 

def  outer():
    print('outer running...')
​
    def inner():
        print('inner running...')
​
    inner()
​
outer()
执行结果:

outer running...
inner running...

        上面程序中,inner()就是定义在outer()函数内部的函数。inner()的定义和调用都在outer()函数内部。

一般在什么情况下使用嵌套函数?

  • 封装 - 数据隐藏

    外部无法访问“嵌套函数”。

  • 贯彻 DRY(Don’t Repeat Yourself) 原则

  • 嵌套函数,可以让我们在函数内部避免重复代码。

  • 闭包(后面会讲解)

 使用嵌套函数避免重复代码

def printChineseName(name,familyName):
    print("{0} {1}".format(familyName,name))
​
def printEnglishName(name,familyName):
    print("{0} {1}".format(name, familyName))
def printName(isChinese,name,familyName):
    def inner_print(a,b):
        print("{0} {1}".format(a,b))
​
    if isChinese:
        inner_print(familyName,name)
    else:
        inner_print(name,familyName)
​
printName(True,"洋","杨")
printName(False,"George","Bush")

十四、nonlocal和global关键字

nonlocal 用来在内部函数中,声明外层的局部变量。

global 函数内声明全局变量,然后才使用全局变量

使用nonlocal声明外层局部变量 

#测试nonlocal、global关键字的用法
a = 100
​
def outer():
    b = 10
​
    def inner():
        nonlocal  b         #声明外部函数的局部变量
        print("inner b:",b)
        b = 20
​
        global a            #声明全局变量
        a = 1000
​
    inner()
    print("outer b:",b)
​
outer()
print("a:",a)

十五、LEGB规则

Python在查找“名称”时,是按照LEGB规则查找的: 

  •  Local 指的就是函数或者类的方法内部
  • Enclosed 指的是嵌套函数(一个函数包裹另一个函数,闭包)
  • Global 指的是模块中的全局变量
  • Built in 指的是Python为自己保留的特殊名称

        如果某个name映射在局部local命名空间中没有找到,接下来就会在闭包作用域enclosed进行搜索,如果闭包作用域也没有找到,Python就会到全局global命名空间中进行查找,最后会在内建built-in命名空间搜索 (如果一个名称在所有命名空间中都没有找到,就会产生一个NameError)         

#测试LEGB
​
s = "global"
def outer():
    s = "outer"
​
    def inner():
        s = "inner"
        print(s)
​
    inner()
​
outer()

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值