从Python代码到诗

news2024/11/20 6:16:36

在这里插入图片描述

🐳序言

  在Python社区,没有强制的编码标准,这虽然赋予了开发者更多的自由,但也导致代码风格不一致性。使得部分代码变得晦涩难懂,本文将探讨一系列的开发技巧和最佳实践,开发出优雅的Python脚本。

1、参数接收与校验

argparse模块可以使用标准的命令行界面风格解析命令行参数,支持位置参数和可选参数,使用-h可查看脚本使用说明。

1.1 ArgumentParser常用属性

字段说明备注
name参数名称或选项例如:-file、–file
default默认值
type类型
choices可选值列表
required是否必传默认为False
nargs参数的数量默认为None,支持int,+,*
metavar帮助信息显示参数的名字
action动作支持store_true、store_false等
help帮助信息

1.2 获取位置参数

使用位置参数我们可以处理n多个值,输出列表

# python main.py 3 5

import argparse

parser = argparse.ArgumentParser(description="位置参数测试")
parser.add_argument("number", type=int, nargs="*", help="传入数字")

args = parser.parse_args()

# [3, 5]
print(args.number)

1.3 获取可选参数

使用可选参数可以避免传入位置不准确的问题

# python main.py --name '欢颜' --datatime '2023-08-18 13:14:52' --is_worktime

import argparse

from datetime import datetime


class ParserArgs:

    def __init__(self):
        parser = argparse.ArgumentParser(description='从Python代码到诗')
        parser.add_argument('--name', type=str, required=True, help='输入名称')
        parser.add_argument(
            '--datetime', 
            type=self.validate_dt, 
            required=True, 
            help='日期时间(YYYY-MM-DD HH:MM:SS)'
        )
        parser.add_argument('--is_worktime', action='store_true', help='是否在工作时间')

        self.args = parser.parse_args()
		
    @staticmethod
    def validate_dt(dt_str, fmt_dt='%Y-%m-%d %H:%M:%S'):
        try:
            dt_obj = datetime.strptime(dt_str, fmt_dt)
        except ValueError:
            raise argparse.ArgumentTypeError("Invalid datetime format.")
        else:
            return dt_obj


# Namespace(name='欢颜', datetime=datetime.datetime(2023, 8, 17, 0, 0), is_worktime=True)
Args = ParseArgs().args

2、日志记录器

​ 项目开发中应避免直接使用print函数,日志记录能够更好地管理和调试代码、故障排除以及监控程序的运行状态。Python内置了logging 模块,用于创建日志记录器,实现灵活的日志记录和输出控制,通常将日志直接写入文件,输出更加优雅。

2.1 日志简单使用

#!/usr/bin/env python3
# -*-coding:utf-8 -*-

import logging


def get_logger():
    # 实例化日志对象
    logger = logging.getLogger()
    logger.setLevel(logging.INFO)

    ch = logging.StreamHandler()
    formatter = logging.Formatter(
        "[%(asctime)s] [%(levelname)s] [%(filename)s] [%(funcName)s:%(lineno)d] %(message)s", 
        "%Y-%m-%d %H:%M:%S"
    )
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    return logger


logger = get_logger()

# [2023-08-18 16:40:07] [INFO] [main.py] [main:27] hello world!
logger.info("hello world!")

3、进度条的使用

  在命令行界面中展示进度条,以增强长时间运行的循环、任务或操作的可视化体验。Python的tqdm库,可以非常方便地应用于各种迭代操作,从文件读写到数据处理,都能通过进度条的方式直观地显示进度。

3.1 tqdm方法常用参数

参数含义类型必填备注
iterableIterable 用进度条装饰iterable可选可迭代对象
total预计迭代的次数int/float可选默认为迭代器的长度
ncols整个输出消息的宽度int可选
mininterval最小进度显示更新间隔float可选默认0.1s
ascii进度填充bool可选默认unicode平滑块来填充仪表
unit单位str可选默认为it
color条形颜色str可选
initial初始计数器值int/float可选

3.2 trange方法

trange方法是封装了range方法的tqdm, 它会在循环中显示进度条、多用于循环次数固定的情况。

