文章目录

  • 前言
  • 一、函数内存分析
  • 二、全局变量&局部变量
  • 1、输出局部变量和全局变量
  • 三、参数的传递内存分析
  • 1、传递可变对象的引用
  • 2、传递不可变对象的引用
  • 3、传递不可变对象包含可变的子对象
  • 四、浅拷贝&深拷贝内存分析
  • 五、参数的类型
  • 1、位置参数
  • 2、默认值参数
  • 3、命名参数
  • 4、可变参数
  • 5、强制命名参数
  • 六、lambda 表达式
  • 七、eval() 函数
  • 八、递归函数
  • 1、递归函数调用内存分析
  • 2、阶乘计算案例



前言

本文为7月1日Python基础学习笔记,分为八个章节:

  • 函数内存分析;
  • 全局变量&局部变量;
  • 参数的传递内存分析:传递可变对象、传递不可变对象、传递包含可变子对象的不可变对象;
  • 参数的类型:位置参数、默认值参数、命名参数、可变参数、强制命名参数;
  • lambda 表达式;
  • eval() 函数;
  • 递归函数:递归函数调用内存分析以及阶乘计算案例。

一、函数内存分析

函数也是对象。

def print_star(n):
    print('*' * n)
    
print(print_star)
>>> <function print_star at 0x00000174D2010670>

print(id(print_star))
>>> 1601251116656

print_star(3)
>>> ***

c = print_star

c(3)
>>> ***

print(id(c))
>>> 1601251116656

python 函数解析插件 python分析函数_python


如图所示:

  • 执行 “c=print_star” 后,c 和 print_star 指向同一个函数对象。

二、全局变量&局部变量

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

1、输出局部变量和全局变量

a = 100

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

f1(2, 3, 4)
>>> 2 3 4
>>> {'a': 2, 'b': 3, 'c': 4}
>>> --------------------
>>> {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'print_star': <function print_star at 0x00000174D2010670>, 'c': <function print_star at 0x00000174D2010670>, 'a': 100, 'f1': <function f1 at 0x00000174D20137F0>}

三、参数的传递内存分析

从实参到形参的赋值操作。 Python 中参数的传递都是引用传递,不是值传递。 分为:

  1. 可变对象进行“写操作”,直接作用于原对象本身;
  2. 不可变对象进行“写操作”,会产生新的对象空间。

可变对象:

  • 字典;
  • 列表;
  • 集合;
  • 自定义的对象等。

不可变对象:

  • 数字;
  • 字符串;
  • 元组;
  • function 等。

1、传递可变对象的引用

传递参数是可变对象,实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。

python 函数解析插件 python分析函数_python_02

b = [10, 20]

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

f2(b)
print("b: ", id(b))
print(b)

>>> m:  2687028557440
>>> b:  2687028557440
>>> [10, 20, 30]

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

传递参数是不可变对象,在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。

python 函数解析插件 python分析函数_不可变对象_03

a = 100

def f1(n):
    print("n: ", id(n)) # 打印 a 刚传递进来时的地址
    n = n + 200 # a 是不可变对象,创建新的对象 n
    print("n: ", id(n)) # 打印新对象的地址
    print(n)

f1(a)
print("a: ", id(a)) # 打印全局变量 a 的地址

>>> n:  2291565858128
>>> n:  2291565870224
>>> 300
>>> a:  2291565858128

3、传递不可变对象包含可变的子对象

修改了这个可变对象,源对象也发生了变化。

python 函数解析插件 python分析函数_开发语言_04

# 传递不可变对象时,不可变对象里若包含可变的子对象
# 修改该子对象,源对象也会被修改

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: 1688105036928
>>> m:  1688105036928
>>> (10, 20, [888, 6])
>>> m:  1688105036928
>>> (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)

testCopy()
>>> 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

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)

testDeepCopy()
>>> 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)
>>> 2 3 4

2、默认值参数

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

def f1(a, b, c=10, d=20):
    print(a, b, c, d)
   
f1(8, 9, 19)
>>> 8 9 19 20

3、命名参数

按照形参的名称传递参数。

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

4、可变参数

  • *param(一个星号),将多个参数收集到一个元组对象中;
  • **param(两个星号),将多个参数收集到一个字典对象中。
def f3(a, b, *c, **d):
    print(a, b, c, d)
 
f3(8, 9, 20, 30, name='xiaoli', age=18)
>>> 8 9 (20, 30) {'name': 'xiaoli', 'age': 18}

5、强制命名参数

在带星号的“可变参数”后面增加新的参数。

def f1(*a, b, c):
    print(a, b, c)
   
f1(2, b=3, c=4)
>>> (2,) 3 4

六、lambda 表达式

lambda 表达式可用来声明匿名函数,是一种在同一行中定义函数的方法。
lambda 表达式只允许包含一个表达式, 不能包含复杂语句,该表达式的计算结果就是函数的返回值。

f = lambda a, b, c: a+b+c

print(f)
>>> <function <lambda> at 0x00000174D2013760>

f(2, 3, 4)
>>> 9

七、eval() 函数

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

a = 10
b = 20
c = eval("a+b")
print(c)
>>> 30

dict1 = dict(a=100, b=200)
d = eval("a+b", dict1)
print(d)
>>> 300

八、递归函数

1、递归函数调用内存分析

每个递归函数必须包含:

  • 终止条件: 表示递归什么时候结束。一般用于返回值,不再调用自己;
  • 递归步骤: 把第 n 步的值和第 n-1 步相关联。

python 函数解析插件 python分析函数_不可变对象_05

# 递归函数的内存使用

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

    print("test***", n)

test01(4)

>>> test01:  4
    test01:  3
    test01:  2
    test01:  1
    test01:  0
    over
    test*** 0
    test*** 1
    test*** 2
    test*** 3
    test*** 4

2、阶乘计算案例

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

result = factorial(5)
print(result)

>>> 120