attrs,一个OOP编程的Python高效神器

Python类的定义和管理库之attrs使用详解 在 Python 编程中,定义类和数据结构是常见的任务。然而,手动编写类的初始化方法、属性验证和比较方法可能会非常繁琐和容易出错。attrs库是一个功能强大且灵活的工具,旨在简化类的定义和管理。通过使用attrs,开发者可以轻松定义不可变对象、进行属性验证和类型检查,并生成友好的类表示形式。本文将详细介绍attrs库,包括其安装方法、主要特性、基本和高级功能,以及实际应用场景,帮助全面了解并掌握该库的使用。 阅读详情
AI摘要

本文介绍了Python的attrs库,它是OOP编程的高效工具,能统一类属性描述,让代码更简洁。文章讲解了attrs库的安装、基本用法、默认参数、派生属性等内容,还给出了Web应用表单验证、数据传输对象等使用场景示例,推荐优先使用较新的导入方式。

大家好,这一期我想和大家分享一个OOP编程的高效神器:attrs库。

这可能是 Python 面向对象编程的最佳实践。

为什么需要attrs库

在编写大型项目时,特别是在开发和维护大型项目时,你可能会发现编写 Python 类很繁琐。

我们经常需要添加构造函数、表示方法、比较函数等。这些函数很麻烦,而这正是语言应该透明地处理的。

之前的文章,我们介绍过在Python 3.7(PEP 557)后引入一个新功能是装饰器@dataclass帮助我们优雅的处理这一系列问题。今天我们继续介绍另一个能打的库:attrs。

attrs统一了类属性描述,让代码更加简洁可读。

如果要写一个完整的类需要加上很多内置方法的实现。这会导致大量的重复工作,而且代码会非常冗长。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
from typing import Any


class Coordinate:
    def __init__(self, x: Any, y: Any) -> None:
        self.x = x  # 设置横坐标
        self.y = y  # 设置纵坐标

    def __repr__(self) -> str:
        return f"Coordinate(x={self.x}, y={self.y})"  # 返回对象的字符串表示形式

    def __eq__(self, other: Any) -> bool:
        # 检查另一个对象是否是相同类型的Coordinate
        if isinstance(other, self.__class__):
            # 比较两个对象的横纵坐标是否相等
            return (self.x, self.y) == (other.x, other.y)
        else:
            return NotImplemented

    def __ne__(self, other: Any) -> bool:
        result = self.__eq__(other)
        if result is NotImplemented:
            return NotImplemented
        else:
            # 返回两个对象的相等性的否定
            return not result

    def __lt__(self, other: Any) -> bool:
        # 检查另一个对象是否是相同类型的Coordinate
        if isinstance(other, self.__class__):
            # 比较两个对象的横纵坐标的大小关系
            return (self.x, self.y) < (other.x, other.y)
        else:
            return NotImplemented

    def __le__(self, other: Any) -> bool:
        if isinstance(other, self.__class__):
            return (self.x, self.y) <= (other.x, other.y)
        else:
            return NotImplemented

    def __gt__(self, other: Any) -> bool:
        if isinstance(other, self.__class__):
            return (self.x, self.y) > (other.x, other.y)
        else:
            return NotImplemented

    def __ge__(self, other: Any) -> bool:
        if isinstance(other, self.__class__):
            return (self.x, self.y) >= (other.x, other.y)
        else:
            return NotImplemented

    def __hash__(self) -> int:
        # 返回对象的哈希值,用于将对象存储在散列数据结构中
        return hash((self.__class__, self.x, self.y))

添加测试代码:

if __name__ == '__main__':
    # 创建坐标对象
    coord1 = Coordinate(3, 4)
    coord2 = Coordinate(5, 6)

    # 打印对象的字符串表示形式
    print(coord1)  # 输出: Coordinate(x=3, y=4)
    print(coord2)  # 输出: Coordinate(x=5, y=6)

    # 比较坐标对象是否相等
    print(coord1 == coord2)  # 输出: False
    print(coord1 != coord2)  # 输出: True

    # 比较坐标对象的大小关系
    print(coord1 < coord2)  # 输出: True
    print(coord1 <= coord2)  # 输出: True
    print(coord1 > coord2)  # 输出: False
    print(coord1 >= coord2)  # 输出: False

    # 将坐标对象用作字典的键
    coord_dict = {coord1: 'Point 1', coord2: 'Point 2'}
    print(coord_dict)  # 输出: {Coordinate(x=3, y=4): 'Point 1', Coordinate(x=5, y=6): 'Point 2'}

    # 使用集合去除重复的坐标对象
    coords = [Coordinate(1, 2), Coordinate(3, 4), Coordinate(1, 2)]
    unique_coords = set(coords)
    print(unique_coords)  # 输出: {Coordinate(x=1, y=2), Coordinate(x=3, y=4)}

聪明的你很快想到可以像下面这样优化下:

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
from typing import Any, Tuple
from functools import total_ordering


@total_ordering
class Coordinate:
    def __init__(self, x: Any, y: Any) -> None:
        self.x = x  # 设置横坐标
        self.y = y  # 设置纵坐标

    def __repr__(self) -> str:
        return f"Coordinate(x={self.x}, y={self.y})"  # 返回对象的字符串表示形式

    def __eq__(self, other: Any) -> bool:
        if isinstance(other, self.__class__):
            return (self.x, self.y) == (other.x, other.y)
        return NotImplemented

    def __lt__(self, other: Any) -> bool:
        if isinstance(other, self.__class__):
            return (self.x, self.y) < (other.x, other.y)
        return NotImplemented

    def __hash__(self) -> int:
        return hash((self.__class__, self.x, self.y))

然后你想起数据类装饰器@dataclass可能更优雅,不过今天我们用attrs库来实现:

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

from typing import Any
import attr


@attr.s(hash=True)
class Coordinate:
    x: Any = attr.ib()  # 横坐标
    y: Any = attr.ib()  # 纵坐标

