面向对象之魔法方法

2023-11-17

目录

概念

 魔法方法分类

构造与初始化

 __new__()

__new__()的使用场景

__init__()

 __del__()

类的表示

__str__() / __repr__()

__bool__()

访问控制

比较操作

__eq__()

__ne__()

_lt__() / __gt__()

容器类操作(重要)

可调用对象

序列化

__getstate__():

__setstate__():

重点掌握的魔法方法有:

例题


概念

在Python的类中,以两个下划线开头、两个下划线结尾的方法,如常见的 :__init__、__str__、__del__等,就被称为「魔术方法」(Magic methods)。魔术方法在类或对象的某些事件出发后会自动执行,如果希望根据自己的程序定制特殊功能的类,那么就需要对这些方法进行重写。使用这些「魔法方法」,我们可以非常方便地给类添加特殊的功能。
 

    魔法方法其实就是类中定义的双下方法
    之所以会叫魔法方法原因是这些方法都是到达某个条件自动触发 无需调用
         eg: __init__方法在给对象设置独有数据的时候自动触发(实例化)
 

 魔法方法分类

python中常见的魔法方法大致可分为以下几类:

  • 构造与初始化
  • 类的表示
  • 访问控制
  • 比较操作
  • 容器类操作
  • 可调用对象
  • 序列化

构造与初始化

我们都知道一个最基本的魔术方法, __init__ 。通过此方法我们可以定义一个对象的初始操作。但你知道吗,当实例化我们定义的类,如x = SomeClass() 的时候, __init__ 并不是第一个被调用的方法。实际上,还有一个叫做 __new__ 的方法,来实例化这个对象。然后给在开始创建时候的初始化函数 来传递参数。在对象生命周期的另一端,也有一个 __del__ 方法。接下来看一看这三个方法:

  • __init__()
  • __new__()
  • __del__()

 __new__()


(1)__new__(cls, [...]) 是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。
(2)它的第一个参数是这个类,其他的参数是用来直接传递给 __init__ 方法。
(3)__new__ 决定是否要使用该 __init__ 方法,因为 __new__ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __new__ 没有返回实例对象,则 __init__ 不会被调用。
(4)__new__ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string。

class Person(object):

    def __new__(cls, *args, **kwargs):
        print("__new__()方法被调用了")
        print('这个是*agrs', *args)
        print('这个是kwagrs', **kwargs)
        
        # cls表示这个类,剩余所有的参数传给__init__()方法,
        # 若不返回,则__init__()不会被调用
        return object.__new__(cls)

    def __init__(self, name, age):
        print("__init__()方法被调用了")
        self.name = name
        self.age = age
        print(self.name, self.age)

p = Person("张三", 20)

# Output:
# __new__()方法被调用了
# 这个是*agrs 张三 20
# 这个是kwagrs
# __init__()方法被调用了
# 张三 20

__new__()的使用场景

当我们需要继承内置类时,例如,想要继承 int、str、tuple,就无法使用 __init__ 来初始化了,只能通过 __new__ 来初始化数据:下面这个例子实现了一个类,这个类继承了 float,之后就可以对这个类的实例进行计算了。
 

class g(float):
    """千克转克"""
    def __new__(cls, kg):
        return float.__new__(cls, kg * 2)

a = g(50) # 50千克转为克
print(a) 	# 100
print(a + 100)	# 200 由于继承了float,所以可以直接运算,非常方便!

__init__()

__init__()方法:构造器,当一个实例被创建的时候调用的初始化方法。

class Person(object):

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

p1 = Person('张三', 20)
p2 = Person('李四', 22)

 __del__()

__del__()方法:析构器,当一个实例被销毁时自动调用的方法。

class Washer:
    def __del__(self):
        """
        当删除对象时,解释器会自动调用del方法
        """
        print('对象已删除!')

haier = Washer() 
# output:
# 对象已删除!

类的表示

关于类的表示相关的魔法方法,主要包括以下几种:

  • __str__() / __repr__()
  • __bool__()

__str__() / __repr__()

这两个方法都是用来描述类或对象信息的,比如你直接实例化了一个对象,打印出来的是这个对象的地址。而要是重新在类中定义了这两个方法,那打印对象的结果就是方法返回的信息

class Washer:
    def __int__(self):
        pass

    def __repr__(self):
        return '我是__repr__()魔法方法!'

    def __str__(self):
        """
        这个str的作用就是:类的说明或对象状态的说明
        :return:
        """
        return '我是__str__魔法方法!'

