目录
自定义属性访问: __setattr__、__getattribute__、__getattr__、__delattr__、__dir__方法详解。
模拟容器类型的方法:__setitem__、__getitem__、__delitem__、__missing__、__iter__、__reversed__、__contains__
自定义属性访问: __setattr__、__getattribute__、__getattr__、__delattr__、__dir__方法详解。
可以定义下列方法来自定义对类实例属性访问(self.name
的使用、赋值或删除)的具体含义.
下列方法都是object基类中特殊方法。
- __dict__:以字典对象表示的模块命名空间属性,存储类实例的属性名和属性值。
object.
__setattr__
(self, key, value): 对类的实例属性key赋值为value,存储在属性字典__dict__中。object.
__getattribute__
(self, key): 访问类的实例属性key,如果属性key不存在或者发生AttributeError异常,则调用__getattr__()方法。
object.
__getattr__
(self, key): 当属性访问引发异常时被调用,此方法应当返回(找到的)属性值或是引发一个 AttributeError异常。object.
__delattr__
(self, key): 删除一个属性时调用,此方法应该仅在del obj.name
对于该对象有意义时才被实现。object.
__dir__
(self): 调用 dir(obj)方法 时被调用。返回值必须为一个序列。 dir()会把返回的序列转换为列表并对其排序。
代码示例:
class Demo(object):
def __init__(self):
self.name = '大帅' # 设置属性,会调用__setattr__方法
self.age = 18 # 设置属性,会调用__setattr__方法
print(self.__dict__) # 访问属性__dict__的值,输出:{'name': '大帅', 'age': 18}
del self.age # 删除age属性,调用__delattr__方法
def __setattr__(self, key, value):
print(f"__setattr__方法执行, 给{key}属性设置值:{value}")
self.__dict__[key] = value # 会调用__getattribute__方法,获取__dict__的值,再给key赋值为vaule
def __getattribute__(self, key):
# print(f"调用__getattribute__方法,key的值:{key}")
return super().__getattribute__(key)
def __getattr__(self, key):
print(f"访问不存在的key:{key}")
return None
def __delattr__(self, key):
print(f"删除属性: {key}")
super().__delattr__(key)
def __dir__(self):
print("调用__dir__方法")
return super().__dir__()
d = Demo() # 初始化实例化对象 d
print(f"d.name的值:{d.name}") # 访问属性name的值,输出:大帅
print(f"d.__dict__的值:{d.__dict__}") # 访问属性__dict__的值,输出:{'name': '大帅'}
print(f"d.age的值:{d.age}") # 访问不存在的属性age的值,输出:None
print(dir(d)) # 列出d对象拥有的所有的属性名和方法名,调用__dir__方法
输出:
__setattr__方法执行, 给name属性设置值:大帅
__setattr__方法执行, 给age属性设置值:18
{'name': '大帅', 'age': 18}
删除属性: age
d.name的值:大帅
d.__dict__的值:{'name': '大帅'}
访问不存在的key:age
d.age的值:None
调用__dir__方法
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
说明:上述示例中,自定义了Demo类,并重写了object中的上述几个__xxx__()方法。
- 当调用self.key = value给属性赋值时,会调用__setattr__()方法,把属性名和属性值存在__dict__属性中。
- 代码执行到第10行时:self.__dict__[key] = value,会调用__getattribute__()方法,去获取self.__dict__的值。
- 执行完del self.age调用__delattr__方法删除age属性,当再使用d.age去访问时,此时先调用__getattribute__方法,属性不存在或发生
AttributeError
异常,则调用__getattr__()返回None - 使用dir()方法列出d对象拥有的所有的属性名和方法名
- self.name的调用顺序:先在 __getattribute__方法中查找属性,找不到或属性抛出异常时,再到
__getattr__方法中查找,最后在
属性__dict__
中查找。
模拟容器类型的方法:__setitem__、__getitem__、__delitem__、__missing__、__iter__、__reversed__、__contains__
常见的python容器类型包含序列类型:列表list、元组typle;容器类型:字典dict等。都实现的如下特殊方法:
object.
__getitem__
(self, key): 调用此方法以实现向self[key]
赋值(添加或修改)。如果 key 的类型不正确则会引发TypeError
,如果为序列索引异常则引发IndexError
,对于映射类型,如果 key 找不到则应引发KeyError
异常。object.
__setitem__
(self, key, value): 调用此方法以实现self[key]
的求值。object.
__delitem__
(self, key): 调用此方法以实现self[key]
的删除。object.
__missing__
(self, key): 此方法由 dict.__getitem__() 在找不到字典中的键时调用以实现 dict 子类的self[key]
object.
__iter__
(self): 此方法在需要为容器创建迭代器时被调用。此方法应该返回一个新的迭代器对象,它能够逐个迭代容器中的所有对象。对于映射,它应该逐个迭代容器中的键。object.
__reversed__
(self): 此方法(如果存在)会被reversed()
内置函数调用以实现逆向迭代。它应当返回一个新的以逆序逐个迭代容器内所有对象的迭代器对象。object.
__contains__
(self, item): 调用此方法以实现成员检测运算符。如果 item 是 self 的成员则应返回真,否则返回假。对于映射类型,此检测应基于映射的键而不是值或者键值对。
代码示例(讲解__setitem__,__getitem__,__delitem__,__missing__方法
):
class Demo(dict):
def __init__(self):
self["name"] = "大帅" # 设置属性,会调用__setitem__方法
self["age"] = 18 # 设置属性,会调用__setitem__方法
def __setitem__(self, key, value):
super().__setitem__(key, value) # 调用父类dict类中的方法,设置字典key的值为vaule
super().__setattr__(key, value) # 调用父类dict类中的方法,设置属性key的值为vaule
def __getitem__(self, key):
return super().__getitem__(key)
def __missing__(self, key):
"""访问的key不存在时,返回None"""
# print(f"不存在的key:{key}, __dict__的值:{self.__dict__}")
return None
def __getattr__(self, key):
"""访问的属性不存在时,返回False"""
# print(f"不存在的key:{key}, __dict__的值:{self.__dict__}")
return False
def __getattribute__(self, item):
return super().__getattribute__(item)
d = Demo()
print(d.name) # 打印:大帅
print(d["age"]) # 打印:18
print(d.__dict__) # 打印:{'name': '大帅', 'age': 18}
print(d['a']) # a是key不存在,此方式引发KeyError, 调用__missing__方法返回None
print(d.a) # a是属性不存在,此方式引发AttributeError异常,调用__getattr__方法返回False
输出:
大帅
18
{'name': '大帅', 'age': 18}
None
False
说明:上述示例中,自定义了Demo类,并重写了dict类中的上述几个__xxx__()方法,这几个方法object基类中不存在的。
要注意的是d.name和d["age"]这两种方式,在类中调用的方法不一样。d.name是访问属性name的值,d["age"]是访问映射类型字典__dict__中的key
__new__
(cls[, ...])
此方法是用来创建并返回cls类实例的静态方法,该方法会优先比__init__(self)方法被调用,__new__(cls)返回的类实例,就是__init__(self)方法中的self参数,如果__new__()方法没有返回该cls类实例,或者是返回了其他类的实例,则__init__()方法则不会被调用。
class NewClass:
instances_created_cnt = 0 # 类属性,用来统计实例创建的数量
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls) # 创建该类newClass的类实例
instance.number = cls.instances_created_cnt # 设置实例属性并赋值,用来查看是第几个创建的实例
cls.instances_created_cnt += 1 # +1
return instance # 返回该类的实例属性,下一步会调用init方法
def __init__(self, attribute):
self.attribute = attribute
if __name__ == '__main__':
test1 = NewClass("abc") # 创建了test1实例对象,instances_created_cnt=1
test2 = NewClass("xyz") # 创建了test2实例对象,instances_created_cnt=2
print(test1.number, test1.instances_created_cnt) # 输出:0 2
print(test2.number, test2.instances_created_cnt) # 输出:1 2
__new__()
的目的主要是允许不可变类型的子类 (例如 int, str 或 tuple) 定制实例创建过程。它也常会在自定义元类中被重载以便定制类创建过程。
__init__
(self[, ...])
在实例 (通过 __new__()
) 被创建之后,返回调用者之前调用。其参数与传递给类构造器表达式的参数相同。一个基类如果有 __init__()
方法,则其所派生的类如果也有 __init__()
方法,就必须显式地调用它以确保实例基类部分的正确初始化;例如: super().__init__([args...])
.
因为对象是由 __new__()
和 __init__()
协作构造完成的 (由 __new__()
创建,并由 __init__()
定制),所以 __init__()
返回的值只能是 None
,否则会在运行时引发 TypeError
。
__del__
(self)
在实例将被销毁时调用。 init方法是用来创建实例,而del方法恰好相反,是用来销毁实例。
__del__()
方法可以 (但不推荐!) 通过创建一个该实例的新引用来推迟其销毁。这被称为对象 重生。__del__()
是否会在重生的对象将被销毁时再次被调用是由具体实现决定的 ;当前的 CPython 实现只会调用一次。
当解释器退出时不会确保为仍然存在的对象调用 __del__()
方法。
注意:del x
并不直接调用 x.__del__()
--- 前者会将 x
的引用计数减一,而后者仅会在 x
的引用计数变为零时被调用。
class Demo:
def __init__(self):
print("调用 __init__() 方法构造对象")
def __del__(self):
print("调用__del__() 销毁对象,释放其空间")
if __name__ == '__main__':
d = Demo() # 创建实例对象d
dd = d # dd的引用指向d
del d # 销毁对象d,但此时并没有释放d所占用的空间资源,因为有dd对象在引用
print('------')
输出:
调用 __init__() 方法构造对象
---------------
调用__del__() 销毁对象,释放其空间
说明:从输出结果上看,执行del d后并没有立即释放该对象d所占用的内存空间,这和python的垃圾回收机制(gc)有关:Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。该方法的核心思想是:每个 Python 对象都会配置一个计数器,初始 Python 实例对象的计数器值都为 0,如果有变量引用该实例对象,其计数器的值会加 1,依次类推;反之,每当一个变量取消对该实例对象的引用,计数器会减 1。如果一个 Python 对象的的计数器值为 0,则表明没有变量引用该 Python 对象,即证明程序不再需要它,此时 Python 就会自动调用 __del__() 方法将其回收。
上面的程序,创建了d这个实例对象,则python的回收机制会给此对象指向的空间的计数器+1,执行到dd=d时,计数器+1,此时计数器值为2,执行del d时,计数器-1,但并没有释放空间,因为只有计数器为0时,才会释放空间
__repr__
(self)
由 repr()
内置函数调用以输出一个对象的“官方”字符串表示。
__str__
(self)
通过 str(object)
以及内置函数 format()
和 print()
调用以生成一个对象的“非正式”或格式良好的字符串表示。返回值必须为一个 字符串 对象。
class demo:
def __repr__(self):
return "repr"
def __str__(self):
return "str"
if __name__ == '__main__':
d = demo() # 创建实例对象d
print(d) # 输出str
print(str(demo())) # 输出str
print(repr(demo())) # 输出repr
两者的区别
- _str_用于为最终用户创建输出,而 _repr_ 主要用于调试和开发。 _repr_ 的目标是明确无误,_str_ 是可读的
- _repr_ 用于推断对象的"官方"字符串表示形式(包含有关对象的所有信息的表示, _str_ 用于推断对象的“非正式”字符串表示形式(对打印对象有用的表示形式
import datetime
today = datetime.datetime.now()
print(str(today))
print(repr(today))
输出
import datetime
today = datetime.datetime.now()
print(str(today))
print(repr(today))
__call__
(self[, args...])
该方法可以使得对象的使用跟函数的调用一样,及“对象名()”的形式
class Demo:
# 定义__call__方法
def __call__(self):
print("调用__call__()方法")
d = Demo()
d() # 输出:调用__call__()方法
def demo():
print("demo方法调用__call__()方法")
demo.__call__() # 输出:demo方法调用__call__()方法
hasattr() 函数可以查找类的实例对象中是否包含指定名称的属性或者方法,但该函数有一个缺陷,即它无法判断该指定的名称,到底是类属性还是类方法。
要解决这个问题,我们可以借助可调用对象的概念。要知道,类实例对象包含的方法,其实也属于可调用对象,但类属性却不是。举个例子:
class Demo:
def __init__(self):
self.name = "dashuai"
def say(self):
print("hello")
d = Demo()
if hasattr(d, "name"):
print(hasattr(d.name, "__call__")) # name是属性,没有__call__方法
print("**********")
if hasattr(d, "say"):
print(hasattr(d.say, "__call__")) # say是实例方法。含有__call__方法
输出
False
**********
True
__slots__
__slots__可以定义和限制一个类的属性
class Demo:
__slots__ = ('name', 'age')
d = Demo()
d.name = "dashuai" # 设置属性name值为dashuai
d.age = 18 # 设置属性age值为18
d.score = 100 # 发生AttributeError异常。设置的属性在__slots__范围之外
相比使用 __dict__ 此方式可以显著地节省空间。 属性查找速度也可得到显著的提升。