像专业人士一样组织Python代码

像专业人士一样组织Python代码🐍📦

For every minute spent in organizing, an hour is earned.
by Benjamin Franklin(本杰明·富兰克林)

Python与c#或Java等语言不同,在这些语言中,它们强制让类以它们所在的文件命名。

到目前为止,Python是我接触过的最灵活的语言之一,任何过于灵活的东西都会增加错误决策的几率。

  • 你想把所有的项目类保存在一个main.py文件中吗?是的,它可以。
  • 您需要读取操作系统环境变量吗?就在那里读吧。
  • 你需要修改一个函数的行为吗?为什么不使用装饰器!?

许多容易实现的决策可能会适得其反,产生极其难以维护的代码。
如果你知道自己在做什么,这并不一定是坏事。

在本章中,我将向你介绍过去在不同的公司和许多不同的人工作时,对我有用的准则。

🌳组织你的Python项目

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

我建议你把所有的模块文件放在src文件夹里面,所有的测试都和它并排而居。

顶层项目

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

其中<module>是你的主要模块。如果有疑问,请考虑人们会pip install什么,以及你希望如何import module

通常情况下,它与顶级项目的名称相同。但这并不是一个规则。

🎯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的字母排序而使东西(module_a和module_b)分开是很令人厌烦的。

src 文件夹背后的主要原因是让活动的项目代码集中在一个目录中,而设置、CI/CD设置和项目元数据可以驻留在它的外部。

这样做的唯一缺点是不能开箱即用地在python代码中import module_a。我们需要将项目安装在此资源库下。我们稍后将在本章中讨论如何解决这个问题。

🏷️ 如何命名文件

规则 1:没有文件
首先,在 Python 中没有 "文件 "这种东西,我注意到这是让初学者感到困惑的主要原因。

如果你在一个包含任何__init__.py的目录里面,那就是一个由模块组成的目录,而不是文件。

把每个模块看作是一个命名空间。

我指的是命名空间,因为你不能确定它们是否有很多函数、类,或者只是常量。它可以有几乎所有的东西,或者只是一堆一些。

规则2:根据需要把东西放在一起

在一个模块内有几个类是可以的,你应该这样做。(当类与模块相关时,显然。)

只有当你的模块变得太大,或处理不同的关注点时,才将其分解。