haier = Washer()
# 不定义str方法,直接打印,结果是对象的内存地址,定义了str方法,
# 显示的就是str方法返回的内容
print(haier)  # 我是__str__魔法方法

要是同时写了这两个方法,只会调用__str__方法。

都是用来描述类或对象的信息,那为啥要定义两个呢?

设计的目的是不一样的: 1. __repr__的目标是准确性,或者说,__repr__的结果是让解释器用的。 2. __str__的目标是可读性,或者说,__str__的结果是让人看的。更详细的信息参考:linkicon-default.png?t=M85Bhttps://zhuanlan.zhihu.com/p/80911576

__bool__()

当调用 bool(obj) 时,会调用 __bool__() 方法,返回 True 或 False:

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __bool__(self):
        return self.uid > 10

p1 = Person(4)
p2 = Person(14)
print(bool(p1))  # False
print(bool(p2))  # True

访问控制


关于访问控制的魔法方法,主要包括以下几种:

__setattr__:定义当一个属性被设置时的行为
__getattr__:定义当用户试图获取一个不存在的属性时的行为
__delattr__:删除某个属性时调用
__getattribute__:访问任意属性或方法时调用
 

class Person(object):

    def __setattr__(self, key, value):
        """属性赋值"""
        if key not in ('name', 'age'):
            return
        if key == 'age' and value < 0:
            raise ValueError()
        super(Person, self).__setattr__(key, value)

    def __getattr__(self, key):
        """访问某个不存在的属性"""
        return 'unknown'

    def __delattr__(self, key):
        """删除某个属性"""
        if key == 'name':
            raise AttributeError()
        super().__delattr__(key)

    def __getattribute__(self, key):
        """所有属性/方法调用都经过这里"""
        if key == 'money':
            return 100
        elif key == 'hello':
            return self.say
        return super().__getattribute__(key)

p1 = Person()
p1.name = '张三'  # 调用__setattr__
p1.age = 20  # 调用__setattr__
print(p1.name, p1.age)  # 张三 20

setattr(p1, 'name', '李四')	# 调用__setattr__
setattr(p1, 'age', 30)  # 调用__setattr__
print(p1.name, p1.age)  # 李四 30

print(p1.sex)  # 调用__getattr__

# 上面只要是访问属性的地方,都会调用__getattribute__方法

比较操作

比较操作的魔法方法主要包括以下几种:

  • __eq__()
  • __ne__()
  • __lt__()
  • __gt__()

__eq__()

__eq__ 方法,可以判断两个对象是否相等:

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __eq__(self, other):
        return self.uid == other.uid

p1 = Person(1)
p2 = Person(1)
p3 = Person(2)
print(p1)
print(p1 == p2) # True
print(p2 == p3) # False

__ne__()

判断两个对象是否不相等,这个和__eq__()方法基本一样,只不过这个是反面:

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __ne__(self, other):
        """对象 != 判断"""
        return self.uid != other.uid

p1 = Person(1)
p2 = Person(1)
p3 = Person(2)

print(p1 != p2) # False
print(p2 != p3) # True

_lt__() / __gt__()

这两个方法比较对象的大小的,__lt__()为小于,__gt__()为大于:

class Person(object):
    def __init__(self, uid):
        self.uid = uid

    def __lt__(self, other):
        """对象 < 判断 根据self.uid"""
        return self.uid < other

    def __gt__(self, other):
        """对象 > 判断 根据self.uid"""
        return self.uid > other

p1 = Person(1)
p2 = Person(1)
p3 = Person(2)

print(p1 < p2) # False
print(p2 < p3) # True

print(p1 > p2) # False
print(p2 > p3) # False

容器类操作(重要)


容器类的魔法方法,主要包括:

__setitem__(self, key, value):定义设置容器中指定元素的行为,相当于 self[key] = value;

__getitem__(self, key): 定义获取容器中指定元素的行为,相当于 self[key];

__delitem__(self, key):定义删除容器中指定元素的行为,相当于 del self[key];

__len__(self):定义当被 len() 调用时的行为(返回容器中元素的个数);

__iter__(self):定义当迭代容器中的元素的行为;

__contains__(self, item):定义当使用成员测试运算符(in 或 not in)时的行为;

__reversed__(self):定义当被 reversed() 调用时的行为。

在介绍容器的魔法方法之前,首先要知道,Python 中的容器类型都有哪些,Python 中常见的容器类型有:

字典
元组
列表
字符串
因为它们都是「可迭代」的。可迭代是因为,它们都实现了容器协议,也就是下面要介绍到的魔法方法。

下面通过自己定义类实现列表,来说明这些方法的用法:
 

