Python 高质量类编写指南

news2025/1/15 6:46:40

原文:https://www.youtube.com/watch?v=lX9UQp2NwTk
代码:https://github.com/ArjanCodes/examples/tree/main/2023/classguide

Python 高质量类编写指南

在这里插入图片描述

我们将通过一些方法增加类的可读性和易用性。

  1. 通过(按照属性或行为)拆分类,保持类精简
  2. 通过__str__ , @property等使得类容易访问。
  3. 使用依赖注入(dependency injection) 减少耦合。
  4. 只在必要时使用类。
  5. 适度封装,通过__<name> 约定私有属性。

开始时的Person类,包含非常多的属性和方法,阅读、修改和使用时都比较不方便。

from dataclasses import dataclass
from email.message import EmailMessage
from smtplib import SMTP_SSL

SMTP_SERVER = "smtp.gmail.com"
PORT = 465
EMAIL = "hi@arjancodes.com"
PASSWORD = "password"

# todo 1. 精简类
@dataclass
class Person:
    name: str
    age: int
    address_line_1: str
    address_line_2: str
    city: str
    country: str
    postal_code: str
    email: str
    phone_number: str
    gender: str
    height: float
    weight: float
    blood_type: str
    eye_color: str
    hair_color: str

    def split_name(self) -> tuple[str, str]:
        first_name, last_name = self.name.split(" ")
        return first_name, last_name

    def get_full_address(self) -> str:
        return f"{self.address_line_1}, {self.address_line_2}, {self.city}, {self.country}, {self.postal_code}"

    def get_bmi(self) -> float:
        return self.weight / (self.height**2)

    def get_bmi_category(self) -> str:
        if self.get_bmi() < 18.5:
            return "Underweight"
        elif self.get_bmi() < 25:
            return "Normal"
        elif self.get_bmi() < 30:
            return "Overweight"
        else:
            return "Obese"

    def update_email(self, email: str) -> None:
        self.email = email
        # send email to the new address
        msg = EmailMessage()  #  todo 3. 通过依赖注入连少耦合。
        msg.set_content(
            "Your email has been updated. If this was not you, you have a problem."
        )
        msg["Subject"] = "Your email has been updated."
        msg["To"] = self.email

        with SMTP_SSL(SMTP_SERVER, PORT) as server:
            # server.login(EMAIL, PASSWORD)
            # server.send_message(msg, EMAIL)
            pass
        print("Email sent successfully!")
	# todo 2. 增加@propery 和 __str__ 使得类容易访问
	
def main() -> None:
    # create a person
    person = Person(
        name="John Doe",
        age=30,
        address_line_1="123 Main St",
        address_line_2="Apt 1",
        city="New York",
        country="USA",
        postal_code="12345",
        email="johndoe@gmail.com",
        phone_number="123-456-7890",
        gender="Male",
        height=1.8,
        weight=80,
        blood_type="A+",
        eye_color="Brown",
        hair_color="Black",
    )

    # compute the BMI
    bmi = person.get_bmi()
    print(f"Your BMI is {bmi:.2f}")
    print(f"Your BMI category is {person.get_bmi_category()}")

    # update the email address
    person.update_email("johndoe@outlook.com")


if __name__ == "__main__":
    main()

1. 保持类精简

保持类精简,如果你发现类很复杂,考虑将类拆分。有两种简单的拆分方式:

  • 根据属性拆分(专注数据)
  • 根据方法拆分(专注行为)

我们根据属性,从Person类拆分出StatsAddress两个数据类。

from dataclasses import dataclass
from functools import cached_property

from email_tools.service import EmailService

SMTP_SERVER = "smtp.gmail.com"
PORT = 465
EMAIL = "hi@arjancodes.com"
PASSWORD = "password"