通常情况下,人们认为这是一种不好的做法,因为其他语言的一些经验,它们执行的是相反的方式(例如Java和C#)。

规则3:默认情况下给出复数的名字
作为一个经验法则,用复数来命名你的模块,并以业务背景来命名。

不过这条规则也有例外!模块可以被命名为coremain.py,以及类似的名字,以代表一个单一的东西。使用你的判断力,如果有疑问,坚持使用复数规则。

🔎命名模块时的现实生活例子

我将分享一个我建立的Google Maps Crawler项目,作为一个例子。

这个项目负责使用Selenium从Google Maps抓取数据并输出(如果好奇的话,可以在这里阅读更多内容)。

这是目前的项目树概述,包括了3号规则的例外情况。

gmaps_crawler
├── src
│   └── gmaps_crawler
│        ├── __init__.py
│        ├── config.py 👈 (Singular)
│        ├── drivers.py
│        ├── entities.py
│        ├── exceptions.py
│        ├── facades.py
│        ├── main.py  👈 (Singular)
│        └── 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中可能有一个或多个exception类,等等。

拥有复数模块的好处在于:

  • 它们不会太小(例如,每个类有一个)
  • 如果需要,你可以在任何时候将其分解成更小的模块
  • 它们给你一种强烈的感觉,让你知道里面可能存在的东西

🔖 命名类、函数和变量

有些人声称给事物命名是很难的。当你定义一些准则时,它就不那么难了。

👊 函数和方法应该是动词

函数和方法代表一个动作或可操作的东西。

有些东西 “不是”。一些东西正在 “发生”。

行动是由动词明确说明的。

从我以前做过的真实项目中举几个好例子。

def get_orders():
    ...

def acknowledge_event():
    ...

def get_delivery_information():
    ...

def publish():
    ...

一些不好的例子:

def email_send():
    ...

def api_call():
   ...

def specific_stuff():
   ...

他们有点不清楚他们是否返回一个对象让我执行API调用,或者是否真的发送了电子邮件,例如。

我可以想象这样的场景:

email_send.title = "title"
email_send.dispatch()

这条规则的例外情况只是少数,但它们存在。

  • 在你的应用程序的主入口处创建一个main()函数来被调用是跳过这条规则的一个好理由。
  • 使用@property将类方法视为属性也是有效的。

🐶 变量和常量应该是名词

应该永远是名词,而不是动词(这澄清了函数之间的区别)。

好的例子:

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

坏的例子:

fly = Plane()
get_customer_id = 5
COMPARE_KEY = "abc"

如果你的变量/常量是一个列表或集合,那就用复数吧!

planes: list[Plane] = [Plane()] # 👈 Even if it contains only one item
customer_ids: set[int] = {5, 12, 22}
KEY_MAP: dict[str, str] = {"123": "abc"} # 👈 Dicts are kept singular

🏛️类应该是自解释的,但后缀也可以

更倾向于使用自解释的名字的类。像ServiceStrategyMiddleware这样的后缀是可以的,但只有在极其必要的情况下才能使其目的明确。

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

代表实体的类

代表商业环境中的事物的类应该按原样命名(名词!)。像OrderSaleStoreRestaurant等等。

后缀的使用实例

让我们考虑一下,你想创建一个负责发送电子邮件的类。如果你仅仅把它命名为"Email",它的目的就不明确了。

有人可能认为它可能代表一个实体,例如:

email = Email() # inferred usage example
email.title = "Title"
email.body = create_body()
email.send_to = "guilatrova.dev"

send_email(email)

你应该把它命名为"EmailSender"或"EmailService"。

🐪 命名惯例

默认情况下遵循这些命名惯例。
在这里插入图片描述

⚠️ 关于"““私有””"方法的声明

一些人发现,如果你有__method(self)(任何以两个下划线开头的方法),Python不会让外面的类/方法正常调用它,这导致他们认为这很好。

如果你像我一样来自C#环境,你不能保护一个方法,这听起来可能很奇怪。

但是Guido(Python的创造者)在这背后有一个很好的理由:

“We’re all consenting adults here”

这意味着,如果你知道你不应该调用一个方法,那么你就不应该调用,除非你知道你在做什么。

毕竟,如果你真的决定要调用那个方法,你会做一些肮脏的事情来使之发生(在C#中被称为 “反射”)。

用一个首字母下划线来标记你的私有方法/函数,说明它只用于私有用途,并与之共存。

↪️ 什么时候在Python中创建一个函数或一个类?

这是一个我收到过几次的常见问题。

如果你遵循上述建议,你就会有清晰的模块,清晰的模块是组织函数的有效方式。

from gmaps_crawler import storages

storages.get_storage()  # 👈 Similar to a class, except it's not instantied and has a plural name
storages.save_to_storage()  # 👈 Potential function inside module

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

关于对不同子集的函数进行分组的例子
考虑同样的storages模块,有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")
        ...

这里有一个经验法则:

  • 总是从函数开始
  • 一旦你觉得你可以对不同的函数子集进行分组,就增长到类。

🚪 创建模块和入口点

每个应用程序都有一个入口点。

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

每当你创建一个入口点时,确保添加一个条件以确保它被执行而不是被导入

def execute_main():
    ...


if __name__ == "__main__":  # 👈 Add this condition
    execute_main()

**通过这样做,你确保任何导入都不会意外地触发你的代码。**除非它被明确地执行。

为模块定义main

你可能已经注意到了一些可以通过传递-m来调用的python包,比如:

python -m pytest
python -m tryceratops
python -m faust
python -m flake8
python -m black

这样的包几乎被当作普通的命令,因为你也可以把它们当作:

pytest
tryceratops
faust
flake8
black

为了实现这一点,你需要在你的main模块中指定一个单独的__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的方式运行你的项目。


原文链接:Organize Python code like a PRO 🐍📦

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值