attrs只需要简单的描述就可以帮你完成上面一大堆的功能,非常有用。

安装

pip install attrs cattrs

初识attrs库

官方的介绍是这样的:

attrs 是一个 Python 包,它将通过将您从实现对象协议(又名 dunder 方法)的苦差事中解脱出来,带回编写类的乐趣。自 2020 年以来,受到 NASA 火星任务的信任!

它的主要目标是帮助您编写简洁正确的软件,而不会减慢您的代码速度。

目前,attrs库也已经有5K star,是非常受欢迎的,而且也在被不断更新维护。

github地址:https://github.com/python-attrs/attrs

attrs的工作原理是使用attrs.define或attr.s装饰一个类,然后使用attrs.field、attr.ib或类型注解定义类的属性。

从 21.3.0 版开始,attrs包含两个顶级包名称:

  • 经典的attr为古老的attr.s和attr.ib提供动力。

  • 较新的attrs只包含大多数现代API,并依赖attrs.define和attrs.field来定义类。此外,它还提供了一些默认值更好的attr API(如 attrs.asdict)。

推荐优先从较新的attrs导入

另外,attrs库经典的attr API定义了一些别名,自己使用的时候或看别人代码的时候注意一下即可:

attrs库介绍

基本用法与默认参数

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
from loguru import logger
from attrs import define, field, Factory


@define
class Color(object):
    r = field(type=int, default=0)
    g = field(type=int, default=0)
    b = field(type=int, default=0)

    # 可变对象作为默认值
    mutable = field(type=list, default=Factory(list))
    mutable2 = field(type=list, default=Factory(lambda: [1, 2, 3]))
    mutable3 = field(factory=list)


if __name__ == '__main__':
    color = Color(250, 255, 255, mutable=[1, 2, 3])
    logger.info(color)

    color2 = Color()
    logger.info(color2)

输出结果为:

2024-02-18 19:42:08.012 | INFO     | __main__:<module>:23 - Color(r=250, g=255, b=255, mutable=[1, 2, 3], mutable2=[1, 2, 3], mutable3=[])
2024-02-18 19:42:08.012 | INFO     | __main__:<module>:26 - Color(r=0, g=0, b=0, mutable=[], mutable2=[1, 2, 3], mutable3=[])

一切都显得那么简洁。一个字,爽!

不参与初始化

如果某个类的特定属性不希望在初始化过程中被设置,例如希望直接将其设置为固定的初始值且保持不变,我们可以将该属性的 init 参数设置为 False。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

from attrs import define, field
from loguru import logger


@define
class Color(object):
    r = field(type=int, default=0, init=False)
    g = field(type=int, default=0)
    b = field(type=int, default=0)


if __name__ == '__main__':
    color = Color(255, 255)
    logger.info(color)

输出结果为:

2024-02-18 22:46:44.228 | INFO     | __main__:<module>:19 - Color(r=0, g=255, b=255)

派生属性

如何拥有依赖于其他属性的属性?

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import datetime

import attr
from attrs import field
from loguru import logger


@attr.s
class Data(object):
    dt: datetime.datetime = attr.ib(factory=datetime.datetime)
    dt_30_later: datetime.datetime = field(init=False)
    dt_30_later2 = field(init=False)

    # 方法一
    def __attrs_post_init__(self):
        self.dt_30_later = self.dt + datetime.timedelta(days=30)

    # 方法二
    @dt_30_later2.default
    def _dt_30_later2(self):
        return self.dt + datetime.timedelta(days=30)


def serialize(inst, attribute, value):
    if isinstance(value, datetime.datetime):
        return value.isoformat()
    return value


json_data = attr.asdict(
    Data(datetime.datetime(2024, 2, 18, 21, 46)),
    value_serializer=serialize)

logger.info(json_data)

输出结果为:

2024-02-18 21:50:52.516 | INFO     | __main__:<module>:36 - {'dt': '2024-02-18T21:46:00', 'dt_30_later': '2024-03-19T21:46:00', 'dt_30_later2': '2024-03-19T21:46:00'}

强制关键字

from attr import attrs, attrib
from loguru import logger


@attrs
class Color(object):
    r = attrib(type=int, default=0)
    g = attrib(type=int, default=0)
    b = attrib(type=int, default=0, kw_only=True)


if __name__ == '__main__':
    color = Color(250, 255, b=255)
    logger.info(color)

    color2 = Color()
    logger.info(color2)

输出结果为:

2024-02-18 15:25:11.078 | INFO     | __main__:<module>:19 - Color(r=250, g=255, b=255)
2024-02-18 15:25:11.078 | INFO     | __main__:<module>:22 - Color(r=0, g=0, b=0)

我们把b参数设置为关键字参数,若传入则必须使用关键字的名字来传入,否则会报错。

元数据

attrs还支持在类和属性上添加元数据,为类和属性添加更多的信息,使代码更加自文档化。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import attrs


@attrs.define
class Product:
    name = attrs.field(metadata={"description": "Product name"})
    price = attrs.field(metadata={"description": "Product price"})


# 获取属性的元数据
name_description = attrs.fields(Product).name.metadata["description"]
print(f"Name description: {name_description}")

构建不可变类

通过设置frozen=True,可以创建不可变的实例。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

from attr import attrib, attrs
from loguru import logger


@attrs(frozen=True)
class Color(object):
    r = attrib(type=int, default=0)
    g = attrib(type=int, default=0)
    b = attrib(type=int, default=0, kw_only=True)


if __name__ == '__main__':
    color = Color(250, 255, b=255)
    logger.info(color)

    # color.r = 100  # 加入 frozen 参数可以让类初始化之后不可改变,强行改变会报错

比较

import attr


@attr.attrs
class Point:
    x = attr.ib()
    y = attr.ib()


point1 = Point(1, 2)
point2 = Point(3, 4)
point3 = Point(1, 2)