class MyList(object):
    """自己实现一个list"""

    def __init__(self, values=None):
        # 初始化自定义list
        self.values = values or []
        self._index = 0

    def __setitem__(self, key, value):
        # 添加元素
        self.values[key] = value

    def __getitem__(self, key):
        # 获取元素
        return self.values[key]

    def __delitem__(self, key):
        # 删除元素
        del self.values[key]

    def __len__(self):
        # 自定义list的元素个数
        return len(self.values)

    def __iter__(self):
        # 可迭代
        return self

    def __next__(self):
        # 迭代的具体细节
        # 如果__iter__返回self 则必须实现此方法
        if self._index >= len(self.values):
            raise StopIteration()
        value = self.values[self._index]
        self._index += 1
        return value

    def __contains__(self, key):
        # 元素是否在自定义list中
        return key in self.values

    def __reversed__(self):
        # 反转
        return list(reversed(self.values))

# 初始化自定义list
my_list = MyList([1, 2, 3, 4, 5])

print(my_list[0])	     # __getitem__
my_list[1] = 20		     # __setitem__

print(1 in my_list)	     # __contains__
print(len(my_list))     # __len__

print([i for i in my_list])  # __iter__
del my_list[0]	             # __del__

reversed_list = reversed(my_list) # __reversed__
print([i for i in reversed_list])  # __iter__

说明:
这个例子实现了一个 MyList 类,在这个类中,定义了很多容器类的魔法方法。这样一来,这个 MyList 类就可以像操作普通 list 一样,通过切片的方式添加、获取、删除、迭代元素了。

 

__setitem__():

当执行 my_list[1] = 20 时,就会调用 __setitem__ 方法,这个方法主要用于向容器内添加元素。

__getitem__():

当执行 my_list[0] 时,就会调用 __getitem__ 方法,这个方法主要用于从容器中读取元素。

__delitem__():

当执行 del my_list[0] 时,就会调用 __delitem__ 方法,这个方法主要用于从容器中删除元素。

__len__():

当执行 len(my_list) 时,就会调用 __len__ 方法,这个方法主要用于读取容器内元素的数量。

__iter__

这个方法需要重点关注,为什么我们可以执行 [i for i in my_list]?就是因为定义了 __iter__。
这个方法的返回值可以有两种:

1)返回 iter(obj):代表使用 obj 对象的迭代协议,一般 obj 是内置的容器对象;
2)返回 self:代表迭代的逻辑由本类来实现,此时需要重写 next 方法,实现自定义的迭代逻辑
在这个例子中,__iter__ 返回的是 self,所以需要定义 __next__ 方法,实现自己的迭代细节。

__next__ 方法使用一个索引变量,用于记录当前迭代的位置,这个方法每次被调用时,都会返回一个元素,当所有元素都迭代完成后,此时 for 会停止迭代,若迭代时下标超出边界,这个方法会返回 StopIteration 异常。

可调用对象

在Python中,方法也是一种高等的对象。这意味着他们也可以像其他对象一样被传递到方法中,这是一个非常惊人的特性。 Python中有一个特殊的魔术方法可以让类的实例的行为表现的像函数一样,你可以调用他们,将一个函数当做一个参数传到另外一个函数中等等。这个魔法方法就是 __call__(self, [args...])。

class Circle(object):

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __call__(self, x, y):
        self.x = x
        self.y = y

a = Circle(10, 20)	 # __init__
print(a.x, a.y)	# 10 20

a(100, 200)	# 此时a这个对象可以当做一个方法来执行,这是__call__魔法方法的功劳
print(a.x, a.y)	 # 100 200

这个例子首先初始化一个 Circle 实例 a,此时会调用 __init__ 方法,这个很好理解。

但是,我们对于实例 a 又做了调用 a(100, 200),注意,此时的 a 是一个实例对象,当我们这样执行时,其实它调用的就是 __call__。这样一来,我们就可以把实例当做一个方法来执行。

也就是说,Python 中的实例,也是可以被调用的,通过定义 __call__ 方法,就可以传入自定义参数实现自己的逻辑。

这个魔法方法通常会用在类实现一个装饰器、元类等场景中,当遇到这个魔法方法时,能理解其中的原理就可以了。

序列化

Python 提供了序列化模块 pickle,当使用这个模块序列化一个实例化对象时,也可以通过魔法方法来实现自己的逻辑,这些魔法方法包括:

  • __getstate__()

  • __setstate__()

 

import pickle

