[TOC]
深浅拷贝
1
2
3
4
5
6
7
8
9
10
11
|
a = [i for i in range(4)]
b = a
print(a)
# 0 1 2 3
print(b)
# 0 1 2 3
b[0] = 100
print(a)
# 100 1 2 3
print(id(a)) # 1879915237696
print(id(b)) # 1879915237696
|
Python直接赋值相当于建立一个新的引用
浅拷贝
创建新的对象,只复制原对象本身,不复制原对象内部的子对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
a = [i for i in range(4)]
from copy import copy
b = copy(a)
print(a)
print(b)
b[0] = 100
print(a)
print(id(a))
print(id(b))
'''
[0, 1, 2, 3]
[0, 1, 2, 3]
[0, 1, 2, 3] # a未发生改变
2752117477696 # 地址不一样
2752117479552
'''
|
之所以是浅拷贝,会体现到含有子对象的情况:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from copy import copy
a = [
[i for i in range(5)],
[i for i in range(5)],
[i for i in range(5)],
]
b = copy(a)
print(id(a), id(b))
for ia, ib in zip(a, b):
print(id(ia), id(ib))
'''
2955264350656 2955262793600
2956986749248 2956986749248 # 子对象未发生变化
2956986751104 2956986751104 # 子对象未发生变化
2955300137024 2955300137024 # 子对象未发生变化
'''
|
当只是简单创建副本,同时不存在内部资源修改时使用
深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from copy import deepcopy
a = [
[i for i in range(5)],
[i for i in range(5)],
[i for i in range(5)],
]
b = deepcopy(a)
print(id(a), id(b))
for ia, ib in zip(a, b):
print(id(ia), id(ib))
'''
1640473937792 1640472338688
1640049480000 1640052597184
1640049481856 1640049786560
1640509759936 1640049482304
'''
|
所有子对象都会被重新创建
函数传参
首先需要分两类:
- 可变对象:列表、字典
- 不可变对象:数值、字符串、元组
可变对象作为引用进行传参,函数修改值,外部会发生改变
1
2
3
4
5
6
|
def modify(x: list):
x.append(42)
x = [1, 2, 3]
modify(x)
print(x) # Output: [1, 2, 3, 42]
|
但是如果发生赋值,则也是重新创建一个对象
1
2
3
4
5
6
|
def modify(x : list):
x = [2, 3, 4] # 新创建
x = [1, 2, 3]
modify(x)
print(x) # Output: [1, 2, 3]
|
因此可以利用这个机制,使用.copy
一个副本,进行重新赋值
1
2
3
4
5
6
|
def modify(x : list):
x = x.copy()
x = [1, 2, 3]
modify(x)
print(x)
|
其中.copy()
是内置可变容器类型(如list
、dict
、set
)自带的浅拷贝方法
不可变对象相当于重新创建一个副本,和原来没有任何关系
1
2
3
4
5
6
7
8
|
def modify(x: str, y: int):
x = x + "niko"
y += 10
x = "hello"
y = 5
modify(x, y)
print(x, y) # Output: hello 5
|
函数
函数参数
1
2
3
|
def fun(a, b, c):
return a + b + c
print(fun(1, 2, 3))
|
1
2
3
|
def fun(a, b, c=12):
return a + b + c
print(fun(1, 2))
|
注意默认参数需要按顺序写在最后面
1
2
3
4
|
def fun(*args): # 加一个*就表示可变参数,args是约定俗成,可以更换其他名字
print(args) # 以元组形式打包
fun(1,2,3) # (1, 2, 3)
|
1
2
3
4
5
|
def fun(**kwargs):
print(kwargs)
fun(name='hcf', school='dhu/ecnu')
# Output: {'name': 'hcf', 'school': 'dhu/ecnu'}
|
以键值对的形式传入,打包成字典
1
2
3
4
5
|
def combined_example(*args, **kwargs):
print("位置参数 (args):", args)
print("关键字参数 (kwargs):", kwargs)
combined_example(1, 2, 3, name="Alice", age=25)
|
当两者一起使用时,函数可以接受任意数量和类型的参数(位置参数 + 关键字参数)。
规则:*args
必须在 **kwargs
之前。
作用域
1
2
3
4
5
6
7
|
a = 100
print(a) # 100
def fun():
a = 200
print('fun ', a) # 200
fun()
print(a) # 100
|
函数内部的a是局部变量,与外部的a没有任何关系
1
2
3
4
5
6
7
8
9
10
|
def fun():
a = 100
# print(a) error 局部变量超出作用域
if True:
b = 10
print(b) # ok 这里b不是全局变量
|
所以这个和C++不太一样
1
2
3
4
5
6
7
|
a = 100
def fun():
global a # 声明这个是全局变量,这样后续赋值不会认为是新的变量
a = 300
print(a)
fun()
print(a)
|
lambda
1
2
3
4
5
6
7
8
9
|
add_fun = lambda a,b : a+b
print(add_fun(2,3))
add_fun = lambda a,b=5 : a+b # 带默认参数
print(add_fun(2))
fun = lambda **kwargs: kwargs
print(fun(name='hcf', age=18, city='beijing'))
# {'name': 'hcf', 'age': 18, 'city': 'beijing'}
|
内置函数
zip
之类的就不说了,写点自己不会的
map
1
2
3
4
5
6
7
8
9
10
11
|
a = [1,2,3,4]
fun = lambda a: a**2
b = [fun(i) for i in a]
c = list(map(fun, a))
print(b, c)
a = [1,2,3,4]
b = [5,6,7,8]
c = [9,10,11,12]
d = list(map(lambda x, y, z: x + y + z, a, b, c))
print(d)
|
map
传入一个函数和若干个可迭代对象,把所有对象塞进去跑
最后返回一个迭代器,可以直接list
转换
解包
(不要管为什么放在这里)
1
2
3
4
5
6
7
8
9
10
11
12
13
|
a = [1,2,3,4]
a1, a2, a3, a4 = a
print(a1, a2, a3, a4)
print(*a)
# 1 2 3 4
# 1 2 3 4
*a, = [1, 2, 3] # a = [1, 2, 3]
*a, b = [1, 2, 3] # a = [1, 2], b = 3
first, *rest = [1, 2, 3] # first=1, rest=[2, 3]
*a = [1, 2, 3]
# SyntaxError: starred assignment target must be in a list or tuple
# 因为这个写法非常奇怪,为什么不直接 a = [1,2,3]
|
异常
1
2
|
raise Exception("Error in test.py")
# 主动抛出异常
|
闭包与装饰器
闭包
闭包就是函数及其周边环境的组合
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def outer_function(msg):
# 外部函数的变量
message = msg
def inner_function():
# 内部函数访问外部函数的变量
print(message)
# 返回内部函数
return inner_function
# 创建闭包
my_closure = outer_function("Hello, 闭包!")
my_closure() # 输出: Hello, 闭包!
|
本质上记住了message = msg
这一函数的中间状态,使得超出作用域时,仍然能正常运行
1
2
3
4
5
6
7
8
9
10
11
12
|
def counter():
counter = 0
def increment():
nonlocal counter # 声明为上一级的变量
counter += 1
print('counter:', counter)
return increment
count = counter()
count() # 输出: counter: 1
count() # 输出: counter: 2
count() # 输出: counter: 3
|
我们可以基于这个特性,使用闭包做一个计数器
1
2
3
4
5
6
7
8
9
|
def power_factory(exponent):
def power(base):
return base ** exponent
return power
square = power_factory(2)
cube = power_factory(3)
print(square(5)) # 输出 25
print(cube(5)) # 输出 125
|
同时可以用闭包预设一些参数,批量生成函数
装饰器
装饰器可以给函数添加新功能,且满足:
- 不修改原函数代码
- 不改变原函数调用方法
- 本质是一个闭包函数
首先我希望实现一个功能,在一个函数执行的前后,输出debug信息:
1
2
3
4
5
6
7
8
9
|
def debug(fun):
print('debug start')
fun()
print('debug end')
def test():
print('test function')
debug(test)
|
如果使用闭包的方法进行实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
def debug(func):
def wrapper():
print('debug start')
func()
print('debug end')
return wrapper
def test():
print('test function')
closure = debug(test)
closure()
|
这样就非常清新自然,把test
打包成另外一个带有调试信息的函数
事实上有时候我们不需要引入额外的函数:
1
2
|
test = debug(test)
test()
|
这样我们就可以完成函数功能的动态修改
此时,还有一种更加简单的语法糖写法——装饰器,与之等效:
1
2
3
4
5
6
7
8
9
10
11
12
|
def debug(func):
def wrapper():
print('debug start')
func()
print('debug end')
return wrapper
@debug
def test():
print('test function')
test()
|
后续你只需要通过注释这行代码就能决定修不修改函数功能
被装饰的函数携带参数并不影响:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
def debug(func):
# 所以wrapper就是被装饰后的函数本身
def wrapper(*args, **kwargs): # func需要什么参数全部一样传
print('debug start')
func(*args, **kwargs) # 传参数
print('debug end')
return wrapper
@debug
def test(*args, **kwargs):
print(f'test function')
print(args)
print(kwargs)
test(1, 2, 3, a=4, b=5, c=6)
'''
debug start
test function
(1, 2, 3)
{'a': 4, 'b': 5, 'c': 6}
debug end
'''
|
带参数的装饰器
debug
作为闭包函数,只能传入一个函数作为参数
因此无法携带更多参数,所以我们为了能够给装饰器引入其他参数,需要再嵌套一层:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def decorator_with_args(arg1, arg2):
def debug(func):
def wrapper(*args, **kwargs):
print('debug start')
print('Decorator arguments:', arg1, arg2) # 装饰器的参数
func(*args, **kwargs)
print('debug end')
return wrapper
return debug
@decorator_with_args('arg1_value', 'arg2_value')
def test(*args, **kwargs):
print(f'test function')
print(args)
print(kwargs)
test(1, 2, 3, a=4, b=5, c=6)
|
元信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
def test():
print('test function')
print(test.__name__) # test
def debug(func):
def wrapper():
print('debug start')
func()
print('debug end')
return wrapper
@debug
def test():
print('test function')
test()
print(test.__name__) # wrapper
|
使用装饰器后,原函数的__name__
、__doc__
等元信息会被wrapper函数覆盖
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from functools import wraps
def debug(func):
@wraps(func) # 使用wraps(也是一个装饰器),把func作为参数传入
def wrapper(*args, **kwargs):
print('debug start')
func(*args, **kwargs)
print('debug end')
return wrapper
@debug
def test(*args, **kwargs):
print('function test')
test()
print(test.__name__) # test
|
wraps
本身也是一个装饰器,功能大概是修改了wrapper
的元信息
应用
应用上非常广泛
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
def timing(func):
@wraps(func)
def wrapper(*args, **kwargs):
import time
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Function {func.__name__} took {end_time - start_time:.4f} seconds")
return result
return wrapper
@timing
def test(*args, **kwargs):
print('function test')
test(1,2,3,4,5,6,7,8,9)
|
比如计时功能的添加
1
2
3
4
5
|
@debug
@timing
def test(*args, **kwargs):
print('function test')
test(1,2,3,4,5,6,7,8,9)
|
可以堆叠
注意:装饰器的应用顺序是从下往上的,即最靠近函数的装饰器最先应用。