import time

from tqdm import trange

for _ in trange(10):
    time.sleep(0.1)

3.3 tqdm方法

tqdm是tqdm模块最常用的方法,用于在循环中显示进度条。传递一个迭代对象给它,然后迭代这个对象,会产生一个进度条。

import time

from tqdm import tqdm
from colorama import Fore, Style

iterable = ['Python', 'GoLang', 'Java', 'JavaScript']
progress_bar = tqdm(iterable, ncols=100, ascii=False)
for element in progress_bar:
    progress_bar.set_description("{}正在处理:{}".format(Fore.LIGHTBLUE_EX, element))

    time.sleep(0.5)

4、测试函数执行时间

  Python关于计时比较有代表性的两个库是timetimeittime库中有time()perf_counter()以及process_time()三个函数可用来计时(以秒为单位),加后缀_ns表示以纳秒计时。上述三者的区别如下:

  • time()精度上相对没有那么高,而且受系统的影响,适合表示日期时间或者大程序的计时。
  • perf_counter()适合小一点的程序测试,会计算sleep()时间。
  • process_time()适合小一点的程序测试,不计算sleep()时间。

time库相比,timeit 有两个优点:

  • timeit 会根据您的操作系统和 Python 版本选择最佳计时器。
  • timeit 在计时期间会暂时禁用垃圾回收。

4.1 使用time计时

import time