class Person(object):

    def __init__(self, name, age, birthday):
        self.name = name
        self.age = age
        self.birthday = birthday

    def __getstate__(self):
        # 执行 pick.dumps 时 忽略 age 属性
        return {
            'name': self.name,
            'birthday': self.birthday
        }

    def __setstate__(self, state):
        # 执行 pick.loads 时 忽略 age 属性
        self.name = state['name']
        self.birthday = state['birthday']

person = Person('李四', 20, (2017, 2, 23))
pickled_person = pickle.dumps(person) # 自动执行 __getstate__ 方法

p = pickle.loads(pickled_person) # 自动执行 __setstate__ 方法
print(p.name, p.birthday)  # 李四 (2017, 2, 23)
# 由于执行 pick.loads 时 忽略 age 属性,所以下面执行回报错
print(p.age)  # AttributeError: 'Person' object has no attribute 'age'

__getstate__():

这个例子首先初始了 Person 对象,其中包括 3 个属性:name、age、birthday。
当调用 pickle.dumps(person) 时,__getstate__ 方法就会被调用,在这里忽略了 Person 对象的 age 属性,那么 person 在序列化时,就只会对其他两个属性进行保存。

__setstate__():

同样地,当调用 pickle.loads(pickled_person) 时,__setstate__ 会被调用,其中传入的参数就是 __getstate__ 返回的结果。
在 __setstate__ 方法,我们从入参中取得了被序列化的 dict,然后从 dict 中取出对应的属性,就达到了反序列化的效果。
 

重点掌握的魔法方法有:

1.__str__

    对象被执行打印(print、前端展示)操作的时候自动触发
     该方法必须返回字符串类型的数据
  很多时候用来更加精准的描述对象

2.__del__
    对象被执行(被动、主动)删除操作之后自动执行

3.__getattr__
  对象查找不存在名字的时候自动触发

4.__setattr__
    对象在执行添加属性操作的时候自动触发    >>>    obj.变量名=变量值
 
5.__call__
    对象被加括号调用的时候自动触发

6.__enter__
    对象被执行with上下文管理语法开始自动触发 
      该方法返回什么as后面的变量名就会得到什么
  
7.__exit__
    对象被执行with上下文管理语法结束之后自动触发

8.__getattribute__
    只要对象查找名字无论名字是否存在都会执行该方法
  如果类中有__getattribute__方法 那么就不会去执行__getattr__方法

9.__new__
    产生空对象   类是先通过__new__去调用,再通过__init__执行.

例题

1.让字典具备句点符查找值的功能

	# 1.定义一个类继承字典
  class MyDict(dict):
      def __getattr__(self, item):
          return self.get(item)

      def __setattr__(self, key, value):
          self[key] = value
  '''要区别是名称空间的名字还是数据k:v键值对'''
  obj = MyDict({'name':'jason','age':18})
  # 1.具备句点符取v
  # print(obj.name)
  # print(obj.age)
  # 2.具备句点符设k:v
  # obj['gender'] = 'male'
  obj.pwd = 123  # 给字典名称空间添加名字  不是数据k:v
  print(obj)

2.  补全下列代码 使其运行不报错

class Context:
			pass
	with Context() as ctx:
			ctx.do_something()

 补全后:

  class Context:
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass
    def do_something(self):
        pass
	with Context() as ctx:
    ctx.do_something()


    

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

面向对象之魔法方法 的相关文章