@dataclass
class Stats:
    age: int
    gender: str
    height: float
    weight: float
    blood_type: str
    eye_color: str
    hair_color: str

    @cached_property
    def bmi(self) -> float:
        return self.weight / (self.height**2)

    def get_bmi_category(self) -> str:
        if self.bmi < 18.5:
            return "Underweight"
        elif self.bmi < 25:
            return "Normal"
        elif self.bmi < 30:
            return "Overweight"
        else:
            return "Obese"


@dataclass
class Address:
    address_line_1: str
    address_line_2: str
    city: str
    country: str
    postal_code: str

    def get_full_address(self) -> str:
        return f"{self.address_line_1}, {self.address_line_2}, {self.city}, {self.country}, {self.postal_code}"


@dataclass
class Person:
    name: str
    address: Address
    email: str
    phone_number: str
    stats: Stats

    def split_name(self) -> tuple[str, str]:
        first_name, last_name = self.name.split(" ")
        return first_name, last_name

    def update_email(self, email: str) -> None:
        self.email = email
        # send email to the new address
        email_service = EmailService(
            smtp_server=SMTP_SERVER,
            port=PORT,
            email=EMAIL,
            password=PASSWORD,
        )
        email_service.send_message(
            to_email=self.email,
            subject="Your email has been updated.",
            body="Your email has been updated. If this was not you, you have a problem.",
        )


def main() -> None:
    # create a person
    address = Address(
        address_line_1="123 Main St",
        address_line_2="Apt 1",
        city="New York",
        country="USA",
        postal_code="12345",
    )
    stats = Stats(
        age=30,
        gender="Male",
        height=1.8,
        weight=80,
        blood_type="A+",
        eye_color="Brown",
        hair_color="Black",
    )
    person = Person(
        name="John Doe",
        email="johndoe@gmail.com",
        phone_number="123-456-7890",
        address=address,
        stats=stats,
    )

    # compute the BMI
    bmi = stats.bmi
    print(f"Your BMI is {bmi:.2f}")

    # update the email address
    person.update_email("johndoe@outlook.com")


if __name__ == "__main__":
    main()
# email_tools/service.py

import smtplib
from email.message import EmailMessage


class EmailService:
    def __init__(self, smtp_server: str, port: int, email: str, password: str) -> None:
        self.smtp_server = smtp_server
        self.port = port
        self.email = email
        self.password = password

    def send_message(self, to_email: str, subject: str, body: str) -> None:
        msg = EmailMessage()
        msg.set_content(body)
        msg["Subject"] = subject
        msg["To"] = to_email

        with smtplib.SMTP_SSL(self.smtp_server, self.port) as server:
            # server.login(self.email, self.password)
            # server.send_message(msg, self.email)
            pass
        print("Email sent successfully!")

2. 使得类易用

通过__str__ , @property等使得类容易访问。

from dataclasses import dataclass
from functools import lru_cache

from email_tools.service import EmailService

SMTP_SERVER = "smtp.gmail.com"
PORT = 465
EMAIL = "hi@arjancodes.com"
PASSWORD = "password"


@lru_cache
def bmi(weight: float, height: float) -> float:
    return weight / (height**2)


def bmi_category(bmi_value: float) -> str:
    if bmi_value < 18.5:
        return "Underweight"
    elif bmi_value < 25:
        return "Normal"
    elif bmi_value < 30:
        return "Overweight"
    else:
        return "Obese"


@dataclass
class Stats:
    age: int
    gender: str
    height: float
    weight: float
    blood_type: str
    eye_color: str
    hair_color: str


@dataclass
class Address:
    address_line_1: str
    address_line_2: str
    city: str
    country: str
    postal_code: str
	# !! 
    def __str__(self) -> str:
        return f"{self.address_line_1}, {self.address_line_2}, {self.city}, {self.country}, {self.postal_code}"