def statistic_cost(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        cost = time.time() - start_time
        logger.info(f"execute {func.__name__} cost {cost:.3f} 秒")
        return result

    return wrapper

def test():
    time.sleep(3)


if __name__ == '__main__':
    test()

4.2 使用timeit

"""
timeit方法参数:

stmt: 需要计时的语句或者函数
setup: 执行stmt之前要运行的代码。通常,它用于导入一些模块或声明一些必要的变量。
timer: 计时器函数,默认为time.perf_counter()。
number: 执行计时语句的次数,默认为一百万次。
globals: 指定执行代码的命名空间。
"""

import time
from timeit import timeit

def test():
    time.sleep(3)


if __name__ == '__main__':
    
    cost = timeit(test, number=1)
    print(cost)

5、使用装饰器缓存

  Python支持装饰器缓存,在内存中维护特定类型的缓存,以实现最佳软件驱动速度,使用lru_cache来实现。lru_cache 的性能优势在于重复性高、计算密集型的函数。在一些情况下,使用缓存可能会导致额外的内存使用,因为缓存会保留一部分函数调用的结果。因此,应根据具体情况权衡是否使用缓存。对于需要实时更新或频繁变动的数据,lru_cache 并不适用。它适用于那些函数的计算结果在短时间内保持一致的场景。

5.1 缓存简单使用

#!/usr/bin/env python3
# -*-coding:utf-8 -*-
# maxsize: 指定缓存中保留的最大函数调用结果数量。当超过数量,最早的结果将被移除,默认为128。
# typed: 如果设置为True,则不同类型的参数会被视为不同的参数。

import functools

@functools.lru_cache(maxsize=128)
def fibonacci(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

# 从获取相同
for _ in range(10):
	fibonacci(10)

5.2 缓存强制刷新

  lru_cache函数不支持主动刷新的功能, 缓存的更新是由缓存装饰器内部的缓存策略自动管理的,通常是根据函数的参数和调用次数来决定哪些缓存条目会被保留或淘汰。如果需要在特定时刻主动刷新缓存, 可以使用cache_clear() 方法强制清除缓存中的所有条目,从而让缓存失效,下次访问时会重新计算。你可以在数据发生变动时调用此方法。

# 以上面计算斐波那契函数为例

# 强制清除缓存
fibonacci.cache_clear()

6、单元测试

本文使用unittest编写单元测试

  单元测试是软件开发过程中的一个关键环节,旨在验证代码中的最小功能单元是否按预期工作。通过编写单元测试,你可以有效地发现和修复代码中的错误,确保代码在不同情况下都能正确运行。在Python中有多个测试框架可以选择unittest、pytest和nose等。

6.1 单元测试简单使用

6.1.1 编写一个函数用于测试
import functools

@functools.lru_cache(maxsize=128)
def fibonacci(n):
    if n < 0:
        raise ValueError('Invalid Value')
    elif n in (0, 1):
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)
6.1.2 使用unittest完成测试
import unittest

from script import main as alg


class TestFibonacci(unittest.TestCase):

    def test_fibonacci(self):
        output = alg.fibonacci(10)
        self.assertEqual(output, 55)

    def test_fibonacci_invalid(self):
        with self.assertRaises(ValueError):
            alg.fibonacci(-1)


if __name__ == '__main__':
    unittest.main()

6.2.1 模拟对象

  模块unittest.mock提供了两个主要的类来进行模拟和断言行为(Mock和MagicMock),Mock通常用于模拟普通方法和属性,MagicMock具有更多的魔法方法,允许一些特殊的Python行为,如迭代,上下文管理等,返回自身。

对以下函数进行测试

ParserArgs对象在参数接收于校验模块

import time
from colorama import Fore

from logger import logger
from parser_args import ParserArgs


def launch():
    args = ParserArgs().args
    logger.info(f"input args: {args.__dict__}")

    iterable = ['Python', 'GoLang', 'Java', 'JavaScript']
    progress_bar = tqdm(iterable, ncols=100, ascii=False)
    for element in progress_bar:
        progress_bar.set_description("{}正在处理:{}".format(Fore.LIGHTBLUE_EX, element))

    return True


if __name__ == '__main__':
    launch()

模拟获取参数的行为

替换ParserArgs的参数为mock对象

import argparse
import unittest
from unittest.mock import patch

from script import main as alg


class TestParserArgs(unittest.TestCase):

    def setUp(self) -> None:
        self.mock_args = argparse.Namespace(
            name='欢颜',
            datetime='2023-05-20 13:14:00',
            is_worktime=True
        )

    @patch('script.main.ParserArgs')
    def test_launch(self, mock_parser_args):
        parser_args_obj = mock_parser_args.return_value
        parser_args_obj.args = self.mock_args

        output = alg.launch()
        expect = True
        self.assertEqual(output, expect)


if __name__ == '__main__':
    unittest.main()

7、包的构建与分发

7.1 打包程序为可执行文件(EXE)

  可以使用pyinstaller将单文件应用或多文件应用,打包为exe可执行文件,需要编译作为程序入口的文件即可。

7.1.1 打包应用程序

pip3 install pyinstaller

# pyinstaller -F app.py

def main():
    print('程序开始执行')

if __name__ == '__main__':
    main()
7.2.2 Pyinstaller常用选项
-h帮助信息
-F产生单个的可执行文件
-D产生一个目录(包含多个文件)作为可执行程序
-a不包含 Unicode 字符集支持
-d产生 debug 版本的可执行文件
-w指定程序运行时不显示命令行窗口(仅对 Windows 有效)
-c指定使用命令行窗口运行程序(仅对 Windows 有效)
-o指定 spec 文件的生成目录。如果没有指定,则默认使用当前目录来生成 spec 文件
-p设置 Python 导入模块的路径
-n指定项目名字。如果省略该选项,那么第一个脚本的主文件名将作为 spec 的名字

7.2 使用setuptools构建包

7.2.1 创建合适的项目结构
# 一个最简单的项目结构
pkgs/
├── LICENSE       # 许可证文件
├── main.py       # 入口文件
├── app           # 存放子文件等
├── README.md     # 应用文档
└── setup.py      # 用于构建、安装和分发包的脚本文件
7.2.2 编写setup文件

一个最简单的setup示例

from setuptools import setup, find_packages

setup(
    name='PkgName',
    version='0.1',
    description='描述信息',
    author='Your Name',
    packages=find_packages(),
    install_requires=[
        # 依赖项列表
    ],
)
7.2.3 构建包

  在项目根目录中运行python setup.py sdist生成源分发包,也可以生成二进制分发包bdist_wheel。然后可以将包发布到Python包索引(PyPI),这需要在PyPI上创建一个帐户,并使用工具如twine来上传包(twine upload dist/*)。

7.2.4 分发包

  您可以通过以下方式分享生成的分发包,将其发布到代码托管平台如GitHub,上传至内部或外部的PyPI服务器,或者使用pip来进行安装包。

🧀 小结

  编写一个优雅的Python脚本关键在于遵循Python的最佳实践和编码规范(PEP8),以确保代码风格一致性和可读性。使用有意义的命名、拆分代码块成小函数、适度添加注释和文档、合理处理异常、充分利用Python的内置函数和模块、避免滥用全局变量、保持模块化和可重用性、采用测试驱动开发等方法,有助于编写出清晰、可维护、优雅的Python脚本,提高代码质量和可维护性。以上是笔者开发过程中经常使用的一些小Tips,在这里记录分享,当然,我没还可以使用pandas,numpy等工具处理数据,提高脚本执行效率等…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1035424.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenMesh 网格平滑

文章目录 一、简介二、相关参数二、实现代码三、实现效果参考资料一、简介 由于物理采样过程固有的局限性,三维扫描仪获得的网格通常是有噪声的。为了消除这种噪声,所谓的平滑算法被开发出来。这类方法有很多,OpenMesh主要为我们提供了两种平滑算法,一种是较为经典的Laplac…

dp训练题解

训练链接 CF101E 题目链接 点击打开链接 题目解法 朴素的 d p dp dp 很好写&#xff0c;但发现难以得到最优路径 考虑对于 ( x , y ) (x,y) (x,y) 的转移方式只有两种&#xff0c;可以想到用 b i t s e t bitset bitset 来维护转移&#xff0c;这样可以很节约空间 但我…

113双周赛

题目列表 2855. 使数组成为递增数组的最少右移次数 2856. 删除数对后的最小数组长度 2857. 统计距离为 k 的点对 2858. 可以到达每一个节点的最少边反转次数 一、使数组成为递增数组的最少右移次数 这题可以直接暴力求解&#xff0c;枚举出每种右移后的数组&#xff0c;将…

MySQL学习笔记4

客户端工具的使用&#xff1a; MySQL&#xff1a; mysql命令行工具&#xff0c;一般用来连接访问mysql的数据。 案例&#xff1a;使用mysql客户端工具连接服务器端&#xff08;用户名&#xff1a;root&#xff1b;密码&#xff1a;123456&#xff09;. [rootmysql-server ~]#…

不甘于被强势厂商捆绑,中国移动未来或自研5G基站

一直以来运营商被认为只是做服务&#xff0c;而设备等都是由设备商提供的&#xff0c;甚至由于如今的设备高度复杂&#xff0c;设备商已承包越来越多的基站运维工作&#xff0c;运营商的技术水平越来越低&#xff0c;不过随着中国移动发布5G射频芯片8676&#xff0c;似乎显示出…

centos7用docker安装WireGuard教程

1、 检查centos内核版本 uname -r2、升级内核 下载脚本上传到服务器运行脚本进行升级内核 链接&#xff1a;https://pan.baidu.com/s/1vYmqVy2St3nFnJWGPIwdOw 提取码&#xff1a;owac 3、安装WireGuard 方案一&#xff1a;使用脚本安装 执行第二步脚本进行安装#启动wg0wg…

CSRF攻击(跨站请求伪造)

1.CSRF原理 程序员开发的时候&#xff0c;未对相关页面进行token和referer判断&#xff0c;造成攻击者可构造自己的URL地址欺骗用户进行点击 漏洞分析&#xff08;低级可绕过&#xff09; 通过这个可以更改密码 改为了password 中级多了referer头可以绕过 源代码 多了一个refer…

简单好用的Python装饰器详解

装饰器&#xff08;Decorators&#xff09;是Python中一种强大而灵活的功能&#xff0c;用于修改或增强函数或类的行为。装饰器本质上是一个函数&#xff0c;它接受另一个函数或类作为参数&#xff0c;并返回一个新的函数或类。它们通常用于在不修改原始代码的情况下添加额外的…

linux服务器加固-密码验证设置

安全问题 安全控制点 风险分析 风险等级 标准要求 查看登录/etc/login.defs文件PASS_MAX_DAYS参数为99999&#xff0c;查看/etc/pam.d/system-auth文件&#xff0c;未对密码策略进行有效配置&#xff0c;如&#xff1a;密码更换周期&#xff0c;密码是否包括字母、数字与特…

基于SpringBoot的网上点餐系统

目录 前言 一、技术栈 二、系统功能介绍 用户功能模块 管理员功能模块 美食店功能模块 前台首页功能模块 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 系统管理也都将通过计算机进行整体智能化操作&#xff0c;对于网上点餐系统所牵扯的管理及数据保存…

【从0学习Solidity】 32. 代币水龙头

【从0学习Solidity】32. 代币水龙头 博主简介&#xff1a;不写代码没饭吃&#xff0c;一名全栈领域的创作者&#xff0c;专注于研究互联网产品的解决方案和技术。熟悉云原生、微服务架构&#xff0c;分享一些项目实战经验以及前沿技术的见解。关注我们的主页&#xff0c;探索全…

兴达易控EtherCAT转Modbus网关用Modbus Slave模拟从站配置案例

兴达易控EtherCAT到Modbus网关可以用作Modbus从站的配置。EtherCAT到Modbus网关允许Modbus协议转换为EtherCAT&#xff0c;实现不同通信系统之间的互操作性。通过配置从站到网关的Modbus&#xff0c;您可以访问和控制Modbus设备。同时&#xff0c;网关还可以扩展Modbus网络的范…

[C++基础]-继承

前言 作者&#xff1a;小蜗牛向前冲 名言&#xff1a;我可以接受失败&#xff0c;但我不能接受放弃 如果觉的博主的文章还不错的话&#xff0c;还请点赞&#xff0c;收藏&#xff0c;关注&#x1f440;支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 目录 一、模板的…

2023-9-23 最大不相交区间数量

题目链接&#xff1a;最大不相交区间数量 #include <iostream> #include <algorithm>using namespace std;const int N 100010;int n;struct Range {int l, r;bool operator< (const Range &W) const {return r < W.r;} }range[N];int main() {cin >…

Unity中的两种ScriptingBackend

一&#xff1a;前言 二&#xff1a;两种模式的介绍 ios&#xff1a;unity只有il2cpp模式的编译才支持64位系统&#xff0c;mono是不支持的&#xff0c;在快速开发阶段仍然支持Mono&#xff0c;但是不能再向Apple提交Mono(32位)的应用 苹果在2016年1月就要求所有新上架游戏必须支…

ASO优化之竞争对手的选择取决于什么

任何具有良好ASO策略的应用程序都是来自于良好的基准&#xff0c;不仅可以对市场有总体概述&#xff0c;还可以发现应用的增长潜力以及如何在不同的应用商店中获得知名度。在发布应用之前&#xff0c;需要制定ASO策略以获得搜索和浏览可见性&#xff0c;所以研究竞争对手是基础…

【小沐学NLP】关联规则分析Apriori算法(Mlxtend库,Python)

文章目录 1、简介2、Mlxtend库2.1 安装2.2 功能2.2.1 User Guide2.2.2 User Guide - data2.2.3 User Guide - frequent_patterns 2.3 入门示例 3、Apriori算法3.1 基本概念3.2 apriori3.2.1 示例 1 -- 生成频繁项集3.2.2 示例 2 -- 选择和筛选结果3.2.3 示例 3 -- 使用稀疏表示…

Klocwork 2023.2 windows

Klocwork 2023.2 windows 2692407267qq.com&#xff0c;更多内容请见http://user.qzone.qq.com/2692407267/

es6新语法特性+vue2的学习笔记

1. es6 ECMA的第6版&#xff0c;15年发布&#xff0c;规定新的语法特性 2. let声明变量 varlet声明的变量会越狱声明的变量有严格的作用域可以声明多次只能声明一次会变量提升(未声明的变量不会报错)不会变量提升 代码案例&#xff1a; <script>// {// var a 1;/…