Python 高质量类编写指南

原文: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()

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大模型检索召回系统:RAG技术的全面调查与未来展望

随着人工智能技术的飞速发展&#xff0c;大型语言模型&#xff08;LLMs&#xff09;在自然语言处理&#xff08;NLP&#xff09;领域取得了显著成就。然而&#xff0c;这些模型在处理特定领域或知识密集型任务时仍面临挑战&#xff0c;如产生错误信息或“幻觉”。为了克服这些难…

docker-compose搭建redis环境:哨兵模式(一主两重两哨兵)

文章目录 0.BG1. 编写docker-compose.yml文件2. 哨兵配置文件sentinel.conf3.启动容器4.模拟故障转移 0.BG redis环境有多中模式&#xff0c;包括Standalone&#xff0c;Cluster和Sentinel模式等。这里介绍一种简单搭建Sentinel模式的方法&#xff0c;搭建一个一主两重两哨兵的…

做视频号小店一年半,内部玩法曝光,今日全盘托出

大家好&#xff0c;我是电商笨笨熊 腾讯推出电商的消息一出来&#xff0c;就成为了电商界的又一关注点&#xff1b; 不少人称腾讯做电商不会长久&#xff0c;也有人称视频号小店必将成为未来电商黑马&#xff1b; 无论是哪种说法&#xff0c;视频号小店我先替大家做了一年半…

进程状态和优先级(进程第2篇)【Linux复习篇】

目录 一、进程状态 1、进程有什么状态&#xff1f; 2、 Linux下的进程状态有什么&#xff1f; 二、进程优先级 1、进程优先级是什么&#xff1f; 2、为什么要有优先级 3、怎么改进程优先级&#xff1f;要改吗&#xff1f; 4、操作系统如何根据优先级开展调度的&#xff…

使用原型学习和特权信息进行可解释的医学图像分类

Interpretable Medical Image Classification Using Prototype Learning and Privileged Information 摘要 .可解释性通常是医学成像的基本要求。需要先进的深度学习方法来满足这种对可解释性和高性能的需求。 本文研究了训练过程中可用的其他信息是否可用于创建易于理解且强…

DS32K查看内置寄存器数值

需要在debug的时候进行查看&#xff0c;先暂停&#xff0c;再打开EmbSys Registers窗口。 需要先将导出的内容选中并双击&#xff0c;不然复制出来会变成问号。右上角有个复制按钮&#xff0c;复制到剪贴板就行。譬如我这里选择了MCR寄存器&#xff0c;复制出来的就是这个寄存器…

Redis入门到通关之Redis数据结构-List篇

文章目录 ☃️概述☃️数据结构☃️源码☃️其他 欢迎来到 请回答1024 的博客 &#x1f353;&#x1f353;&#x1f353;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后端…

七分钟“手撕”三大特性<多态>

目录 一、学习多态之前需要的知识储备 二、重写 1.什么是重写 2.重写可以干嘛 3.怎么书写重写 4.重载与重写的区别 三、向上转型 1.什么是向上转型&#xff1f; 2.向上转型的语法 3.向上转型的使用场景 四、多态是什么 六、多态实现 七、多态的好处 八、多态的缺…

程序员过了35岁没人要?“这行越老越香”

程序员35岁失业&#xff1f;参加完OceanBase开发者大会&#xff0c;我又悟了&#xff01; 周六参加了OceanBase2024 开发者大会的现场&#xff0c;来之前我其实挺忐忑的&#xff0c;我觉得一个数据库产品的发布会&#xff0c;能有什么新鲜的东西&#xff1f; 踏入酒店的那一刻&…

经风靡全球的 PHP 为何逐渐失去优势?

TIOBE 编程语言人气指数发布更新&#xff0c;并提出“PHP 的魔力是否正在消散&#xff1f;”的灵魂拷问。今年 4 月&#xff0c;PHP 在 TIOBE 编程语言指数榜上仅位列第 17&#xff0c;“成为其有史以来的最低排位”。 暴露 PHP 人气急剧下滑的还不只是 TIOBE 榜单。在年度 Sta…