@dataclass
class Person:
    name: str
    address: Address
    email: str
    phone_number: str
    stats: Stats

    def split_name(self) -> tuple[str, str]:
        first_name, last_name = self.name.split(" ")
        return first_name, last_name

    def update_email(self, email: str) -> None:
        self.email = email
        # send email to the new address
        # send email to the new address
        email_service = EmailService(
            smtp_server=SMTP_SERVER,
            port=PORT,
            email=EMAIL,
            password=PASSWORD,
        )
        email_service.send_message(
            to_email=self.email,
            subject="Your email has been updated.",
            body="Your email has been updated. If this was not you, you have a problem.",
        )


def main() -> None:
    # create a person
    address = Address(
        address_line_1="123 Main St",
        address_line_2="Apt 1",
        city="New York",
        country="USA",
        postal_code="12345",
    )
    stats = Stats(
        age=30,
        gender="Male",
        height=1.8,
        weight=80,
        blood_type="A+",
        eye_color="Brown",
        hair_color="Black",
    )
    person = Person(
        name="John Doe",
        email="johndoe@gmail.com",
        phone_number="123-456-7890",
        address=address,
        stats=stats,
    )

    # compute the BMI
    bmi_value = bmi(stats.weight, stats.height)
    print(f"Your BMI is {bmi_value:.2f}")
    print(f"Your BMI category is {bmi_category(bmi_value)}")

    # update the email address
    person.update_email("johndoe@outlook.com")


if __name__ == "__main__":
    main()

3. 使用依赖注入(dependency injection)

from dataclasses import dataclass
from functools import lru_cache
from typing import Protocol

from email_tools.service import EmailService

SMTP_SERVER = "smtp.gmail.com"
PORT = 465
EMAIL = "hi@arjancodes.com"
PASSWORD = "password"


class EmailSender(Protocol):
    def send_message(self, to_email: str, subject: str, body: str) -> None: ...


@lru_cache
def bmi(weight: float, height: float) -> float:
    return weight / (height**2)


def bmi_category(bmi_value: float) -> str:
    if bmi_value < 18.5:
        return "Underweight"
    elif bmi_value < 25:
        return "Normal"
    elif bmi_value < 30:
        return "Overweight"
    else:
        return "Obese"


@dataclass
class Stats:
    age: int
    gender: str
    height: float
    weight: float
    blood_type: str
    eye_color: str
    hair_color: str


@dataclass
class Address:
    address_line_1: str
    address_line_2: str
    city: str
    country: str
    postal_code: str

    def __str__(self) -> str:
        return f"{self.address_line_1}, {self.address_line_2}, {self.city}, {self.country}, {self.postal_code}"


@dataclass
class Person:
    name: str
    address: Address
    email: str
    phone_number: str
    stats: Stats

    def split_name(self) -> tuple[str, str]:
        first_name, last_name = self.name.split(" ")
        return first_name, last_name
	
	# 依赖注入
    def update_email(self, email: str, service: EmailSender) -> None:
        self.email = email
        service.send_message(
            to_email=self.email,
            subject="Your email has been updated.",
            body="Your email has been updated. If this was not you, you have a problem.",
        )


def main() -> None:
    # create a person
    address = Address(
        address_line_1="123 Main St",
        address_line_2="Apt 1",
        city="New York",
        country="USA",
        postal_code="12345",
    )
    stats = Stats(
        age=30,
        gender="Male",
        height=1.8,
        weight=80,
        blood_type="A+",
        eye_color="Brown",
        hair_color="Black",
    )
    person = Person(
        name="John Doe",
        email="johndoe@gmail.com",
        phone_number="123-456-7890",
        address=address,
        stats=stats,
    )
    print(address)

    # compute the BMI
    bmi_value = bmi(stats.weight, stats.height)
    print(f"Your BMI is {bmi_value:.2f}")
    print(f"Your BMI category is {bmi_category(bmi_value)}")

    # update the email address
    service = EmailService(
        smtp_server=SMTP_SERVER,
        port=PORT,
        email=EMAIL,
        password=PASSWORD,
    )
    person.update_email("johndoe@outlook.com", service)


if __name__ == "__main__":
    main()

4. 只在必要时使用类

如果你只是需要一个方法,就不要创建类。

