python夯实基础日记-函数详解

函数也是对象_内存分析

Python中,“一切都是对象”。实际上,执行def定义函数后,系统就创建了相应的函数对象。
在定义时就已经创建好了函数对象,调用时无需创建,只是反复调用
执行如下程序,然后进行解释:

#定义时创建了test01变量存在栈中,test01的id指向堆中的值对象
def test01():
    print('daxian')
#函数带括号表示调用了该函数,执行了函数里面的代码
test01()
c = test01 #将test01中的值拷贝给c;c的id也指向了堆中值对象
c()#函数是对象,可以作为参数传递,也可以作为返回值返回

print(id(test01))#test01和c的id相同
print(id(c))
print(type(c))#c也成为了funtion

在这里插入图片描述

71.变量的作用域(全局变量_局部变量)_栈帧内存分析

变量起作用的范围称为变量的作用域,不同作用城内同名变量之间互不影晌,变量分为:全局变量、局部变量。

全局变量:

1.在函数和定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束

2.全局变量降低了函数的通用性和可读性。尽量免全局变量的使用

3.全局量一般做常量使用。

4.函数内要改变全局变量的值,使global声明一下

局部变量:

1.在函数体中(包含形式数参数)声明的量

2.局部变量的引用比全局变量快,优先考虑使用

3.如果局变量和全局变量同名,则在函数内隐藏全局变量,只使用同可名的局部变量

【操作】全局变量的作用域测试

#测试全局变量和局部变量
a = 3 #全局变量
def test01():
    b = 4  #局部变量
    '''global a  想要使用全局a时需要global声明'''
    print(b*10)
    a = 300 #局部变量
    print(a)
    print(locals())#打印输出局部变量
    print(globals())#打印输出全局变量
test01() #当test01被调用会创建一个栈帧,栈帧里可以访问外面,外面不能访问栈帧里,当调用结束后栈帧销毁,就像胶卷
#print(b) 局部变量被使用会出错
print(a) #这个a是全局变量,和test()里的不是同一个a

在这里插入图片描述

72.局部变量和全局变量_效率测试

局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。
在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运行速度

#测试局部变量,全局变量的效率
import math
import time
def test01():
    start01 = time.time()
    for i in range(10000000):
        math.sqrt(30)
    end01 = time.time()
    print('耗时{}:'.format(end01-start01))
test01()

def test02():
    b = math.sqrt 
    ’‘’把函数对象赋给b,b就成为一个函数了
    math相当于一筐水果,sqrt是一个苹果,b相当于一个纸片如果在循环中使用math.sqrt(),就要每次去框里取,
    b = math.sqrt相当于把纸片当成了math.sqrt,循环中每次去使用纸片就可以了‘’‘
    # print(type(b)) →'''class 'builtin_function_or_method''''
    start02 = time.time()
    for i in range(10000000):
        b(30)
    end02 = time.time()
    print('耗时{}:'.format(end02 - start02))
test02()

在这里插入图片描述

73.参数的传递_传递可变对象_内存分析

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

1.对“可变对象”进行“写操作”,直接作用于原对象本身。传递了可变对象以后,原对象和被传递的对象是同一个对象(引用)

2.对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填充这块空间。(起到他语言的“值传递”效果,但不是“值传递”)

可变对象有:

字典、列表、集合、自定义的对象等

不可变对象有:

数字、字符串、元组、 function等

【操作】参数传递:传递可变对象的引用

b = [10,20]
def f2(m):
    print(id(m))
    m.append(30)
    print(id(m))
f2(b)
print(id(b))

传递可变对象的引用
传递参数是可变对象(例如:列表、字典、自定义的其他可变对象等),实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。在这里插入图片描述

74.参数的传递_传递不可变对象_内存分析

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

a = 100 #定义全局变量
def f1(n):
    print('n:',id(n)) #传递进来的是a对象的地址,n在栈帧内也指向堆内100这个对象
    n = n + 200 #由于a是不可变对象,因此创建新的对象n;栈帧的引用消亡,重新指向300对象
    print('n',id(n)) #n已经变成了新的对象
    print(n)
f1(a) #a传递给n
print('a',id(a))

输出结果:

n: 4419577744
n 4422324464
300
a 4419577744

在这里插入图片描述

显然,通过id值我们可以看到n和a一开始是同一个对象。给n赋值后,n是新的对象。

75.浅拷贝和深拷贝_内存分析

为了更深入的了解参数传递的底层原理,我们需要讲解一下“浅拷贝和深拷贝”。我们可以使用内置函数:copy(浅拷贝)、 deepcopy(深拷贝)

浅拷贝:不拷贝子对象的内容,只是拷贝子对象的引用。(只拷贝自己,不拷贝儿子和孙子)