print(point1 == point2)  # 输出: False
print(point1 != point2)  # 输出: True
print(point1 == point3)  # 输出: True

print(point1 < point2)  # 输出: True
print(point1 <= point2)  # 输出: True
print(point1 > point2)  # 输出: False
print(point1 >= point2)  # 输出: False

由于使用了attrs,相当于我们定义的类已经有了__eq__、__ne__、__lt__、__le__、__gt__、__ge__这几个方法,所以我们可以直接使用比较符来对类和类之间进行比较。

在比较对象时,attrs 库内部实现是将类的各个属性转换为元组来进行比较。例如,对于 Point(1, 2) < Point(3, 4) 这样的比较,实际上是在比较两个元组 (1, 2) 和 (3, 4)。关于元组之间的比较逻辑,具体细节可以参考官方文档:https://docs.python.org/3/library/stdtypes.html#comparisons。

验证数据

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

import attr
from loguru import logger


@attr.s
class Book:
    title = attr.ib(default="Unknown", validator=attr.validators.instance_of(str))
    pages = attr.ib(default=0, validator=attr.validators.instance_of(int))


# 创建对象
book = Book(title="三国演义", pages=200)

# 输出对象信息
logger.info(book)

book2 = Book(title="三国演义", pages="200")

输出结果为:

2024-02-18 15:43:23.098 | INFO     | __main__:<module>:20 - Book(title='三国演义', pages=200)
Traceback (most recent call last):
  File "E:/projects/mukewang/python_and_go/约瑟夫.py", line 22, in <module>
    book2 = Book(title="三国演义", pages="200")
  File "<attrs generated init 3ea315b0de8c75a1cfeeeb1010a6df97094df790>", line 6, in __init__
  File "E:\ENV\py3.8_blog\lib\site-packages\attr\validators.py", line 22, in __call__
    raise TypeError(
TypeError: ("'pages' must be <class 'int'> (got '200' that is a <class 'str'>).", Attribute(name='pages', default=0, validator=<instance_of validator for type <class 'int'>>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), <class 'int'>, '200')

支持对初始化的数据进行校验。

# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

import attr


@attr.s
class TrafficLight:
    color = attr.ib(validator=attr.validators.in_({"red", "yellow", "green"}), kw_only=True)


# 创建对象
traffic_light = TrafficLight(color="red")

# 这里不会报错
traffic_light.color = "blue"

@attr.s修改属性时不会报错。

使用较新的attrs.define,修改属性时也会校验。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import attrs


@attrs.define
class TrafficLight:
    color = attrs.field(default="red", validator=attrs.validators.in_({"red", "yellow", "green"}), kw_only=True)


# 创建对象
traffic_light = TrafficLight()

# 修改属性,会触发验证器并引发异常
try:
    traffic_light.color = "blue"
except ValueError as e:
    print(f"Validation Error: {e}")

输出结果为:

Validation Error: ("'color' must be in {'red', 'green', 'yellow'} (got 'blue')", Attribute(name='color', default='red', validator=<in_ validator with options {'red', 'green', 'yellow'}>, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=True, inherited=False, on_setattr=None, alias='color'), {'red', 'green', 'yellow'}, 'blue')

在attrs库中,旧版api中验证器通常在对象初始化时触发,而不是在属性赋值时触发,因此需要额外处理。

import attr


def validate_color(instance, attribute, value):
    if value not in {"red", "yellow", "green"}:
        raise ValueError(f"Invalid color: {value}")


@attr.s
class TrafficLight:
    _color = attr.ib(default="red")

    @property
    def color(self):
        return self._color

    @color.setter
    def color(self, value):
        validate_color(self, self.color, value)
        self._color = value


# 创建对象
traffic_light = TrafficLight()

# 修改属性,会触发验证器并引发异常
try:
    traffic_light.color = "blue"
except ValueError as e:
    print(f"Validation Error: {e}")

自定义验证器的两种方法:

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

import attr
from loguru import logger


def pages_validator(instance, attribute, value):
    if value > 300:
        raise ValueError("pages must be greater than 300")
    return value


@attr.s
class Book:
    title = attr.ib(default="Unknown", validator=attr.validators.instance_of(str))
    pages = attr.ib(default=0, validator=[attr.validators.instance_of(int), pages_validator])  # 校验一
    price = attr.ib(default=0.0, validator=attr.validators.instance_of(float))

    # 校验二
    @title.validator
    def check_title(self, attribute, value):
        if value == "Unknown":
            raise ValueError("title cannot be 'Unknown'")


# 验证自定义验证器
try:
    book3 = Book(title="三国演义", pages=500)

    logger.info(book3)
except Exception as e:
    logger.error(e)

try:
    book3 = Book(pages=500)

    logger.info(book3)
except Exception as e:
    logger.error(e)

输出结果为:

在自定义Validator时,有三个固定的参数:

  • instance(或self):表示类对象
  • attribute:表示属性名
  • value:表示属性值

这三个参数在类初始化时被固定地传递给了 Validator。因此,Validator 在接收到这三个值后,就能够进行相应的判断。

另外还有其他的一些 Validator,比如与或运算、可执行判断、可迭代判断等等,可以参考官方文档:https://www.attrs.org/en/stable/api.html#validators。

转换器

有时候,我们可能会不小心传入一些格式不太标准的数据,例如本来应该是整数类型的数字 100,却传入了字符串类型的 “100”。在这种情况下,直接抛出错误可能不太友好。因此,我们可以设置一些转换器来增强容错机制,例如自动将字符串转换为数字等。让我们看一个实例:

import attr


def to_int(value):
    return int(value)


@attr.s
class Point:
    x = attr.ib(converter=to_int)
    y = attr.ib(converter=to_int)


# 使用示例
point1 = Point(1, 2)
print(point1) 

point2 = Point("3", "4")
print(point2)  

point3 = Point("5", "hello")
print(point3)  

输出结果为:

Point(x=1, y=2)
Point(x=3, y=4)
Traceback (most recent call last):
  File "E:/projects/mukewang/python_and_go/约瑟夫.py", line 26, in <module>
    point3 = Point("5", "hello")
  File "<attrs generated init bc20f05ee98d8a0eaee1da898b5665773d9e94d7>", line 3, in __init__
  File "E:/projects/mukewang/python_and_go/约瑟夫.py", line 10, in to_int
    return int(value)
ValueError: invalid literal for int() with base 10: 'hello'

attrs允许你在创建类时自动修改或转换类的字段。其主要目的是根据属性类型自动为属性添加转换器。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

import datetime

import attrs
from attrs import frozen
from loguru import logger


def auto_convert(cls, fields):
    results = []
    for field in fields:
        logger.info(field)
        if field.converter is not None:
            results.append(field)
            continue
        if field.type in {datetime.datetime, 'datetime'}:
            converter = (lambda d: datetime.datetime.fromisoformat(d) if isinstance(d, str) else d)
        else:
            converter = None
        results.append(field.evolve(converter=converter))
    return results


@frozen(field_transformer=auto_convert)
class Data:
    a: int
    b: str
    c: datetime


logger.info("-----" * 5)
from_json = {"a": 3, "b": "spam", "c": "2020-05-04T13:37:00"}
logger.info(Data(**from_json))

logger.info(Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37)))