# email_tools.service_v2

from email.message import EmailMessage
from smtplib import SMTP_SSL


def create_email_message(to_email: str, subject: str, body: str) -> EmailMessage:
    msg = EmailMessage()
    msg.set_content(body)
    msg["Subject"] = subject
    msg["To"] = to_email
    return msg


def send_email(
    smtp_server: str,
    port: int,
    email: str,
    password: str,
    to_email: str,
    subject: str,
    body: str,
) -> None:
    msg = create_email_message(to_email, subject, body)
    with SMTP_SSL(smtp_server, port) as server:
        # server.login(email, password)
        # server.send_message(msg, email)
        print("Email sent successfully!")
from dataclasses import dataclass
from functools import lru_cache, partial
from typing import Protocol

from email_tools.service_v2 import send_email

SMTP_SERVER = "smtp.gmail.com"
PORT = 465
EMAIL = "hi@arjancodes.com"
PASSWORD = "password"

# 参数类型 typing ...
class EmailSender(Protocol):
    def __call__(self, to_email: str, subject: str, body: str) -> None: ...


@lru_cache
def bmi(weight: float, height: float) -> float:
    return weight / (height**2)


def bmi_category(bmi_value: float) -> str:
    if bmi_value < 18.5:
        return "Underweight"
    elif bmi_value < 25:
        return "Normal"
    elif bmi_value < 30:
        return "Overweight"
    else:
        return "Obese"


@dataclass
class Stats:
    age: int
    gender: str
    height: float
    weight: float
    blood_type: str
    eye_color: str
    hair_color: str


@dataclass
class Address:
    address_line_1: str
    address_line_2: str
    city: str
    country: str
    postal_code: str

    def __str__(self) -> str:
        return f"{self.address_line_1}, {self.address_line_2}, {self.city}, {self.country}, {self.postal_code}"


@dataclass
class Person:
    name: str
    address: Address
    email: str
    phone_number: str
    stats: Stats

    def split_name(self) -> tuple[str, str]:
        first_name, last_name = self.name.split(" ")
        return first_name, last_name

    def update_email(self, email: str, send_message: EmailSender) -> None:
        self.email = email
        send_message(
            to_email=email,
            subject="Your email has been updated.",
            body="Your email has been updated. If this was not you, you have a problem.",
        )


def main() -> None:
    # create a person
    address = Address(
        address_line_1="123 Main St",
        address_line_2="Apt 1",
        city="New York",
        country="USA",
        postal_code="12345",
    )
    stats = Stats(
        age=30,
        gender="Male",
        height=1.8,
        weight=80,
        blood_type="A+",
        eye_color="Brown",
        hair_color="Black",
    )
    person = Person(
        name="John Doe",
        email="johndoe@gmail.com",
        phone_number="123-456-7890",
        address=address,
        stats=stats,
    )
    print(address)

    # compute the BMI
    bmi_value = bmi(stats.weight, stats.height)
    print(f"Your BMI is {bmi_value:.2f}")
    print(f"Your BMI category is {bmi_category(bmi_value)}")

    # update the email address
    send_message = partial(
        send_email, smtp_server=SMTP_SERVER, port=PORT, email=EMAIL, password=PASSWORD
    )
    person.update_email("johndoe@outlook.com", send_message)


if __name__ == "__main__":
    main()

5. 使用封装

尽管Python没有私有属性,但是可以通过__<name> 约定私有属性。

class Person:
    def __init__(self, name: str, age: int, ssn: str):
        self.name = name
        self.age = age
        self.__ssn = ssn  # Private attribute

    # Public method
    def display_info(self) -> None:
        print(f"Name: {self.name}")
        print(f"Age: {self.age}")
        print(f"SSN: {self.ssn}")

    @property
    def ssn(self) -> str:
        masked_ssn = "XXX-XX-" + self.__ssn[-4:]
        return masked_ssn