MP4转gif如何操作?一个常见方法分享

MP4是一种视频格式&#xff0c;而gif则是图片格式。当我们需要将MP4格式的时候转成gif格式图片的时候要怎么操作呢&#xff1f;怎样在不下载软件的情况下在线转换格式呢&#xff1f;很简单&#xff0c;通过使用gif图片制作&#xff08;https://www.gif.cn/&#xff09;工具-GIF…

Android开发者必备:RootEncoder引领实时流媒体传输革新

Android开发者必备&#xff1a;RootEncoder引领实时流媒体传输革新 I. 引言 A. RootEncoder简介 RootEncoder for Android&#xff08;rtmp-rtsp-stream-client-java&#xff09;是一个功能强大的流编码器&#xff0c;旨在通过多种协议&#xff08;包括RTMP、RTSP、SRT和UDP…

VR全景创业项目应该如何开展?未来有市场吗?

伴随着5G网络的发展&#xff0c;VR全景得到了众多的关注和提升。与此同时&#xff0c;各行各业都开始关注自身产业在互联网的展示效果&#xff0c;因为年轻一代的生活已经离不开互联网&#xff0c;而VR全景在互联网上的3D展示效果能给商家带来流量&#xff0c;提升营业额。 随着…

ERROR: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).

今天本来想在A服务器上传文件给B服务器的结果发现明明给root用户设置了密码就是远程登陆不了&#xff0c;后来才发现在容器中很多服务都是没有的&#xff0c;所以刚安装后忘记了修改配置文件&#xff0c;导致远程登陆失败。 报错&#xff1a; 解决方法&#xff1a; 在/etc/ssh…

【电控实物-infantry】

云台电机参数 电机内部参数 相电阻:Rs1.8欧 相电感:Ls5.7810^-3H 转矩常数:Kt 0.741 NM/A 转动惯量:J KG-m^2 电机接收数据&#xff1a;-16384到16384&#xff08;-3A到3A&#xff09; 电机反馈&#xff1a;速度RPM rad/s &#xff08;2πrpm&#xff09;/60 C板陀螺仪&…

苍穹外卖学习笔记(9.订单状态定时处理,来电提醒,客户催单)

目录 一、订单状态定时处理1、需求分析设计2、代码开发3、测试 二、来单提醒1、需求分析设计2、代码开发3、测试 三、客户催单1、需求分析设计2、代码开发3、测试 四、相关知识1、Spring Task2、WebSocket 一、订单状态定时处理 1、需求分析设计 2、代码开发 创建orderTask类…

初学若依笔记

初学若依 下载ruoyi(以前后端分离板为例) https://ruoyi.vip/ 部署 安装mysql安装redis将数据库和redis配置到若依 配置文件为 ruoyi-admin\src\main\resource\application-druid.yml 运行 略 开发自己的功能 创建模块 为了不影响原有功能&#xff0c;创建一个模块写自…

物联网(iot)深度解析——FMEA软件

物联网即IoT&#xff0c;是指通过各种信息传感器、射频识别技术、全球定位系统、红外感应器、激光扫描器等各种装置与技术&#xff0c;实时采集任何需要监控、连接、互动的物体或过程&#xff0c;采集其声、光、热、电、力学、化学、生物、位置等各种需要的信息&#xff0c;通过…

顺序栈算法库构建

学习贺利坚老师,顺序栈,构建顺序栈算法库 数据结构之自建算法库——顺序栈_设计一个主函数实现对顺序栈进行操作测试&#xff0c;测试方法&#xff0c;依次把元素-CSDN博客文章浏览阅读4.9k次&#xff0c;点赞10次&#xff0c;收藏10次。本文针对数据结构基础系列网络课程(2)&…

CRAFT文字检测算法解析和基于C++和TensorRT的推理实现

本文讲解了CVPR 2019的一篇文字检测算法《Character Region Awareness for Text Detection》的原理&#xff0c;并给出我使用C和TensorRT重新实现的推理&#xff0c;速度比原版代码快12倍。 论文&#xff1a;https://arxiv.org/pdf/1904.01941.pdf 官方代码&#xff1a;https:…