logger.info(attrs.asdict(Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37))))
logger.info(attrs.astuple(Data(c=datetime.datetime(2020, 5, 4, 13, 37), a=3, b='spam')))

输出结果为:

2024-02-18 23:12:00.571 | INFO     | __main__:auto_convert:16 - Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=<class 'int'>, converter=None, kw_only=False, inherited=False, on_setattr=None, alias=None)
2024-02-18 23:12:00.571 | INFO     | __main__:auto_convert:16 - Attribute(name='b', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=<class 'str'>, converter=None, kw_only=False, inherited=False, on_setattr=None, alias=None)
2024-02-18 23:12:00.571 | INFO     | __main__:auto_convert:16 - Attribute(name='c', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=<module 'datetime' from 'd:\\installdir\\python3.8.0\\Lib\\datetime.py'>, converter=None, kw_only=False, inherited=False, on_setattr=None, alias=None)
2024-02-18 23:12:00.571 | INFO     | __main__:<module>:35 - -------------------------
2024-02-18 23:12:00.571 | INFO     | __main__:<module>:37 - Data(a=3, b='spam', c='2020-05-04T13:37:00')
2024-02-18 23:12:00.571 | INFO     | __main__:<module>:39 - Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37))
2024-02-18 23:12:00.571 | INFO     | __main__:<module>:41 - {'a': 3, 'b': 'spam', 'c': datetime.datetime(2020, 5, 4, 13, 37)}
2024-02-18 23:12:00.587 | INFO     | __main__:<module>:42 - (3, 'spam', datetime.datetime(2020, 5, 4, 13, 37))

序列转换

在许多情况下,我们经常需要在JSON等字符串序列和对象之间进行转换,特别是在编写REST API和数据库交互时。

attrs为我们提供了asdict、astuple方法用于序列化。

默认情况下,asdict方法将实例中的所有属性转换为字典的键值对,但是可以通过一些参数来自定义转换行为,比如使用filter参数来指定要包含或排除的属性,使用value_serializer参数来指定自定义的值序列化函数。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import attr
import attrs
from loguru import logger


@attrs.define
class UserInfo(object):
    users = attr.ib()


@attrs.define
class User(object):
    email = attr.ib()
    name = attr.ib()


# including only name and not email

json_data = attrs.asdict(UserInfo([User("lee@har.invalid", "Lee"),
                                   User("rachel@har.invalid", "Rachel")]),
                         filter=lambda _attr, value: _attr.name != "email")

logger.info(json_data)


# ---

@attrs.define
class UserInfo(object):
    name = attrs.field()
    password = attrs.field()
    age = attrs.field()


# excluding attributes
logger.info(
    attrs.asdict(UserInfo("Marco", "abc@123", 22), filter=attrs.filters.exclude(attrs.fields(UserInfo).password, int)))


# ---
@attr.s
class Coordinates(object):
    x = attr.ib()
    y = attr.ib()
    z = attr.ib()


# inclusing attributes

logger.info(attrs.asdict(Coordinates(20, "5", 3),
                         filter=attr.filters.include(int)))

logger.info(attrs.astuple(Coordinates(20, "5", 3),
                          filter=attr.filters.include(int)))

输出结果为:

2024-02-18 23:25:21.529 | INFO     | __main__:<module>:27 - {'users': [{'name': 'Lee'}, {'name': 'Rachel'}]}
2024-02-18 23:25:21.529 | INFO     | __main__:<module>:40 - {'name': 'Marco'}
2024-02-18 23:25:21.529 | INFO     | __main__:<module>:55 - {'x': 20, 'z': 3}
2024-02-18 23:25:21.529 | INFO     | __main__:<module>:58 - (20, 3)

序列化时间:

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import datetime

import attr
from loguru import logger


@attr.s
class Data(object):
    dt: datetime.datetime = attr.ib(factory=datetime.datetime)


def serialize(inst, attribute, value):
    if isinstance(value, datetime.datetime):
        return value.isoformat()
    return value


json_data = attr.asdict(
    Data(datetime.datetime(2020, 5, 4, 13, 37)),
    value_serializer=serialize)

logger.info(json_data)

输出结果为:

2024-02-18 21:14:20.875 | INFO     | __main__:<module>:26 - {'dt': '2020-05-04T13:37:00'}

尽管attrs库提供了序列化的能力,但是我们一般习惯使用cattrs库。

attrs更侧重于创建Python类,并提供了一些辅助方法来处理这些类的实例,而cattrs则更专注于对象的序列化和反序列化操作。

cattrs库的导入名称稍有不同,称为cattr。它提供了两个主要的方法:structure 和 unstructure。这两个方法是互补的,对于类的序列化和反序列化提供了很好的支持。

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