随机推荐

  • Hive(完整版)

    Hive 1 基本概念 Hive本质上是基于 Hadoop 的一个数据仓库工具 可以将结构化的数据文件映射为一张表 并 提供类 SQL 查询功能 通俗一点就是Hive相当于一个hadoop的客户端 利用hdfs存储数据 利用mapreduc
  • 【CV】第 10 章:使用 R-CNN、SSD 和 R-FCN 进行目标检测

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • JAVA计算摘要,例如MD5和SHA-256

    摘要有什么用 1 保证数据的完整性 例如你发送一个100M的文件给你的B 但是你不知道B收到的是否是完整的文件 此时你首先使用摘要算法 如MD5 计算了一个固定长度的摘要 将这个摘要和文件一起发送给B B接收完文件之后 同样使用MD5计算摘
  • Android 使用SwipeRefreshLayout实现RecyclerVeiw的下拉刷新和上拉加载

    博主前些天发现了一个巨牛的人工智能学习网站 通俗易懂 风趣幽默 忍不住也分享一下给大家 点击跳转到网站 实现下拉刷新和上拉加载的完整代码如下 一 布局文件代码如下 主界面main xml代码
  • PLSQL性能优化方法

    转载自http www itfarmer com cn 1 选择最有效率的表名顺序 只在基于规则的优化器中有效 ORACLE的解析器按照从右到左的顺序处理FROM子句中的表名 FROM子句中写在最后的表 基础表driving table 将
  • stm32f407+cjson的避坑

    1 添加cjson库文件后 编译工程文件 报错 提示 CJSON test c 461 error 268 declaration may not appear after executable statement in block 如下图
  • python 使用socket建立小型聊天室

    一个聊天室 由两个部分组成 服务端和客户端 服务端接收客户端发来的消息 并将接收到的消息发送给其他客户端 客户端负责发送消息到服务端 并接收来自服务端发送的来自其他客户端的消息 示例图 服务端和客户端 这是属于一个群聊的聊天室 服务端会把每
  • 淦、我的服务器又被攻击了

    作者简介 CSDN top100 阿里云博客专家 华为云享专家 网络安全领域优质创作者 推荐专栏 对网络安全感兴趣的小伙伴可以关注专栏 网络安全入门到精通 最近老是有粉丝问我 被黑客攻击了 一定要拔网线吗 还有没有别的方法 按理说 如果条件
  • Python使用os.path.join只保留最后一个变量的原因

    在使用Python的os path join a path bbb ccc 来合并路径时 合并的结果如果只保留了最后的 ccc 是因为最后的一个变量名包含了斜杠 函数会将其识别成绝对路径 因此就会忽略前面所以的其他路径
  • File类的知识

    File 文章目录 File 概述 构造方法 抽象路径 成员方法 创建 删除 判断和获取和遍历 判断 获取 遍历 概述 java编写的一个专门用于描述计算机中的文件和文件夹的类 1 是文件和目录路径名的抽象表示 2 文件和目录是可以通过Fi
  • System has not been booted with systemd as init system (PID 1). Can‘t operate

    system has not been booted with systemd as init system pid 1 can t operate Error 报错 System has not been booted with syst
  • SMTP协议解读以及如何使用SMTP协议发送电子邮件

    电子邮件协议中POP3协议用于接收邮件 SMTP协议用于发送邮件 SMTP的全称为Simple Mail Transfer Protocol 也就是简单邮件传输协议 字如其名 相较于POP3而言 SMTP确实比较简单 这里的简单并不是指SM
  • 李承鹏小说

    1 李可乐抗拆记 城镇化进程 拆迁是个大事情 房子是普通老百姓生活中最重要的东西 一个被拆迁的社区就是一个矛盾激化的社会 有人想终于可捞一笔了 一辈子也就这么一次暴富的机会 有人喜欢老地方 不在乎钱 就不要搬 大部分都是乐意搬 只要补偿合理
  • SprongBoot集成MinIo

    SprongBoot集成MinIo 1 集成MinIo 1 1 添加依赖
  • 第八篇 VGGNet——网络实战

    文章目录 摘要 1 项目结构 2 划分训练集和测试集 3 计算mean和Standard 3 1 标准化的作用 3 2 归一化的作用 4 训练 4 1 导入项目使用的库 4 2 设置随机因子
  • 利用ANSYS随机振动分析功能实现随机疲劳分析

    ANSYS随机振动分析功能可以获得结构随机振动响应过程的各种统计参数 如 均值 均方根和平均频率等 根据各种随机疲劳寿命预测理论就可以成功地预测结构的随机疲劳寿命 本文介绍了ANSYS随机振动分析功能 以及利用该功能 按照Steinberg
  • Android SDK Android NDK 官方下载地址

    Android SDK Android NDK 官方下载地址 Android NDK r6b Windows http dl google com android ndk android ndk r6b windows zip Mac OS
  • VisualStudio(2022)- 打包项目文件为.exe安装包

    目录 前言 一 安装扩展 二 制作安装包 setup文件 2 1 添加setup项目 2 2 配置setup项目 2 3 添加项目文件到setup项目中 扩展知识 三个文件夹说明 2 4 设置项目主输出 2 5 设置快捷方式 2 6 生成安
  • 数据结构--单链表的c语言实现(超详细注释/实验报告)

    数据结构 单链表的c语言实现 超详细注释 实验报告 知识小回顾 在顺序表中 用一组地址连续的存储单元来一次存放线性表的结点 因此结点的逻辑顺序和物理顺序是一致的 而链表则不然 链表是用一组任意的存储单元来存放线性表的结点 这组储存单元可以是
  • 面向对象之魔法方法

    目录 概念 魔法方法分类 构造与初始化 new new 的使用场景 init del 类的表示 str repr bool 访问控制 比较操作 eq ne lt gt 容器类操作 重要 可调用对象 序列化 getstate setstate