🌀Jarson Cai's Blog
头脑是日用品,不是装饰品
Python进阶用法(二)
使用Python的高级特性更加高效地完成任务

Python进阶笔记(二)

Python中“*”的功能

Python中*最常用的便是作为一个运算符号使用,但其实*也可用于实现重复、打包、解包功能。

  • *的重复功能,看一个例子:
1
2
3
4
print("hahaha" * 3)
# hahahahahahahahaha
print([1, 2, 3] * 3)
# [1, 2, 3, 1, 2, 3, 1, 2, 3]
  • *的打包功能,它可以将多个值捆绑到一个容器中
1
2
3
4
5
6
numbers = [1, 2, 3, 4, 5]
first, *rest = numbers
print(first)
print(rest)
# 1
# [2, 3, 4, 5]

还有一个典型的例子便是返回yaml文件的值时,如果不需要用到全部值,可以使用这个方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

import yaml

def config_read():
    config_path = "./configs/config.yaml"
    fo = open(config_path, 'r', encoding='utf-8')
    res = yaml.load(fo, Loader=yaml.FullLoader)

    return res

def return_config():
    config = config_read()
    return (config["LOG"], config["SERVER"], config["DEVICE"])

first, *_ = return_config()
print(first)
print(_)
# {'LOG_LEVEL': 'DEBUG', 'LOG_FOLDER': './logs', 'LOG_FILENAME': 'service.log', 'BACKUPCOUNT': 5}
# [{'IP': '0.0.0.0', 'PORT': 10004, 'WORKERS': 1}, 'mps']

可以看到当前不需要的参数暂时会被存入_中。

在函数中有一个常用的作法是使用args,所有传入的参数都会被打包成一个列表:

1
2
3
4
5
6
def print_values(*args):
    for arg in args:
        print(arg)

print(1, "2", "都是借口")
# 1 2 都是借口

那么说到args,就不得不说kwargs了,它通常配合**使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def example(**kwargs):
    for key, value in kwargs.items():
        print(f"{key} = {value}")

example(a=1, b=2, c=3)
# a = 1
# b = 2
# c = 3

example(1, 2, 3) 
# 注意这种写法会报错,因为kwargs必须传入字典
  • *的解包功能,与打包相反,它会将一系列的值打开(类似于解压),比如列表可以使用*解包,字典可以使用**解包。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def greet(name, age):
    print(f"hello {name}, you are {age} years old.")

person = ("Alice", 30)
greet(*person)
# hello Alice, you are 30 years old.

a = [1, 2, 3]
b = (3, 4, 5)
c = [*a, *b]
print(c)
# [1, 2, 3, 3, 4, 5]

可以看到它甚至可以合并元组和列表,因为*对列表和元组的解包都生效。再看**的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def create_profile(name, age, email):
    print(f"name: {name}")
    print(f"age: {age}")
    print(f"email: {email}")

dict_1 = {
    "name": "Jarson Cai",
    "age": 18,
    "email": "jarsoncai@qq.com"
}
create_profile(**dict_1)
# name: Jarson Cai
# age: 18
# email: jarsoncai@qq.com

dict_2 = {"tall": 180}
dict_3 = {**dict_1, **dict_2}
list_3 = [*dict_1, *dict_2]
set_3 = {*dict_1, *dict_2}
print(dict_3)
# {'name': 'Jarson Cai', 'age': 18, 'email': 'jarsoncai@qq.com', 'tall': 180}
print(list_3)
# ['tall', 'name', 'email', 'age']
print(set_3)
# {'tall', 'name', 'email', 'age'}

**在传入参数重可以解包,在合并字典中也可以解包,同样在使用*对字典进行解包时,默认会将所有的key解包出来作为一个值。

装饰器

装饰器(Decorator)是Python中一种非常强大且灵活的工具,用于修改或增强函数或方法的行为。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。

举一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def square(x):
    return x*x

def print_running(f, x):
    print(f"{f.__name__} is running")
    return f(x)

result = print_running(square, 2)
print(result)
# square is running
# 4

它在这里的作用是不改变函数的情况下,增加了函数运行的提示。同样的功能可以使用装饰器来实现,我们在原有的基础上再增加一个测量时间的功能:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20

def square(x):
    return x*x

import time

def decorator(func):
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} is running")
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} execution time: {end_time - start_time}")
        return result
    return wrapper

decorator_square = decorator(square)
decorator_square(10)
# square is running
# square execution time: 9.5367431640625e-07

而python中定义了一个更为简单的方式来使用装饰器吗,就是直接给函数戴一个装饰器的帽子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import time

def decorator(func):
    def wrapper(*args, **kwargs):
        print(f"{func.__name__} is running")
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} execution time: {end_time - start_time}")
        return result
    return wrapper

@decorator
def square(x):
    return x*x

square(10)

上述的写法效果与前面相同。 但是判断不同函数运行时间是否超过阈值的功能虽然常见,但它往往是变化的,也就是说需要的阈值会不同,这样我们可以再套一层定义一个装饰器生成器,来看下面的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time

def timer_decorator(threshold):
    def time_calculate(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            if end_time - start_time > threshold:
                print(f"{func.__name__} took longer than {threshold}")
            return result
        return wrapper
    return time_calculate


@timer_decorator(0.3)
def time_sleep():
    time.sleep(0.4)

time_sleep()
print(time_sleep.__name__)
# time_sleep took longer than 0.3
# wrapper

上述代码将装饰器变为了可定义的装饰器,便于使用,但是使用了装饰器后的函数名会发生改变,我们需要使用一种特殊的方法进行继承。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import time
import functools

def timer_decorator(threshold):
    def time_calculate(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            if end_time - start_time > threshold:
                print(f"{func.__name__} took longer than {threshold}")
            return result
        return wrapper
    return time_calculate


@timer_decorator(0.3)
def time_sleep():
    time.sleep(0.4)

time_sleep()
print(time_sleep.__name__)
# time_sleep took longer than 0.3
# time_sleep

上述代码中使用python中自带的装饰器@functools.wraps

总的来说,装饰器有许多优点:

  • 提升代码复用性,避免冗余。
  • 使用装饰器可以保证一个复杂的函数逻辑清晰,减少代码查看量。
  • 通过装饰器,可以扩展别人的函数,在添加额外的行为时,不会修改原函数的逻辑。

使用dotenv模块存储敏感信息

在项目中,我们通常会用到一些数据库的密码,以及大模型相关的key信息,然而如果我们将这些信息直接写在代码中,很容易造成泄露。

我们可以使用python-dotenv模块来解决这个问题。首先下载这个模块:

1
pip install python-dotenv

在项目目录下新建一个.env文件,将敏感信息存入其中:

1
2
OPENAI_API_KEY = "FAKE_OPENAI_API_KEY"
DB_PASSWORD = "FAKE_DB_PASSWORD"

通过代码来读取:

1
2
3
4
5
6
7
from dotenv import load_dotenv
# 将文件中的环境变量变为进程中的环境变量
load_dotenv()

import os 
openai_api_key = os.getenv("OPENAI_API_KEY")
db_password = os.getenv("DB_PASSWORD")

当然也可以将项目的名称加入.env文件中来进行区分:

1
2
3
from dotenv import load_dotenv
# 读取的时候也需要加入改变后的名字
load_dotenv("projectA.env")

如果需要上传git仓库,则需要加好.gitignore

*.env

最后修改于 2024-07-01

知识共享许可协议
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。