from typing import Dict, Any
from attr import attrs, attrib
import cattr


@attrs
class Point:
    x: int = attrib(default=0)
    y: int = attrib(default=0)


def drop_non_attrs(d: Dict[str, Any], type_: type) -> Dict[str, Any]:
    if not isinstance(d, dict):
        return d
    attrs_attrs = getattr(type_, '__attrs_attrs__', None)
    if attrs_attrs is None:
        raise ValueError(f'type {type_} is not an attrs class')
    attrs_set = {attr.name for attr in attrs_attrs}
    return {key: val for key, val in d.items() if key in attrs_set}


def structure(d: Dict[str, Any], type_: type) -> Any:
    return cattr.structure(drop_non_attrs(d, type_), type_)


json_data = {'x': 1, 'y': 2, 'z': 3}
print(structure(json_data, Point))

对时间datetime转换的时候进行的处理:

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51

import datetime
from attr import attrs, attrib
import cattr
from loguru import logger

TIME_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'


@attrs
class Event(object):
    happened_at = attrib(type=datetime.datetime)


cattr.register_unstructure_hook(datetime.datetime, lambda dt: dt.strftime(TIME_FORMAT))
cattr.register_structure_hook(datetime.datetime,
                              lambda string, _: datetime.datetime.strptime(string, TIME_FORMAT))

event = Event(happened_at=datetime.datetime(2024, 2, 18))
logger.info(f'event: {event}')
json = cattr.unstructure(event)
logger.info(f'json: {json}')
event = cattr.structure(json, Event)
logger.info(f'Event: {event}')

在这里,我们为 datetime 类型注册了两个钩子。在序列化时,我们调用 strftime 方法将其转换为字符串;在反序列化时,我们调用 strptime 将其转换回 datetime 类型。

嵌套处理

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51


from loguru import logger
from attr import attrs, attrib
from typing import List
from cattr import structure, unstructure


@attrs
class Point:
    x = attrib(type=int, default=0)
    y = attrib(type=int, default=0)


@attrs
class Color:
    r = attrib(default=0)
    g = attrib(default=0)
    b = attrib(default=0)


@attrs
class Line:
    color = attrib(type=Color)
    points = attrib(type=List[Point])


if __name__ == '__main__':
    try:
        line = Line(color=Color(), points=[Point(i, i) for i in range(5)])
        logger.info(f'Created Line object: {line}')

        json_data = unstructure(line)
        logger.info(f'Serialized JSON: {json_data}')

        line = structure(json_data, Line)
        logger.info(f'Deserialized Line object: {line}')
    except Exception as e:
        logger.error(f'An error occurred: {e}')

输出结果为:

2024-02-18 22:41:46.668 | INFO     | __main__:<module>:35 - Created Line object: Line(color=Color(r=0, g=0, b=0), points=[Point(x=0, y=0), Point(x=1, y=1), Point(x=2, y=2), Point(x=3, y=3), Point(x=4, y=4)])
2024-02-18 22:41:46.668 | INFO     | __main__:<module>:38 - Serialized JSON: {'color': {'r': 0, 'g': 0, 'b': 0}, 'points': [{'x': 0, 'y': 0}, {'x': 1, 'y': 1}, {'x': 2, 'y': 2}, {'x': 3, 'y': 3}, {'x': 4, 'y': 4}]}
2024-02-18 22:41:46.668 | INFO     | __main__:<module>:41 - Deserialized Line object: Line(color=Color(r=0, g=0, b=0), points=[Point(x=0, y=0), Point(x=1, y=1), Point(x=2, y=2), Point(x=3, y=3), Point(x=4, y=4)])

可以看到,我们轻松地将对象转换为了 JSON 表示,并且同样轻松地将其转换回对象。

使用场景示例

以下是一些实际的使用场景示例代码:

Web 应用中的表单验证:

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import attrs
from attrs import validators


@attrs.define
class UserRegistrationForm:
    username = attrs.field(validator=validators.instance_of(str))
    email = attrs.field(validator=validators.instance_of(str))
    password = attrs.field(validator=validators.instance_of(str))


# 使用示例
form_data = {"username": "alice", "email": "alice@example.com", "password": "123456"}
form = UserRegistrationForm(**form_data)

数据传输对象 (DTO):

#!usr/bin/env python
# -*- coding:utf-8 _*-
# __author__:lianhaifeng
# __time__:2024/2/14 18:51
import cattr

import attrs
from datetime import datetime

from loguru import logger


@attrs.define
class OrderDTO:
    order_id = attrs.field()
    customer_name = attrs.field()
    total_amount = attrs.field()

    # 添加一个时间戳属性
    created_at = attrs.field(default=datetime.now())


# 定义 datetime 的序列化和反序列化函数
def serialize_datetime(dt):
    return dt.strftime('%Y-%m-%d %H:%M:%S')


def deserialize_datetime(dt_str, _: dict):
    return datetime.strptime(dt_str, '%Y-%m-%d %H:%M:%S')


# 创建一个包含 datetime 序列化和反序列化函数的转换器
converter = cattr.Converter()
converter.register_structure_hook(datetime, deserialize_datetime)
converter.register_unstructure_hook(datetime, serialize_datetime)
# 创建一个订单DTO对象
order_data = {"order_id": "123456", "customer_name": "Alice", "total_amount": 100}
order_dto = converter.structure(order_data, OrderDTO)

# 打印订单DTO对象的属性
logger.info(f"Order ID: {order_dto.order_id}")
logger.info(f"Customer Name: {order_dto.customer_name}")
logger.info(f"Total Amount: {order_dto.total_amount}")
logger.info(f"Created At: {order_dto.created_at}")

# 将订单DTO对象转换为字典
# order_dict = {
#     "order_id": order_dto.order_id,
#     "customer_name": order_dto.customer_name,
#     "total_amount": order_dto.total_amount,
#     "created_at": order_dto.created_at.strftime("%Y-%m-%d %H:%M:%S")
# }

order_dict = converter.unstructure(order_dto)
logger.info(f"Order Dictionary: {order_dict}")

输出结果为:

2024-02-18 17:30:04.563 | INFO     | __main__:<module>:27 - Order ID: 123456
2024-02-18 17:30:04.563 | INFO     | __main__:<module>:28 - Customer Name: Alice
2024-02-18 17:30:04.563 | INFO     | __main__:<module>:29 - Total Amount: 100
2024-02-18 17:30:04.563 | INFO     | __main__:<module>:30 - Created At: 2024-02-18 17:30:04.561213
2024-02-18 17:30:04.563 | INFO     | __main__:<module>:39 - Order Dictionary: {'order_id': '123456', 'customer_name': 'Alice', 'total_amount': 100, 'created_at': '2024-02-18 17:30:04'}

图书出版示例

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

from typing import List
import attrs
from loguru import logger


@attrs.define(auto_attribs=True)
class Book:
    title: str
    author: str
    pages: int
    price: float

    def is_expensive(self) -> bool:
        return self.price >= 20


@attrs.define(auto_attribs=True)
class BookCollection:
    books: List[Book] = attrs.field(factory=list)

    def add_book(self, book: Book):
        self.books.append(book)

    def remove_book(self, book: Book):
        self.books.remove(book)

    def get_expensive_books(self) -> List[Book]:
        return [book for book in self.books if book.is_expensive()]


# 创建Book的实例
book1 = Book(title="流畅的Python", author="拉马略", pages=523, price=24.99)
book2 = Book(title="Coding with Python", author="Another Author", pages=210, price=19.99)

# 创建BookCollection的实例,并添加书籍
collection = BookCollection()
collection.add_book(book1)
collection.add_book(book2)

# 移除一本书
collection.remove_book(book2)

# 查找所有昂贵的图书
expensive_books = collection.get_expensive_books()
logger.info(f"昂贵的图书: {expensive_books}")


@attrs.define
class User:
    name = attrs.field(type=str)
    age = attrs.field(converter=int)
    email = attrs.field(type=str)

    @age.validator
    def check_age(self, attribute, value):
        if value < 18:
            raise ValueError("User must be at least 18 years old")

    @email.validator
    def check_email(self, attribute, value):
        if "@" not in value:
            raise ValueError("Invalid email address")


# 创建User类的实例,注意这里故意创建一个非法的实例来演示验证功能
try:
    user = User(name="John Doe", age=17, email="john@domain.com")
except ValueError as e:
    logger.error(e)


@attrs.define(auto_attribs=True)
class Publisher:
    name: str
    founded: int
    location: str

    def publish(self, book: Book):
        logger.info(f"{self.name} 出版了:【{book.title}】")


@attrs.define(auto_attribs=True)
class Review:
    content: str
    book: Book
    score: int

    def is_positive(self) -> bool:
        return self.score > 3


@attrs.define(auto_attribs=True)
class AuthorProfile:
    name: str
    genre: str
    books_written: List[Book] = attrs.field(factory=list)

    def write_book(self, title: str, pages: int, price: float) -> Book:
        book = Book(title=title, author=self.name, pages=pages, price=price)
        self.books_written.append(book)
        return book


# 创建一个出版商实例
publisher = Publisher(name="人民邮电出版社", founded=2023, location="中国")

# 作者创建书籍并由出版商发布
author_profile = AuthorProfile(name="唐诗三百首", genre="Tech")
new_book = author_profile.write_book("流畅的Python", pages=300, price=29.99)
publisher.publish(new_book)

# 添加一些书评
review1 = Review(content="不错的书!", book=new_book, score=8)
review2 = Review(content="烂书..", book=new_book, score=1)

# 假设我们需要展示所有正面的书评
positive_reviews = [review for review in [review1, review2] if review.is_positive()]

for review in positive_reviews:
    logger.info(f"- {review.content}")

输出结果为:

2024-02-18 22:42:45.841 | INFO     | __main__:<module>:48 - 昂贵的图书: [Book(title='流畅的Python', author='拉马略', pages=523, price=24.99)]
2024-02-18 22:42:45.842 | ERROR    | __main__:<module>:72 - User must be at least 18 years old
2024-02-18 22:42:45.845 | INFO     | __main__:publish:82 - 人民邮电出版社 出版了:【流畅的Python】
2024-02-18 22:42:45.845 | INFO     | __main__:<module>:123 - - 不错的书!

小结

本节介绍了如何利用attrs和cattrs两个库来实现Python的面向对象编程。有了这两个库的支持,Python的面向对象编程变得更加简单易行。

从表面上看,attrs可能会让你联想到数据类(事实上,数据类@dataclass是attrs的后代)。实际上,它的功能更多,也更灵活。例如,它允许你定义NumPy数组的特殊处理方法以进行相等检查,允许更多方法插入初始化过程,并允许使用调试器逐步检查生成的方法。

强烈建议优先使用较新的attrs API,如attrs.define、attrs.field等。

更多attrs库的使用方法请浏览官方文档!

最后

如果你觉得文章还不错,请大家点赞、分享、关注下,因为这将是我持续输出更多优质文章的最强动力!

参考

https://www.attrs.org/en/stable/init.html#hooking-yourself-into-initialization

https://catt.rs/en/stable/index.html#

Python使用attr库打造数据类,你还在手写构造函数吗? 在Python中,attr库是一个简化创建数据类的工具 ,它通过简洁的语法自动添加属性和方法 ,如getter、setter等。要开始使用attr,首先需要通过pip安装这个库。打开终端或命令提示符,运行以下命令进行安装: 阅读详情

相关推荐

Vue中$attrs的作用和使用方法

Vue中 $attrs 的作用和使用方法

qq_39454432的博客 2003

python库(16):Attrs库创建具有属性的对象

