(第八篇)__format__、__hash__、__init_subclass__、__reduce_ex__、__reduce__、__sizeof__、__setstate__、__getstate__

发布时间 2023-04-10 12:23:23作者: hechengQAQ

一、__format__(self, format_spec)

当我们使用format()方法对一个对象进行格式化时,如果这个对象有__format__方法,那么这个方法就会被调用。它接受一个变量作为参数,并返回一个格式化后的字符串。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __format__(self, format_spec):
        if format_spec == "info":
            return f"{self.name}, {self.age} years old"
        else:
            return f"{self.name} ({self.age})"

person = Person("Alice", 25)
print("{:info}".format(person))  # Alice, 25 years old。冒号:表示分隔符,指格式化对象的info
print("{!s}".format(person))    # 强制调用__str__,打印对象自身,若自定义,则调用自定义的__str__,不会调用__format__方法

"""

在格式规范中,我们可以通过使用 : 分隔符来指定格式化选项。例如,我们可以使用 {:.2f} 来将一个浮点数保留两位小数并格式化为字符串。


另外,我们可以使用 ! 符号来指定类型转换,例如:


    • !s:调用对象的 __str__() 方法,将对象转换为字符串;
    • !r:调用对象的 __repr__() 方法,将对象转换为字符串,以表示对象的形式显示;
    • !a:调用对象的 __format__() 方法,将对象转换为 ASCII 码表示的字符串。
"{!s}".format(person) 会调用 person 对象的 __str__() 方法,将其转换为字符串后插入到占位符中。
"""

# 具体的format格式化冒号:中选项有很多,具体可以查阅资料。
例如{:8}将文本格式化为至少8个字符的宽度,如果输出不足8个字符,则使用空格进行填充

二、__hash__(self)

当我们使用hash()函数对一个对象进行哈希时,如果这个对象有__hash__方法,那么这个方法就会被调用。用于计算Python对象的哈希值。哈希值可以用于比较对象是否相等,并且可以被用作字典的键。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __hash__(self):
        return hash((self.name, self.age))

person1 = Person("Alice", 25)
person2 = Person("Alice", 25)
person3 = Person("Ali", 25)

print(hash(person1))  # 7694926712467326889
print(hash(person2))  # 7694926712467326889  相同对象
print(hash(person3))  # 4723851031937723218

"""
哈希值是一种可用于快速比较对象的值,相同的对象应该有相同的哈希值。该方法应该返回一个整数(否则报错)。
"""

三、__init_subclass__(cls,**kwargs)

用于在子类创建时自动调用。当我们定义一个类时,如果这个类有__init_subclass__方法,那么这个方法就会在这个类的任何子类创建时自动调用。__init_subclass__方法可以用于在子类创建时做一些初始化操作。也可以用于为所有子类添加公共的属性或方法。cls参数是子类的类对象,kwargs参数是传递给子类创建时的关键字参数。

class Base:
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.name = kwargs.get("name", "Base")

class Child1(Base, name="Child1"):
    pass

class Child2(Base):
    pass

print(Child1.name)  # Child1
print(Child2.name)  # Base

四、__reduce_ex__(self, protocol)

用于自定义对象的序列化方式。当我们使用pickle模块将一个对象序列化时,如果这个对象有__reduce_ex__方法,那么这个方法就会被调用。__reduce_ex__方法需要返回一个元组,元组的第一个元素是一个可调用对象,用于反序列化对象,第二个元素是一个元组,包含了可调用对象的参数。protocol参数指定了序列化协议的版本号。

import pickle

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __reduce_ex__(self, protocol):
        return (self.__class__, (self.name, self.age))

person = Person("Alice", 25)
data = pickle.dumps(person)
new_person = pickle.loads(data)

print(new_person.name)  # Alice
print(new_person.age)   # 25

五、__reduce__(self)

__reduce_ex__方法类似,也是定义对象在使用pickle模块序列化时的行为,但不支持指定协议版本。


import pickle
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def __reduce__(self):
        return (Person, (self.name, self.age))
        
p = Person('Alice', 25)
b = pickle.dumps(p)   # 将p对象序列化为包含二进制的字节流:b'\x80\x04\x95%\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x06Person\x94\x93\x94\x8c\x05Alice\x94K\x19\x86\x94R\x94.'
new_p = pickle.loads(b)  # 要想查看数据,进行反序列化
print(new_p.name)   # Alice
print(new_p.age)    # 25

六、__sizeof__(self)

 用于返回对象所占用的内存大小。当我们使用sys.getsizeof()函数获取一个对象的大小时,如果这个对象有__sizeof__方法,那么这个方法就会被调用。__sizeof__方法需要返回一个整数,表示对象所占用的内存大小。
import sys

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __sizeof__(self):
        return sys.getsizeof(self.name) + sys.getsizeof(self.age)

person = Person('Alice', 25)
print(sys.getsizeof(person))  # 输出 64

"""
注意这里返回的值并不一定等于对象实际占用的内存大小,因为Python的内存管理机制比较复杂,涉及到了垃圾回收、内存对齐等问题。
"""

七、__setstate__、__getstate__

这两个方法只有在对象要被序列化或反序列化时才会被调用。

import pickle

class MyClass:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    def __getstate__(self):
        return {'x': self.x}
    
    def __setstate__(self, state):
        self.x = state['x']
        self.y = 0

# 序列化一个 MyClass 实例,调用__getstate__
obj = MyClass(1, 2)
data = pickle.dumps(obj)

# 反序列化该 MyClass 实例,调用__setstate__
new_obj = pickle.loads(data)
print(new_obj.x)  # 输出 1
print(new_obj.y)  # 输出 0