Python Rookie 扫盲 · 语法基础

从头开始补习一下Python

[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()是内置可变容器类型(如listdictset)自带的浅拷贝方法

不可变对象相当于重新创建一个副本,和原来没有任何关系

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))
  • 默认参数(缺省参数default)
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)

可以堆叠

注意:装饰器的应用顺序是从下往上的,即最靠近函数的装饰器最先应用。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计