def main() -> None:
    # Creating an instance of the Person class
    person1 = Person("John Doe", 30, "123-45-6789")

    # Accessing public method
    person1.display_info()

    # Output:
    # Name: John Doe
    # Age: 30
    # SSN: XXX-XX-6789

    # Accessing private attribute or method directly will raise an AttributeError
    # print(person1.__ssn)  # This will raise an AttributeError
    # print(person1._Person__ssn)  # This will work so it's not truly private


if __name__ == "__main__":
    main()

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

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

相关文章

前端实现将二进制文件流,并下载为excel文件

目录 一、关于二进制流二、项目实践三、常见问题及解决 一、关于二进制流 含义&#xff1a;二进制流是一种计算机文件格式&#xff0c;它的数据以二进制形式存储&#xff0c;与文本文件不同。 二进制文件可以包含任意类型的数据&#xff0c;例如&#xff1a;图像、音频、视频…

Linux thermal框架介绍

RK3568温控 cat /sys/class/thermal/thermal_zone0/temp cat /sys/class/thermal/thermal_zone1/temp cat /sys/class/thermal/cooling_device0/cur_state cat /sys/class/thermal/cooling_device1/cur_state cat /sys/class/thermal/cooling_device2/cur_state thermal_zone…

文件File类的学习

File类 File类创建File实例创建文件删除文件创建目录 Reader小结 File类 在java中,通过java.io.File类来对一个文件进行抽象的描述. 下面我们来看看File类的构造方法:签名说明File(File parent, String child)根据父目录孩子文件路径,创建出一个新的File实例File(String pathn…

web--crlf注入,url重定向,web资源处理

crlf漏洞 正常的数据包 更改过 就变成这样了 配合xss 然后那个xss脚本就会被启用 crlffuzz url重定向 后面有url地址 改成baidu.com然后再访问&#xff0c;他就会自动访问baidu 实例 web资源处理 对于一张图片 当我们这样 加载的时候&#xff0c;会 无限解压缩包 这是一个…

vue快速入门(三十四)组件data定义方法

注释很详细&#xff0c;直接上代码 上一篇 新增内容 数据绑定方法照常数据定义方法需要作为函数返回值 源码 MyTest.vue <template><div><h1>我的功德&#xff1a;{{merits}} </h1><button click"meritsnum1">功德加一</button>…

什么是用户体验(UX)文案,为什么它很重要?

网上购物如今比以往任何时候都更加相关。所以我们将以此为例说明什么是用户体验&#xff08;UX&#xff09;文案&#xff0c;以及为什么它很重要。 假设你去了一个在线商店。你需要执行一系列操作&#xff1a; 找到合适的部分选择你感兴趣的产品弄清楚它们是什么&#xff0c;…

(超级详细)JAVA之Stream流分析-------持续更新喔!!!

学习目标&#xff1a; 掌握 Java Stream流的相关api 掌握 Java Stream流的基本实现 掌握 java Stream流的使用场景 代码已经整理上传到了gitee中&#xff0c;有需要的小伙伴可以取查看一下源码点个小心心喔 大家也可以帮我提交一点案例喔&#xff01;&#xff01;&#xff01;&…

java:观察者模式

java&#xff1a;观察者模式 1 前言 观察者模式&#xff0c;又被称为发布-订阅&#xff08;Publish/Subscribe&#xff09;模式&#xff0c;他定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时&#xff0c;会通知所…

本地部署Docker容器可视化图形管理工具DockerUI并实现无公网IP远程访问——“cpolar内网穿透”

文章目录 前言1. 安装部署DockerUI2. 安装cpolar内网穿透3. 配置DockerUI公网访问地址4. 公网远程访问DockerUI5. 固定DockerUI公网地址 前言 DockerUI是一个docker容器镜像的可视化图形化管理工具。DockerUI可以用来轻松构建、管理和维护docker环境。它是完全开源且免费的。基…

从0到1带你玩转pandas

学习 pandas 的过程可以分为几个阶段&#xff0c;每个阶段都围绕着不同的核心技能和概念。下面是一个为初学者设计的学习大纲&#xff1a; 一. 基础介绍 学习如何安装和设置 pandas 以及了解它的基本概念是开始使用 pandas 进行数据分析的第一步。下面我将详细介绍这些步骤&am…

overleaf如何使用中文(超简单)

LaTeX默认都是不支持中文的&#xff0c;有时候我们想输入中文。 1、编译器配置为XeLaTeX 菜单-编译器-XeLaTeX 2、代码开头添加宏包 \usepackage[UTF8]{ctex}

STM32 ADC转换器

一、ADC简介 ADC&#xff08;Analog-Digital Converter&#xff0c;模拟-数字转换器&#xff09;&#xff0c;可以将引脚上连续变化的模拟量转换为内存中存储的数字量&#xff0c;建立模拟电路到数字电路的桥梁 模拟量&#xff1a;时间和幅值均连续的信号&#xff0c;例如&…

YOLOv9训练结果分析->mAP、Precision、Recall、FPS、Confienc、混淆矩阵分析

简介 这篇博客&#xff0c;主要给大家讲解我们在训练yolov9时生成的结果文件中各个图片及其中指标的含义&#xff0c;帮助大家更深入的理解&#xff0c;以及我们在评估模型时和发表论文时主要关注的参数有那些。本文通过举例训练过程中的某一时间的结果来帮助大家理解&#xf…

Sigmoid激活函数

Sigmoid函数是一种常用的激活函数&#xff0c;其数学公式为&#xff1a; σ ( x ) 1 1 e − x \sigma(x) \frac{1}{1 e^{-x}} σ(x)1e−x1​ 其中&#xff0c; x x x 是函数的输入&#xff0c; σ ( x ) \sigma(x) σ(x) 是函数的输出。 sigmoid函数在神经网络中常被用于…

等待队列如何应用

等待队列 上一篇的程序写完出现了一个比较棘手的问题,运行应用程序就会发现,此时的read函数是非阻塞的,而实际使用场景往往需要我们在读取按键状态时阻塞,在用户按下按键之后read函数返回并得到按下按键的键值。阻塞用户空间的read当然可以在驱动层用一个死循环来实现,但…

【前端】vue的基础知识及开发指引

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Vue是什么二、学习 Vue.js 的基础知识三、熟悉 Vue.js 的生态系统四、掌握常用工具和库五、实践和项目开发六、 持续学习和跟进 前言 随着开发语言及人工智…

【office安装错误1402或1406】

office安装错误1402或1406 错误如图 解决方法 打开autoremove&#xff0c;点击扩展&#xff0c;输入1402&#xff0c;点击搜索 等待修复成功&#xff0c;再尝试安装office 软件每周六选择其他登录方式可以免费使用

深入探索GDB:Linux下强大的调试神器

目录 一、GDB简介&#xff1a;源码级调试的基石 二、GDB基础操作&#xff1a;从入门到熟练 启动与基本命令 三、GDB进阶功能&#xff1a;解锁更深层次的调试能力 1. 回溯追踪&#xff1a;洞察调用栈 2. 动态内存检测&#xff1a;揪出内存问题 3. 条件断点与观察点&#…

pyTorch框架部署实践

相关代码链接见文末 1.所需基本环境配置 首先&#xff0c;我们需要一个预先训练好的模型以及相应的配置。接下来&#xff0c;为了实际应用这个模型&#xff0c;我们必须搭建一个功能强大的服务器。这台服务器的核心任务是加载我们的模型&#xff0c;并能够接收用户上传的图片。…

关于Domain的查询命令

dig: 用来执行DNS查询&#xff0c;可以获取指定域名的所有类型的DNS记录。对网络管理员和开发人员尤其有用。 host: 一个简化版的DNS查询工具&#xff0c;适合快速查询域名的IP地址或某种类型的DNS记录。 nslookup: 另一个DNS查询工具&#xff0c;既支持交互模式也支持命令行模…