attrs库,即Attribute的缩写,是一个Python第三方库,它提供了一种优雅的方式来定义具有属性的类。使用attrs,你可以用更少的代码,更清晰的方式定义类和对象。

u013938578的博客 1479

OOP编程的Python高效神器 attrs 库的特点和用法

Python 中 attrs 库的特点和用法。包括其能简化类的编写,如统一类属性描述,避免大量重复工作;介绍了安装方法、基本用法、默认参数、不参与初始化、派生属性、强制关键字、元数据、构建不可变类、比较、验证数据、转换器、序列转换、嵌套处理等方面的内容,还列举了其在 Web 应用表单验证、数据传输对象、图书出版等场景的示例,最后强调建议优先使用较新的 attrs API。

wjianwei666的专栏 125

attrs,一个了不起的 Python 库!

attrs提供了多种预定义的验证器,方便进行常见的验证操作。例如,使用@attr.s# 创建对象# 修改属性值(会引发异常)# traffic_light.color = "blue" # 会抛出 ValueError这有助于确保属性值的合法性,提高代码的稳健性。attrs是一个在 Python 类定义中提供简洁、强大功能的库。通过声明式的语法,attrs简化了类的定义,大大减少了样板代码的编写。本文深入介绍了attrs的多个方面,包括基本用法、验证器、不可变实例、元数据、装饰器扩展、属性转换器等。

涛哥聊Python 4092

oVirt 4.4.10超详细安装指南:从零搭建企业级虚拟化环境(附常见错误排查)

本文提供了oVirt 4.4.10企业级虚拟化平台的超详细安装指南,涵盖环境规划、系统准备、Engine安装配置、计算节点部署、存储系统集成及高级运维技巧。通过实战操作手册和常见错误排查方法,帮助用户从零搭建稳定高效的虚拟化环境,特别适合企业IT管理员参考实施。

wasm7browser的博客 349

FICO F.27 Customer statement 打印

需求,定制化打印,替换标准的F.27打印 类似于采购订单的打印,但是略有不同,查阅资料之后步骤如下: T-code F.27 is SAP standard program to produce customer/vendor correspondence (letter). Correspondence Type is used to specify the related printing program and form for the particular correspondence. SAP

IamRunjiang的博客 1963

attrs学习

对于 attrib 的定义,可以传入各种参数,不同的参数对于这个类的定义有非常大的影响。下面来详细了解一下每个属性的具体参数和用法。首先我们用 attrs 里面的 fields 方法可以查看一下参数解释name属性的名字,是一个字符串类型default属性的默认值,如果没有传入初始化数据,那么就会使用默认值,如果没有默认值定义,那么就是 NOTHING,即没有默认值validator验证器,检查传入的参数是否合法init。

哲哲的博客 852

用Python实现四叉树(quad tree)

import numpy as np #正方形类 class square: #lx、ly为左上角坐标(x对应行号,y对应列号),rx、ry为右下角坐标 def __init__(self,lx,ly,rx,ry): self.lx=lx self.ly=ly self.rx=rx self.ry=ry #检查该矩阵中是否满足条件 #参数:原矩阵,阈值 #返回值:布尔类型 def check(s

weixin_44164489的博客 5868

python编写person类_Python 面向对象编程OOP (四) 写类神器:attrs

使用attrs解放双手大家好,这一期我想和大家分享一个OOP编程的高效神器:attrs库首先我们来介绍下 attrs 这个库,其官方的介绍如下:attrs 是这样的一个 Python 工具包,它能将你从繁综复杂的实现上解脱出来,享受编写 Python 类的快乐。它的目标就是在不减慢你编程速度的前提下,帮助你来编写简洁而又正确的代码。因此用了它,定义和实现 Python 类变得更加简洁和高效首先明确...

weixin_39613433的博客 708

DataHub 架构设计与核心工作原理

DataHub 是一个第三代数据目录系统,专为现代数据栈设计,支持数据发现、协作、治理和端到端可观测性。其架构采用模型优先的设计理念,注重不同工具和系统之间的互操作性。

北斗云的博客 1223

实时数字人SoulX-FlashHead 音视频不对齐解决

【代码】实时数字人SoulX-FlashHead 音视频不对齐解决。

jacke121的专栏 200

Python进阶核心:从工程化到并发编程的实战能力提升

在编程语言的学习路径中,从基础语法掌握到高级工程实践是一个关键跃迁。其核心原理在于理解语言的对象模型、内存管理机制与并发处理范式,这直接决定了代码的性能与可维护性。掌握这些底层机制的技术价值在于,开发者能够编写出高效、健壮且易于协作的软件系统,而非仅仅实现功能的脚本。在应用场景上,无论是构建高并发的网络服务、处理大规模数据流,还是设计可复用的框架库,都需要深入的语言特性支持。本文聚焦Python进阶,通过剖析模块化设计、面向对象编程、生成器与迭代器、以及异步编程等热词,系统讲解如何跨越从“会用”到“精通”的

weixin_30384217的博客 500

【AI智能体】Dify 搭建个人专属简历美化助手操作详解

Dify 搭建个人专属简历诊断助手实战操作详解

congge 4378

Python attrs,提高面向对象编程效率

Python是面向对象的语言,一般情况下使用面向对象编程会使得开发效率更高,软件质量更好,并且代码更易于扩展,可读性和可维护性也更高。但是如果在一个较大的项目中,如果实体类非常多并且有非常复杂的属性,你就会逐渐觉得Python的类写起来是真·“累”。为什么这样说,看下下面这个Box类,属性有长(length)、宽(width)、高(hight): class Box: def __init__(self, length, width, hight): .

weixin_50097774的博客 5285

6-Halcon-- var_threshold 函数功能(用于基于局部统计特性的动态阈值分割的算子)

var_threshold 是 HALCON 中用于基于局部统计特性的动态阈值分割的算子,特别适用于处理低对比度图像或纹理均匀但包含细微差异的场景。与 dyn_threshold 不同,它不依赖预处理生成的参考图像,而是直接在局部区域内计算灰度值的统计特性(均值和标准差),从而自适应调整阈值。

