[1]:
import typing as T
import attrs

attrs.__version__
[1]:
'21.4.0'

PyCharm Support

PyCharm 很早就支持基于 attr.s API 的自动补全, 但 attrs 是 2021 年之后的新 API, Pycharm 2022.3 中 attrs.define 还不被 PyCharm 所接受.

[2]:
import attr


@attr.s
class Base:
    id: int = attr.field()


@attr.s
class User(Base):
    name: str = attr.field()


@attr.s
class PaidUser(User):
    account: str = attr.field()


# move cursor in bracket and hit CMD + P to see hint
Base()
User()
PaidUser(id="invalid", name="alice", account="1234")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [2], in <cell line: 20>()
     16     account: str = attr.field()
     19 # move cursor in bracket and hit CMD + P to see hint
---> 20 Base()
     21 User()
     22 PaidUser(id="invalid", name="alice", account="1234")

TypeError: __init__() missing 1 required positional argument: 'id'
[ ]:
@attr.s
class User:
    _id: 1 = attr.ib()
    name: str = attr.ib()


user = User(id=1, name="Alice")
print(user)
print(user._id)
[3]:
@attr.s
class Base:
    _important_attr: T.List[str] = None

    def to_dict(self) -> dict:
        return {
            k: v
            for k, v in attr.asdict(self).items()
            if k in self._important_attr
        }


@attr.s
class Person(Base):
    id: int = attr.ib()
    name: str = attr.ib()

    _important_attr = ["name"]


person = Person(id=1, name="alice")
person.to_dict()
[3]:
{'name': 'alice'}

Immutable 和 Mutable

所谓 Immutable 就是对象一旦被创建, 就无法被修改. 而 Mutable 则是对象被创建后, 其属性值是可以被修改的. 这里要注意的是, 如果对象的属性是 Mutable 的对象, 比如有个属性是列表, 你可以对列表本身进行修改, 但是不能给这个属性赋一个新的值.

Immutable 的好处:

  1. 可以从机制上避免很多因为修改带来的错误.

  2. 对象可以被缓存, 可以被哈希, 可以用来当 Dict Key, 可以用来去重.

[8]:
def sort_arr(arr: list):
    arr.sort()
    return arr


@attr.s(frozen=True)
class MyImmutableData:
    arr: T.List[int] = attr.ib(factory=list, converter=sort_arr)


my_immutable_data = MyImmutableData(arr=[3, 1, 4, 2])
my_immutable_data.arr.append(5)
print(my_immutable_data.arr)
[1, 2, 3, 4, 5]

Converter

很多时候我们的一些属性被传入的时候我们希望对其做一些预处理. 例如将字符串 cast type 成整数, 将列表进行排序等. 而一旦我们的对象是 Mutable 的, Converter 机制就不是很好用了. 因为逻辑上你每次对属性进行修改时就应该运行 converter, 但是你如果用 self.my_attribute = ... 的方式赋值, converter 方法并不会被调用. 所以这时我建议将 Converter 函数都做成这个类的 classmethod, 然后如果你非要调用 self.my_attribute = ... 的时候,

[7]:
def sort_arr(arr: list):
    arr.sort()
    return arr


@attr.s
class MyMutableData:
    arr: T.List[int] = attr.ib(factory=list, converter=sort_arr)

    @classmethod
    def convert_arr(cls, arr: list) -> list:
        return sort_arr(arr)


my_mutable_data = MyMutableData(arr=[1, 2])
my_mutable_data.arr = my_mutable_data.convert_arr([3, 1, 4, 2])
print(my_mutable_data.arr)
[1, 2, 3, 4]
[ ]: