Python_day06_函数

函数

函数也是对象_内存分析

# 测试函数也是对象

def test01():
    print('GSH')

test01()
c = test01
c() # 同样是调test01()

print(id(test01)) # 4458865728
print(id(c)) # 4458865728  此时他们(在栈内存中)指向了堆内存中同一个对象 所以他们的id值是一样的
print(type(c))# <class 'function'> 函数类型

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

  1. 全局变量是指在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。
    全局变量降低了函数的通用性和可读性,尽量避免使用。
    全局变量一般做常量使用。
    函数内要改变全局变量的值,使用global声明一下。
  2. 局部变量是指在函数体重(包含形式参数)声明的变量,函数调用完就消失了。
    局部变量的引用比全局变量快,优先考虑使用。
    如果全局变量和局部变量同名,则在函数内隐藏全局变量,只使用同名的局部变量。

测试全局变量 局部变量

a = 3 # 全局变量
def test01():
    b = 4 # 局部变量 作用域仅限函数内部
    print(b*4)
    # print(a) # 这里不可以直接用 因为默认是局部变量 但是这个局部变量并没有定义
    global a # 如果要在函数内部改变全局变量的值,增加global关键字声明
    a = 300
    # print(a) # 300

test01()
test01()

print(a) # 可以打印 a是全局变量 此时因为函数内部因为global引用了a 所以函数内部可以将外部的全局变量进行改变a 此时a变成300
# print(b) #打印不出来 不在b的作用域内

打印结果:

16
16
300

locals() and globals()

a = 100

def f1(a,b,c):
    c = 0
    print(a,b,c)
    print(locals()) # 打印输出的局部变量
    print('*'*20)
    print(globals()) # 打印输出的全局变量

f1(2,3,4)

输出:

2 3 0
{'a': 2, 'b': 3, 'c': 0}
********************
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x100ba92d0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/Users/guosihan/PycharmProjects/MyTest/day_6/func_test3.py', '__cached__': None, 'a': 100, 'f1': <function f1 at 0x100bf7440>}

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

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

# 测试局变量和全局变量的效率:

import time
import math

def test01():
    start = time.time()
    for i in range(1000000):
        math.sqrt(30)
    end =  time.time()
    print('耗时1:',(end - start)) # 耗时1: 0.09889388084411621

def test02():
    b = math.sqrt
    start = time.time()
    for i in range(1000000):
        b(30)
    end =  time.time()
    print('耗时2:',(end - start)) # 耗时2: 0.05476093292236328

test01()
test02()

参数的传递

函数的参数传递实质上就是: 从实参到形参的赋值操作。
所有的赋值操作都是’引用的赋值’。所以,Python中参数的传递都是’引用的传递’。不是’值传递’。

  1. 对’可变对象‘ 直接作用于原对象的本身。可变对象有: 字典,列表,集合,自定义的对象。
  2. 对’不可变对象’会产生一个新的’对象空间’,并用新的值填充这块。 不可变的对象有: 数字,字符串,元祖,function。
    操作: 参数传递: 传递可变对象的引用。
# 传递可变对象 列表举例
b = [10,20]
# 本质是b和m同时操作同一个对象
def f2(m):
    print('id(m):',id(m)) # b和m是同一个对象
    m.append(30) # 由于m是可变对象 不创建对象拷贝 直接修改这个对象
	print('id(m):',id(m))
f2(b)
print('b:',id(b))
print(b)

结果:
id(m): 4315951584
id(m): 4315951584
b: 4330934752
[10, 20, 30]

传递不可变对象的引用:

# 传递不可变对象的引用 会产生一个新的对象
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))
print('a:',a)
结果:
n: 4325429472
n: 4327458768
300
a: 4325429472 # 跟第一个一样
a: 100 # 注意这里还是没有变化 还是原来的100

总结: 显然,通过id值我们可以看到n和a一开始是同一个对象。给n赋值后,n是新对象。因为原来的n是不可以变化的。

浅拷贝和深拷贝

内置函数copy(浅拷贝),deepcopy(深拷贝)的学习。
浅拷贝: 不拷贝子对象的内容,只是拷贝子对象的引用。
深拷贝: 会连子对象的内存也全部拷贝一份,对子对象的修改不会影响原对象。

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]

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