weixin_45590420的博客 1326

实战案例—encrypt靶场(AES固定key篇)

AES是一种对称密钥加密方法,意味着加密和解密时使用相同的密钥。AES加密存在三种形式:AES-128、AES-192和AES-256,它们分别对应于三种不同的密钥长度——128位(即16字节)、192位(即24字节)和256位(即32字节)。通常情况下,密钥的长度越长,提供的安全性就越高,但相应地,加解密过程所需的时间也会增加。默认情况下,采用的是AES-128,其提供的安全性对于大多数应用来说已经足够充分。

zhouzhou_98的博客 497

Python编程:python-attrs模块的简单使用

文档:http://www.attrs.org/en/stable/index.html attrs 可以简单理解为namedtuple的增强版 安装 pip install attrs 代码示例 1、定义一个tuple p1 = (1, 2) p2 = (1, 2) print(p1 == p2) # True print(p1) # (1, 2) 2、namedtuple定义一个类 fr...

彭世瑜的博客 1万+

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Vue3 - $attrs 的几种用法(1个或多个根元素、Options API 和 Composition API)

theme: smartblue 持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情 本文简介 点赞 + 关注 + 收藏 = 学会了 使用 Vue 开发时,肯定会接触到 组件 的概念,无可避免的也会接触到 组件通讯 的概念。 组件通讯的方式有很多种,我在 《Vue3 过10种组件通讯方式》 一文中粗略罗列了 Vue3 常用的组件通讯方法。 ...

weixin_39415598的博客 1万+

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

Vue 中 props 和 attrs 概念与区别

Vue 中的区别: Vue中的attrs和attrs和attrs和listener Vue-- attrs与attrs与attrs与listeners的详解 在 vue 中,对于在父组件中传递的属性, 若在子组件中使用@Prop声明了属性如@Prop name,则 name 属性只会在 this.props中出现,不会出现在this.props 中出现,不会出现在 this.props中出现,不会出现在this.attrs 中; 若在子组件中没有使用@Prop声明属性,则属性会默认在 this.attrs中

csdn_girl的博客 5144

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

attrs:Python的类装饰器(简化类定义)

attrs 是一个用于简化 Python 类的构建和属性管理的库。它提供了一种优雅的方式来定义类,自动生成初始化方法、比较、表示和其他常用的功能,从而使开发者可以更加专注于业务逻辑,而不是繁琐的样板代码。attrs 特别适合需要大量数据存储的场景,比如数据传输对象(DTO)、配置类和模型类等。

技术无疆 1687

开源库入门教程:attrs - 让Python类构建更优雅

Python类定义神器attrs库:告别样板代码,提升开发效率 attrs库是Python中用于简化类定义的开源工具,它通过声明式语法自动生成__init__、__repr__等样板代码。相比传统类定义方式,attrs能减少90%的重复代码量,同时支持类型提示、属性验证、默认值设置等高级功能。本文介绍了attrs的核心用法,包括基础类定义、属性验证、不可变类实现等特性,并比较了与标准库dataclasses的差异。通过实际案例展示了如何使用attrs构建API响应类,体现了其在提高代码可读性和减少错误方面的

2501_93501684的博客 1804

python提效工具之cattrs(高效类序列化)和attrs库(简化类创建)详解

如果场景中有很多的 序列化和反序列的过程,那有没有一种工具让我们快速的去做这件事情呢,那就可以参考cattrs库。cattrs 是一个Python库,它提供了高级的数据结构转换功能,使得将Python类实例序列化和反序列化变得简单。cattrs特别适用于将复杂或深层的数据结构与常见的数据交换格式(如JSON、YAML等)之间做转换。它非常适合处理通过网络传输或存储在文件中的数据。cattrs核心功能是能够在Python字典和Python类(特别是使用attrs库()定义的类)实例之间自动转换。

Trb401012的博客 377

一图看懂 attrs 模块:一个在类定义时可替换 `__init__`, `__eq__`, `__repr__`等方法的样板,资料整理+笔记(大全)

全文介绍系统 attrs 模块(一个在类定义时可替换__init____eq____repr__等方法的样板)、函数、类及类的方法和属性。它通过代码抓取并经AI智能翻译和人工校对。是一部不可多得的权威字典类工具书。它是系列集的一部分。后续陆续发布、敬请关注。【原创:AhcaoZhu大侠】

常年招收【在读】大学生、研究生、IT一技之长者,远程兼职,待遇丰厚!请私信我! 1479

python中attrs_与attrs告别Python中的样板

python中attrs Python是当今使用的最流行的编程语言之一,并且有充分的理由:它是开源的,具有广泛的用途(例如Web编程,业务应用程序,游戏,科学编程等等),并且有一个充满活力和敬业精神的社区来支持它。 这个社区的原因是我们在Python软件包索引 (PyPI)中拥有如此众多,种类繁多的软件包,以扩展和改进Python并解决不可避免的小问题。 在本系列中,我们将研究七个PyPI库...

cumj63710的博客 3968

attrib和attrs

attrs函数用于定义类,它接受一个或多个属性定义作为参数,并返回一个类对象。通过使用attrs函数,可以快速定义具有一组属性的类,而无需手动编写__init__方法和属性赋值语句。attrib函数用于定义类的属性,它接受一个或多个参数来定义属性的名称、类型和默认值等。通过使用attrib函数,可以简化属性的定义过程,使代码更加简洁和易读。综合起来,attrib和attrs函数的主要用途是简化类的属性定义和类的创建过程,提高代码的可读性和可维护性。

LiuPig 577
上一篇: 60行python代码带你了解刘谦春晚魔术的奥秘
下一篇: 如何解决 SQL 深层分页问题?
海哥python
博客等级 码龄9年 1487粉丝 74原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海哥python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值