日常生活的交流与学习

首页 新随笔 联系 管理

组织Python代码技巧

英文版链接

一.构建Python项目

首先关注目录结构、文件命名和模块组织。

建议把所有的模块都放在src目录下,所有测试都和它并排。 python项目的组织结构 --- 建议把所有的模块都放在src目录下,所有测试都和它并排。

如下图:

<project>
├── src
│   ├── <module>/*
│   │    ├── __init__.py
│   │    └── many_files.py
│   │
│   └── tests/*
│        └── many_tests.py
│
├── .gitignore
├── pyproject.toml
└── README.md

<module> 是您的主要模块。

对此有疑问,可以想象一下使用pip安装模块的时候包名如 pip install pymsql
其中mysql就是包名,再想象一下我们导入模块 import pymysql 其中pymysql也是包名。

通常情况下,它与顶级项目的名称相同,但是你也可以不遵守。

1.设置src目录的原因

有很多项目有不同的做法,有些项目没有src目录,所有项目模块都分散开,显得项目很杂乱不易于管理。
如下案例:


non_recommended_project
├── <module_a>/*
│     ├── __init__.py
│     └── many_files.py
│
├── .gitignore
│
├── tests/*
│    └── many_tests.py
│
├── pyproject.toml
│
├── <module_b>/*
│     ├── __init__.py
│     └── many_files.py
│
└── README.md

由IDE会对模块名称进行排序,就会导致我们查看时很麻烦。
将模块设置在src内部的主要原因是将所有项目代码集中到一个目录中,CI/CD设置、项目元数据可以放在项目模块之外。

这样做唯一的缺点是你不能直接在Python代码中直接import module_a,我们需要将项目设置为安装在这个资源库下。
我们将在下面解决这个问题。

2.如何命名文件

2.1.无'文件'

首先,在Python中没有"文件"这一概念,这就是让初学者感到困惑的主要原因。

如果任意目录包含`__init__.py`,那他就是一个由模块组成的目录而不是文件。

如果任意目录包含__init__.py,那他就是一个由模块组成的目录而不是文件。

将每个模块视为一个命名空间

我指的是命名空间,因为你不能确定他们是否有很多函数、类,或者只是常量。它可以包含所有这些,也可以只是一些。

2.2.根据需要将事务放在一起

应该在一个模块中存放多个类(必须这些类与模块相关)。

只有当一个模块太大了或者需要处理不同问题的时候,我们才需要考虑是不是要将其分割。

受其他开发语言(eg:java,c#)的开发经验影响,有很多人认为这是一种不好的做法,但是在Python我推荐这样做

2.3.模块命名尽量命名为复数

根据经验,尽量结合业务背景用复数来给模块进行命名。

不过这条规则也有例外!模块可以命名为 core、main.py 等,以表示一个单一的事物。
根据自身项目进行判断,如果不知道如何命名可以直接用复数命名。

如何给python中的模块命令 --- 模块命名尽量命名为复数 不过这条规则也有例外!模块可以命名为 core、main.py 等,以表示一个单一的事物。

3.命名模块的真实案例

Google Maps Crawler 项目作为示例。

该项目负责使用Selenium从Google地图中抓取数据并输出

请在此处阅读更多信息

以下是该项目的目录(符合以上三条规则):

gmaps_crawler
├── src
│   └── gmaps_crawler
│        ├── __init__.py
│        ├── config.py 👈 (单数)
│        ├── drivers.py
│        ├── entities.py
│        ├── exceptions.py
│        ├── facades.py
│        ├── main.py  👈 (单数)
│        └── storages.py
│
├── .gitignore
├── pyproject.toml
└── README.md

导入模块也是非常简单:

from gmaps_crawler.storages import get_storage
from gmaps_crawler.entities import Place
from gmaps_crawler.exceptions import CantEmitPlace

我明白我可能在exceptions中有一个或多个异常类。

拥有多个模块的好处在于:

  • 可以丰富模块功能
  • 如果需要,您可以随时将其分解成更小的模块
  • 他可以让您想了解模块内部存在的功能

二、命名类,函数,变量

有些人觉得命名很难。当您定义一些准则时,它会变得不那么困难。

1.函数和方法应该是动词

函数和方法的命令规则 ---

函数和方法应该是动词

函数和方法代表一个动作或可操作的东西,可以是未发生的动作也可以是正在发生的动作。
所以用动词描述更加准确。

如下:
建议的命名:

def get_orders():
    ...

def acknowledge_event():
    ...

def get_delivery_information():
    ...

def publish():
    ...

不建议的命名:

def email_send(): # send_email() 
    ...

def api_call(): # call_api()
   ...

def specific_stuff():  
   ...

上面的例子不清楚函数是否返回一个对象允许我对API调用,或者它是否实际发送了电子邮件。

我们可以想象下列的场景:

# 误导性函数名称的例子
email_send.title = "title"
email_send.dispatch()

通常都会使用这条规则作为函数和方法的命名,但是也有例外如:

  • 创建一个要在应用程序的主入口点调用的 main() 函数
  • 使用@property 将类方法视为属性

2.变量和常量应该为名词

变量和常量的命令规则 ---

变量和常量应该为名词

必须是名词,绝对不能使用动词(这是和函数/方法之间的区别)。

如下:推荐的命名

plane = Plane()
customer_id = 5
KEY_COMPARISON = "abc"

不推荐命名:

fly = Plane()
get_customer_id = 5
COMPARE_KEY = "abc"
如果您的变量/常量是列表或集合,请将其设为复数! python的列表和集合怎么命名 --- 如果您的变量/常量是列表或集合,请将其设为复数!
planes: list[Plane] = [Plane()] # 👈 即使他只有一项
customer_ids: set[int] = {5, 12, 22}
KEY_MAP: dict[str, str] = {"123": "abc"} # 👈 字典保持单数

3.类名应该见名知意,后缀除外

建议类命名的时候尽量做到见名知意,在一些必要的时候加上 Service,Strategy,Middkeware这样的后缀也是可以的。

类名的命名规则,以及后缀的使用 ---
  1. 类的命名尽量使用单数

  2. 建议类命名的时候尽量做到见名知意,在一些必要的时候加上 Service,Strategy,Middkeware这样的后缀也是可以的。

**类的命名尽量使用单数**

复数总让人想起集合。(例如,我读到以orders命名的类,我会认为它是列表或者可迭代函数),
所以要提醒自己,一旦一个类被实例化,它就成为一个单一的对象。

类代表的是实体

代表商业环境中事物的类应该以原样命名(名词).如:OrderSaleStoreRestaurant等等

后准用法示例

假设您想创建一个负责发送电子邮件的类。如果你将类命名为Email,这个类的功能就不是很清晰。

有的可能认为其实一个实体。

例如:

email = Email() # 推断的用法示例
email.title = "Title"
email.body = create_body()
email.send_to = "guilatrova.dev"

send_email(email)

你应该将其命名为:EmailSenderEmailService

三、大小写约定

默认情况下遵守下列命名约定

类型 公开 内部
Packages (包目录) lower_with_under(小驼峰下划线分割) -
Modules (模块文件) lower_with_under.py(小驼峰下划线分割) -
Classes(类) CapWords(大驼峰) -
Functions and methods( 函数方法) lower_with_under()(小驼峰下划线分割) _lower_with_under()(小驼峰下划线分割)
Constants(常量) ALL_CAPS_UNDER(大驼峰下划线分割) _ALL_CAPS_UNDER(大驼峰下划线分割)

四、关于"私有"化方法的声明

有些认为__method(self)(任何以两个下划线开头的方法)Python将不允许外部类/方法正常调用它,这样很好。

如果你了解过其他语言例如c#,那么这样做你会感到十分奇怪。
但Guido(Python 的创造者)这样解释道:

"We're all consenting adults here"  # 我们都是成年人

这意味着如果你知道你不应该调用一个方法,那么你不应该调用,除非你知道你在做什么。
毕竟,如果您真的决定调用该方法,您将要做一些复杂的事情来实现它(在 C# 中称为“反射”)。
用一个初始下划线标记您的私有方法/函数,以声明它仅供私人使用并接受它。

五、何时在Python中创建函数或类?

如果您遵循上述建议,您将拥有清晰的模块,而清晰的模块是组织功能的有效方式:

from gmaps_crawler import storages

storages.get_storage()  # 👈 类似于一个类,除了它没有实例化并且有一个复数名称
storages.save_to_storage()  # 👈 模块内的潜在功能

有时,你可以在一个模块内确定函数的子集。当这种情况发生时,一个类会更有意义。

对不同函数子集进行分组的示例

考虑具有 4 个功能的相同存储模块:

def format_for_debug(some_data):
    ...

def save_debug(some_data):
    """Prints in the screen"""
    formatted_data = format_for_debug(some_data)
    print(formatted_data)


def create_s3(bucket):
    """Create s3 bucket if it doesn't exists"""
    ...

def save_s3(some_data):
    s3 = create_s3("bucket_name")
    ...

S3 是一种云存储,用于存储亚马逊 (AWS) 提供的任何类型的数据。这就像软件的 Google Drive。

我们可以这样认为:

  • 开发者可以将数据保存在DEBUG模式(即只在屏幕上打印)或S3模式(即在云上存储数据)。
  • save_debug使用format_for_debug函数。
  • save_s3使用create_s3函数

我可以看到两组函数,没有理由把它们放在不同的模块中,因为它们看起来很小,因此我喜欢把它们定义为类。

class DebugStorage:
    def format_for_debug(self, some_data):
        ...

    def save_debug(self, some_data):
        """Prints in the screen"""
        formatted_data = self.format_for_debug(some_data)
        print(formatted_data)


class S3Storage:
    def create_s3(self, bucket):
        """Create s3 bucket if it doesn't exists"""
        ...

    def save_s3(self, some_data):
        s3 = self.create_s3("bucket_name")
        ...

规则:

1. 始终从函数开始 2. 一旦你觉得你可以将不同的功能子集分组,就封装成类

六、创建模块和入口

每一个程序都应该有一个入口。
这意味着有一个单一的模块(又称文件)来运行你的应用程序。
它可以是一个单一的脚本,也可以是一个大的模块。

每当您创建入口时,请务必添加一个条件以确保他在导入前就能够执行:

def execute_main():
    ...


if __name__ == "__main__":  # 👈 添加此条件
    execute_main()

通过这样做,您可以确保任何导入都不会意外触发您的代码。除非明确执行。

为模块定义main

一些 python 包可以通过传递 -m 来调用,比如:

执行python包
python -m pytest
python -m tryceratops
python -m faust
python -m flake8
python -m black

这样的软件包几乎被当作普通命令对待,因为你也可以把它们当作普通命令来运行。

pytest
tryceratops
faust
flake8
black

为此,您需要在主模块中指定一个 __main__.py 文件:

<project>
├── src
│   ├── example_module 👈 Main module
│   │    ├── __init__.py
│   │    ├── __main__.py 👈 Add it here
│   │    └── many_files.py
│   │
│   └── tests/*
│        └── many_tests.py
│
├── .gitignore
├── pyproject.toml
└── README.md

不要忘记您仍然需要在 __main__.py 文件中包含检查 __name__ == "main" 。

安装模块时,可以将项目作为 python -m example_module 运行。

posted on 2023-06-24 17:35  lazycookie  阅读(23)  评论(0编辑  收藏  举报