a = (10,20,[3,4])
print('a:',id(a))

def test01(m):
    print('m:',id(m))
    print(m)
    # a = 20 # 重新赋值
    m[2][0] = 888
    print('m:',id(m))
    print('函数内的a:',a)
    print('函数内的m',m)

test01(a)
print('函数外的a:',a)

输出结果:

a: 4345301824
m: 4345301824
(10, 20, [3, 4])
m: 4345301824
函数内的a: (10, 20, [888, 4])
函数内的m (10, 20, [888, 4])
函数外的a: (10, 20, [888, 4])

参数的几种类型:

位置参数

参数为啥按顺序传递,需要个数和形参匹配,按位置传递参数,称为’位置参数’。

默认值参数

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

def f1(a,b=1,c=9):
    print(a,b,c)

f1(8,9) # 输出: 8 9 9
命名参数

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

def f1(a,b,c):
    print(a,b,c)

f1(8,9,10) # 此时位置必须对应的 此时是位置参数
f1(c=10,b=2,a=4) # 根据名字来匹配 不一定非得按顺序来匹配
可变参数
  1. *param(一个星号),将多个参数收集到一个’元祖’对象中。
  2. **param(两个星号),将多个参数收集到一个字典对象中。
def f1(a,b,*c):
    print(a,b,c)

f1(8,9,12,28)
print('*********')

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

f2(8,9,name = 'GSH',age = 18)
print('*********')

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

f3(8,9,20,30,59,name = 'GSH',age = 18) # 没有参数名对应的是c,最后有参数名的是对应参数d

输出:

8 9 (12, 28)
*********
8 9 {'name': 'GSH', 'age': 18}
*********
8 9 (20, 30, 59) {'name': 'GSH', 'age': 18}
强制命名参数

带*号的"可变参数"后面增加新的参数,调用的时候必须是"强制命名参数"。

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

# 当调用f4时必须这样命名。
f4(12,23,34,54,b = 2,c = 9)

输出结果:

(12, 23, 34, 54) 2 9

lamnda 表达式和匿名函数

lambda表达式可以用来声明匿名函数,lambda函数是一种简单的,在同一行中定义函数的方法。lambda函数实际生成了一个函数对象
lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。
基本语法:
lambda 参数1,参数2参数3... : <表达式>
参数1 参数2 参数3位函数的参数.<表达式>相当于函数体。运算结果是: 表达式的运算结果。

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)) # 0是列表序列,6是传进去的数字。
# 解释:
def test01(a,b):
    return a+b

#这里就是将函数看成一个对象传进h列表里
h = [test01,test01]
re = h[1](11,33)
print(re) # 44

输出:

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

eval()函数

# 测试eval()函数
s = 'print("hello")'
eval(s)

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

dict1 = dict(a = 100,b = 200)

d = eval('a+b')
print(d) # 30

e = eval('a+b',dict1)
print(e) # 300

递归函数

# 测试递归函数的基本原理
def test01(n):
    print('test01',n) # 执行语句1 递归语句前出现的语句是最先执行
    if n == 0:
        print('over') # 这里就是终止的条件
    else:
        test01(n-1) # 这里就是把第n步的值和第n-1步相关联
    print("test01***", n) # 执行语句2 递归语句后出现的语句是最后执行
    # test01() # 自己调自己 递归调用
    # RecursionError: maximum recursion depth exceeded while calling a Python object
    # print('********')


def test02():
    print('test02')

test01(4) # 第一个打开的是最后关闭的

# 总结学习:
# 就是自己调用自己的函数,在函数内部直接或间接的自己调用自己。
# 1. 终止条件:表示递归函数什么时候结束。一般用于返回值,不再调用自己。
# 2. 递归步骤:把第n步的值和第n-1步相关联。

结果输出:

test01 4
test01 3
test01 2
test01 1
test01 0
over
test01*** 0
test01*** 1
test01*** 2
test01*** 3
test01*** 4

阶乘案例:

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

re = factorial(5)
print(re) # 120

递归阶乘

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值