python 特殊方法和特殊属性详解

目录

自定义属性访问: __setattr__、__getattribute__、__getattr__、__delattr__、__dir__方法详解。

模拟容器类型的方法:__setitem__、__getitem__、__delitem__、__missing__、__iter__、__reversed__、__contains__

__new__(cls[, ...])

__init__(self[, ...])

__del__(self)

__repr__(self)

__str__(self)

__call__(self[, args...])

__slots__


自定义属性访问: __setattr__、__getattribute__、__getattr__、__delattr__、__dir__方法详解。

可以定义下列方法来自定义对类实例属性访问(self.name 的使用、赋值或删除)的具体含义.

下列方法都是object基类中特殊方法。

  • __dict__:以字典对象表示的模块命名空间属性,存储类实例的属性名和属性值。
  • object.__setattr__(selfkeyvalue):  对类的实例属性key赋值为value,存储在属性字典__dict__中。
  • object.__getattribute__(selfkey):  访问类的实例属性key,如果属性key不存在或者发生AttributeError异常,则调用__getattr__()方法。
  • object.__getattr__(selfkey):  当属性访问引发异常时被调用,此方法应当返回(找到的)属性值或是引发一个 AttributeError异常。
  • object.__delattr__(selfkey):  删除一个属性时调用,此方法应该仅在 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__(selfkey): 调用此方法以实现向 self[key] 赋值(添加或修改)。如果 key 的类型不正确则会引发 TypeError,如果为序列索引异常则引发IndexError,对于映射类型,如果 key 找不到则应引发 KeyError异常。
  • object.__setitem__(selfkeyvalue): 调用此方法以实现 self[key] 的求值。
  • object.__delitem__(selfkey): 调用此方法以实现 self[key] 的删除。
  • object.__missing__(selfkey): 此方法由 dict.__getitem__() 在找不到字典中的键时调用以实现 dict 子类的 self[key]
  • object.__iter__(self): 此方法在需要为容器创建迭代器时被调用。此方法应该返回一个新的迭代器对象,它能够逐个迭代容器中的所有对象。对于映射,它应该逐个迭代容器中的键。
  • object.__reversed__(self): 此方法(如果存在)会被 reversed() 内置函数调用以实现逆向迭代。它应当返回一个新的以逆序逐个迭代容器内所有对象的迭代器对象。
  • object.__contains__(selfitem): 调用此方法以实现成员检测运算符。如果 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

两者的区别

  1. _str_用于为最终用户创建输出,而 _repr_ 主要用于调试和开发。 _repr_ 的目标是明确无误,_str_ 是可读的
  2. _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__ 此方式可以显著地节省空间。 属性查找速度也可得到显著的提升。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大帅不是我

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

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

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

打赏作者

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

抵扣说明:

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

余额充值