深拷贝:会连子对象的内存也全部拷贝一份,对子对象的修改不会影响源对象。(自己,儿子,孙子都拷贝)

在这里插入图片描述

在这里插入图片描述

浅拷贝实例:

import copy
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)

输出结果:

a: [10, 20, [5, 6]]
b [10, 20, [5, 6]]
浅拷贝
a: [10, 20, [5, 6, 7]]
b: [10, 20, [5, 6, 7], 30]

在这里插入图片描述

深拷贝实例

import copy
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)

输出结果:

a: [10, 20, [5, 6]]
b [10, 20, [5, 6]]
深拷贝
a: [10, 20, [5, 6]]
b: [10, 20, [5, 6, 7], 30]

在这里插入图片描述

76.参数的传递_不可变对象含可变子对象_内存分析

传递不可变对象用的是浅拷贝

传递参数是不可变对象(例如:int、float、字符串、元祖、布尔值),实际传递的还是对象的引用,但是在’写操作‘时,会创建一个新的对象拷贝。这个拷贝使用的是浅拷贝,不是深拷贝。

例1:

a = 10
print('a:',id(a))

def test01(m):
    print('m:',id(m))#浅拷贝,m和a引用都指向10
    m = 20#m的引用指向20,原来指向
    print(m)
    print('m:', id(m))

test01(a)#a传递进函数内

输出结果:

a: 4531346512
m: 4531346512
20
m: 4531346832

在这里插入图片描述

例2:

#传递不可变对象时,若不可变对象里面包含的子对象是可变的,则方法内修改了这个可变子对象,源对象也发生了变化
a = (10,20,[5,6])
print('a:',id(a))

def test01(m):
    print('m:',id(m))#浅拷贝,m和a引用都指向10
    #m[0] =1 元组内的元素不能修改,会报错
    m[2][0] = 888
    print(m)
    print('m:', id(m))

test01(a)#a传递进函数内
print(a)

输出结果:

a: 4396437088
m: 4396437088
(10, 20, [888, 6])
m: 4396437088
(10, 20, [888, 6])

在这里插入图片描述

77.参数的类型_位置参数_默认值参数_命名参数

位置参数

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

def f1(a,b,c):
    print(a,b,c)
 
f1(2,3,4)
f1(2,3)#报错,位置参数不匹配 TypeError: f1() missing 1 required positional argument: 'c'

默认值参数

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

def f1(a,b,c=10,d=20): #默认值参数必须位于其他参数后面
    print(a,b,c,d)
 
f1(2,3)
f1(2,3,4)
'''
2 3 10 20
2 3 4 20
'''

命名参数

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

def f1(a,b,c):
    print(a,b,c)
 
f1(2,3,4)
f1(c=10,b=15,a=1)
'''
2 3 4
1 15 10
'''

78.参数的类型_可变参数_强制命名参数

可变参数

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

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

  1. **param(两个星号),将多个参数收集到一个“字典”对象中
def f(a,b,*c):
    print(a,b,c)

def f2(d,e,**f):
    print(d,e,f)

def f3(a,b,*c,**d):
    print(a,b,c,d)

f(1,2,3,4,5)
f2(6,7,name = 'daxian',age = 18,job = 'teacher')
f3(1,2,3,4,5,name = 'daxian',age = 18,job = 'teacher')

强制命名参数

在带星号的“可变参数”后面增加新的参数,必须是“强制命名参数”

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

#f(1,2,3)  会报错,由于a是可变参数,将2,3,4全部收集,造成b,c没有赋值
f(1,b = 2,c = 3)

输出结果:

(1,) 2 3

79.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(1,2,3,))

g = [lambda a:a*3, lambda b:b*4, lambda c:c*5]
print(g)
print(g[0](1),g[1](2),g[2](3))

h = [f,f,f]
print(h[0](2,3,4))

输出结果:

<function <lambda> at 0x100758830>
6
[<function <lambda> at 0x100758a70>, <function <lambda> at 0x100758b00>, <function <lambda> at 0x100758b90>]
3 8 15
9

80.eval()函数用法

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

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

参数
source:一个Python表达式或函数compile()返回的代码对象

globals:可选,必须是dictionary

locals:可选。任意映射对象

s= 'print("abcd")'
eval(s)

a = 10
b = 20
c = eval('(a+b)')
print(c)

dict1 = dict(a=20,b=20)
d = eval('a+b',dict1)#在dict1中的a和b
print(d)

81.递归函数_函数调用内存分析_栈帧的创建

递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类似于大家中学数学学习过的“数学归纳法”。

每个递归函数必须包含两个部分:
1.终止条件
表示递归什么时候结束。一般用于返回值,不再调用自己。
2.递归步骤
把第n步的值和第n-1步相关联

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

内存调用分析

def test01():
    print('test01')
    test02()
    #test01()会报错
    
def test02():
    print('test02')

test01()

输出结果:

test01
test02

在这里插入图片描述

如上图,执行test01()时候,先启动一个test01栈帧,再启动一个test02栈帧,test02栈帧执行完后先消亡,test01()执行结束后再消亡。


如果在test01()中加入test01():
在这里插入图片描述
如上图:第一次创建一个test01栈帧,test01()内代码执行到第二行,又创建第二个test01栈帧,第二个test01()代码执行到第二行,又创建一个test01栈帧,如此循环反复,直到栈内存满了就会报错,因此要设置一个停止的条件。

---------------------------------------在这里插入图片描述

如上图,当在第四次设置了停止条件,第三个栈帧就会执行test01()中之后的代码,代码执行结束,第三个test01栈帧就会消亡,执行第二个test01()剩余代码,执行结束后第二个test01栈帧消亡,知道所有栈帧消亡。


def test01(n):
    print(n)
    if n == 0:
        print('over')
    else:
        test01(n-1)
test01(4)

在这里插入图片描述


类似过滤器

def test01(n):
    print('test01:',n)
    if n == 0:
        print('over')
    else:
        test01(n-1)
    print('test01***',n)

test01(4)

在这里插入图片描述

如上图,创建第一个栈帧,代码执行到test01(3),print(‘test01***’,4)未执行;创建第二个栈帧,代码执行到test01(2),print(‘test01***’,3)未执行…。直到over后,执行print(‘test01***’,0),然后第5个栈帧消亡,执行print(‘test01***’,1),然后第4个栈帧消亡…直到所有栈帧消亡。因此会输入如下结果:
test01: 4
test01: 3
test01: 2
test01: 1
test01: 0
over
test01*** 0
test01*** 1
test01*** 2
test01*** 3
test01*** 4

82.递归函数_阶乘计算案例

计算10以内的阶乘:

#使用递归函数计算阶乘
def factoria(n):
    if n == 1 :
        return 1
    else:
       return n*factoria(n-1) #套娃

m = int(input('请输入1-10的整数:'))
for i in range(1,m+1):
    print('{0}!={1}'.format(i,factoria(i)))

以m=5为例子,分析内存:
在这里插入图片描述

求斐波那契数列第6项

def fibs(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fibs(n-1)+fibs(n-2)
print(fibs(6))

83.嵌套函数_内部函数_数据隐藏

嵌套函数 :
在函数内部定义的函数。
【操作】嵌套函数定义、语法结构(

def f1():
    print('f1() running')

    def f2():
        print('f2 runnig')
    f2()
f1()

定义在f1()内,调用也在f1()内,f2()只为f1()服务。

一般在什么情况下使用嵌套函数?
1.封装-数据隐藏
外部无法访问“嵌套函数

2.贯彻 DRY(Don’t Repeat Yourself)原则
嵌套函数,可以让我们在函数内部避免重复代码

3.闭包
后面会详细讲解。

【操作】使用嵌套函数避免重复代码

def printChineseName(familyName,name):
    print('{0} {1}'.format(familyName,name))
def printEnglishName(name,familyName):
    print('{0} {1}'.format(name,familyName))

'''
将上面的函数封装到嵌套函数中
'''
def printName(isChinese,familyName,name):
    def inner_print(a,b):
        print('{0} {1}'.format(a,b))
    if isChinese: #如果是中文名,把姓传给inner()中的a,名传给inner()中的b
            inner_print(familyName,name)
    else:#如果是英文名,把姓传给inner()中的b,名传给inner()中的a
            inner_print(name,familyName)

printName(True,'ding','daxian')
printName(False,'daxian','ding')

84.nonlocal_global

nonlocal:
用来声明外层的局部变量。

global
用来声明全局变量。

【操作】使用 nonlocal声明外层局变量

a =100

def outer():
    b = 10
    def inner():
        nonlocal b #,声明外部函数的局部变量,不声明nonlocal就不能在inner内修改b,会报错
        print('inner:', b)
        b = 20
        global a
        a = 1000
    
    inner()
    print('inner:', b)
    print('inner:', a)

outer()

85.LEGB规则

Python在查找“名称”时,是按照LEGB规则查找的:
Local–>Enclosed–>Global–>Built in(从内到外)

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

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

#测试LEGB
str('str(30)')
# str='global str'
def outer():
    #str = 'outer'
    def inner():
        #str='inner'
        print(str)
 
    inner()
 
outer()
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值