跳转至

神奇的类

Descriptor

TODO

Python 有三个内置 descriptor(也是 decorator):@property@classmethod@staticmethod

Property

使用 @property 可以让我们建立一个需要 method 计算的 field。例如对一个正方形,我们可以只存储边长,而将面积设置为一个 property,需要的时候即时计算。

例子来自《Learning Python》。

class Person:
    def __init__(self, name):
        # 通常用带下划线的名字作为实际值
        self._name = name

    @property
    def name(self):  # name = property(name)
        """name property docs"""
        print('fetch...')
        return self._name

    # setter 和 deleter 的名字也都是 name
    @name.setter
    def name(self, value):  # name = name.setter(name)
        print('change...')
        self._name = value

    @name.deleter
    def name(self):  # name = name.deleter(name)
        print('remove...')
        del self._name

Static method

使用 @staticmethod,则参数不会添加 selfcls,就像一个 function 一样,只是在一个类内。

class Book:
    @staticmethod
    def foo(x):
        return x

Class method

class Book:
    @classmethod
    def foo(cls, x):
        return cls, x

Data classes

我们经常会写:

class Book:
    def __init__(self, name: str, author: str):
        self.name = name
        self.author = author

dataclasses 的话,只需要:

from dataclasses import dataclass

@dataclass
class Book:
    name: str
    author: str

@dataclass 会帮我们自动加上 __init__()__repr__()__eq__() 等。

用处一是简化类的代码,二是可以替代 namedtupletyping.NamedTupleattrs 等。有一点像 Typescript 的 interface

讨论:https://stefan.sofa-rockers.org/2020/05/29/attrs-dataclasses-pydantic/

特殊 attribute

class Book:
    '''A book'''
    name: str = 'A name'

    # 类的特殊 attribute
    __name__  # 类名 'Book'
    __module__  # 类所在模块 '__main__'
    __dict__  # 类的命名空间 mappingproxy({'__module__': '__main__', '__annotations__': {'name': <class 'str'>}, '__doc__': 'Book class', 'name': 'Good', '__dict__': <attribute '__dict__' of 'Book' objects>, '__weakref__': <attribute '__weakref__' of 'Book' objects>})
    __bases__  # 基础类 (<class 'object'>,)
    __doc__  # 文档 'A book'
    __annotations__  # 属性的标注,若如则无此 attribute {'name': <class 'str'>}

    # 类的特殊方法
    object.__new__(cls, ...)  # 这其实是一个静态方法。创建对象时,先调用 cls.__new__(),再调用 self.__init__()
    object.__init__(self, ...)
    object.__del__(self)  # 在实例被摧毁前调用,注意 del x 并不调用 x.__del__()
    object.__repr__(self)  # repr() 调用该方法,返回一个 str,主要用于 debugging,比如在交互式 shell 里,给程序员看
    object.__str__(self)  # str(object)、format()、print() 调用该方法,可以给最终用户看
    object.__bytes__(self)  # bytes() 调用该方法
    object.__format__(self, format_spec)  # format() 调用该方法
    object.__lt__(self, other)  # <,这些都是用于和另一个对象大小、相等
    object.__le__(self, other)  # <=
    object.__eq__(self, other)  # ==,如果没有定义,默认使用 is 来判断 __eq__()
    object.__ne__(self, other)  # !=,如果没有定义,默认使用 !__eq__()
    object.__gt__(self, other)  # >
    object.__ge__(self, other)  # >=
    object.__hash__(self)  # hash() 调用该方法,应保证 __eq__() 的对象的 hash 相同;如果没有定义 __eq__(),不要定义该方法
    object.__bool__(self)  # bool() 调用该方法,如果没有定义,只要 __len__() 不是 0 则为 True;若 __len__() 没有定义,则永远为 True

    # 自定义类的 attribute 的访问
    object.__getattribute__(self, name)  # 默认 attribute 访问机制;一般不重写此方法
    object.__getattr__(self, name)  # 这三个是可以自定义的方法,作为 fallback
    object.__setattr__(self, name, value)
    object.__delattr__(self, name)
    object.__dir__(self)  # dir() 调用该方法,该方法应返回 sequence;dir() 会将结果转为 list 并排序;一般不重写此方法

    # TODO descriptor
    object.__get__()
    object.__set__()
    object.__delete__()

    # TODO
    object.__slots__()

类的 attribute 访问机制

Managing Attribute Access and Descriptors (Theory of Python) (Python Tutorial) - YouTube