• 面向对象之魔法方法


    目录

    概念

     魔法方法分类

    构造与初始化

     __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。

    1. class Person(object):
    2. def __new__(cls, *args, **kwargs):
    3. print("__new__()方法被调用了")
    4. print('这个是*agrs', *args)
    5. print('这个是kwagrs', **kwargs)
    6. # cls表示这个类,剩余所有的参数传给__init__()方法,
    7. # 若不返回,则__init__()不会被调用
    8. return object.__new__(cls)
    9. def __init__(self, name, age):
    10. print("__init__()方法被调用了")
    11. self.name = name
    12. self.age = age
    13. print(self.name, self.age)
    14. p = Person("张三", 20)
    15. # Output:
    16. # __new__()方法被调用了
    17. # 这个是*agrs 张三 20
    18. # 这个是kwagrs
    19. # __init__()方法被调用了
    20. # 张三 20

    __new__()的使用场景

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

    1. class g(float):
    2. """千克转克"""
    3. def __new__(cls, kg):
    4. return float.__new__(cls, kg * 2)
    5. a = g(50) # 50千克转为克
    6. print(a) # 100
    7. print(a + 100) # 200 由于继承了float,所以可以直接运算,非常方便!

    __init__()

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

    1. class Person(object):
    2. def __init__(self, name, age):
    3. self.name = name
    4. self.age = age
    5. p1 = Person('张三', 20)
    6. p2 = Person('李四', 22)

     __del__()

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

    1. class Washer:
    2. def __del__(self):
    3. """
    4. 当删除对象时,解释器会自动调用del方法
    5. """
    6. print('对象已删除!')
    7. haier = Washer()
    8. # output:
    9. # 对象已删除!

    类的表示

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

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

    __str__() / __repr__()

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

    1. class Washer:
    2. def __int__(self):
    3. pass
    4. def __repr__(self):
    5. return '我是__repr__()魔法方法!'
    6. def __str__(self):
    7. """
    8. 这个str的作用就是:类的说明或对象状态的说明
    9. :return:
    10. """
    11. return '我是__str__魔法方法!'
    12. haier = Washer()
    13. # 不定义str方法,直接打印,结果是对象的内存地址,定义了str方法,
    14. # 显示的就是str方法返回的内容
    15. 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:

    1. class Person(object):
    2. def __init__(self, uid):
    3. self.uid = uid
    4. def __bool__(self):
    5. return self.uid > 10
    6. p1 = Person(4)
    7. p2 = Person(14)
    8. print(bool(p1)) # False
    9. print(bool(p2)) # True

    访问控制


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

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

    1. class Person(object):
    2. def __setattr__(self, key, value):
    3. """属性赋值"""
    4. if key not in ('name', 'age'):
    5. return
    6. if key == 'age' and value < 0:
    7. raise ValueError()
    8. super(Person, self).__setattr__(key, value)
    9. def __getattr__(self, key):
    10. """访问某个不存在的属性"""
    11. return 'unknown'
    12. def __delattr__(self, key):
    13. """删除某个属性"""
    14. if key == 'name':
    15. raise AttributeError()
    16. super().__delattr__(key)
    17. def __getattribute__(self, key):
    18. """所有属性/方法调用都经过这里"""
    19. if key == 'money':
    20. return 100
    21. elif key == 'hello':
    22. return self.say
    23. return super().__getattribute__(key)
    24. p1 = Person()
    25. p1.name = '张三' # 调用__setattr__
    26. p1.age = 20 # 调用__setattr__
    27. print(p1.name, p1.age) # 张三 20
    28. setattr(p1, 'name', '李四') # 调用__setattr__
    29. setattr(p1, 'age', 30) # 调用__setattr__
    30. print(p1.name, p1.age) # 李四 30
    31. print(p1.sex) # 调用__getattr__
    32. # 上面只要是访问属性的地方,都会调用__getattribute__方法

    比较操作

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

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

    __eq__()

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

    1. class Person(object):
    2. def __init__(self, uid):
    3. self.uid = uid
    4. def __eq__(self, other):
    5. return self.uid == other.uid
    6. p1 = Person(1)
    7. p2 = Person(1)
    8. p3 = Person(2)
    9. print(p1)
    10. print(p1 == p2) # True
    11. print(p2 == p3) # False

    __ne__()

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

    1. class Person(object):
    2. def __init__(self, uid):
    3. self.uid = uid
    4. def __ne__(self, other):
    5. """对象 != 判断"""
    6. return self.uid != other.uid
    7. p1 = Person(1)
    8. p2 = Person(1)
    9. p3 = Person(2)
    10. print(p1 != p2) # False
    11. print(p2 != p3) # True

    _lt__() / __gt__()

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

    1. class Person(object):
    2. def __init__(self, uid):
    3. self.uid = uid
    4. def __lt__(self, other):
    5. """对象 < 判断 根据self.uid"""
    6. return self.uid < other
    7. def __gt__(self, other):
    8. """对象 > 判断 根据self.uid"""
    9. return self.uid > other
    10. p1 = Person(1)
    11. p2 = Person(1)
    12. p3 = Person(2)
    13. print(p1 < p2) # False
    14. print(p2 < p3) # True
    15. print(p1 > p2) # False
    16. 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 中常见的容器类型有:

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

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

    1. class MyList(object):
    2. """自己实现一个list"""
    3. def __init__(self, values=None):
    4. # 初始化自定义list
    5. self.values = values or []
    6. self._index = 0
    7. def __setitem__(self, key, value):
    8. # 添加元素
    9. self.values[key] = value
    10. def __getitem__(self, key):
    11. # 获取元素
    12. return self.values[key]
    13. def __delitem__(self, key):
    14. # 删除元素
    15. del self.values[key]
    16. def __len__(self):
    17. # 自定义list的元素个数
    18. return len(self.values)
    19. def __iter__(self):
    20. # 可迭代
    21. return self
    22. def __next__(self):
    23. # 迭代的具体细节
    24. # 如果__iter__返回self 则必须实现此方法
    25. if self._index >= len(self.values):
    26. raise StopIteration()
    27. value = self.values[self._index]
    28. self._index += 1
    29. return value
    30. def __contains__(self, key):
    31. # 元素是否在自定义list中
    32. return key in self.values
    33. def __reversed__(self):
    34. # 反转
    35. return list(reversed(self.values))
    36. # 初始化自定义list
    37. my_list = MyList([1, 2, 3, 4, 5])
    38. print(my_list[0]) # __getitem__
    39. my_list[1] = 20 # __setitem__
    40. print(1 in my_list) # __contains__
    41. print(len(my_list)) # __len__
    42. print([i for i in my_list]) # __iter__
    43. del my_list[0] # __del__
    44. reversed_list = reversed(my_list) # __reversed__
    45. 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...])。

    1. class Circle(object):
    2. def __init__(self, x, y):
    3. self.x = x
    4. self.y = y
    5. def __call__(self, x, y):
    6. self.x = x
    7. self.y = y
    8. a = Circle(10, 20) # __init__
    9. print(a.x, a.y) # 10 20
    10. a(100, 200) # 此时a这个对象可以当做一个方法来执行,这是__call__魔法方法的功劳
    11. print(a.x, a.y) # 100 200

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

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

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

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

    序列化

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

    • __getstate__()

    • __setstate__()

     

    1. import pickle
    2. class Person(object):
    3. def __init__(self, name, age, birthday):
    4. self.name = name
    5. self.age = age
    6. self.birthday = birthday
    7. def __getstate__(self):
    8. # 执行 pick.dumps 时 忽略 age 属性
    9. return {
    10. 'name': self.name,
    11. 'birthday': self.birthday
    12. }
    13. def __setstate__(self, state):
    14. # 执行 pick.loads 时 忽略 age 属性
    15. self.name = state['name']
    16. self.birthday = state['birthday']
    17. person = Person('李四', 20, (2017, 2, 23))
    18. pickled_person = pickle.dumps(person) # 自动执行 __getstate__ 方法
    19. p = pickle.loads(pickled_person) # 自动执行 __setstate__ 方法
    20. print(p.name, p.birthday) # 李四 (2017, 2, 23)
    21. # 由于执行 pick.loads 时 忽略 age 属性,所以下面执行回报错
    22. 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. # 1.定义一个类继承字典
    2. class MyDict(dict):
    3. def __getattr__(self, item):
    4. return self.get(item)
    5. def __setattr__(self, key, value):
    6. self[key] = value
    7. '''要区别是名称空间的名字还是数据k:v键值对'''
    8. obj = MyDict({'name':'jason','age':18})
    9. # 1.具备句点符取v
    10. # print(obj.name)
    11. # print(obj.age)
    12. # 2.具备句点符设k:v
    13. # obj['gender'] = 'male'
    14. obj.pwd = 123 # 给字典名称空间添加名字 不是数据k:v
    15. print(obj)

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

    1. class Context:
    2. pass
    3. with Context() as ctx:
    4. ctx.do_something()

     补全后:

    1. class Context:
    2. def __enter__(self):
    3. return self
    4. def __exit__(self, exc_type, exc_val, exc_tb):
    5. pass
    6. def do_something(self):
    7. pass
    8. with Context() as ctx:
    9. ctx.do_something()


        

  • 相关阅读:
    深圳台电:联合国的“沟通”之道
    《OnJava》——11内部类
    自定义MVC原理
    ts枚举的两种类型是什么?
    JMeter(三十九):selenium怪异的UI自动化测试组合
    基于虚幻引擎的AI训练合成数据生成
    Android学习之路(20) 进程间通信
    (七)Java算法:希尔插入排序(详细图解)
    springboot在filter中设置跨域
    pytest fixture及conftest详解一 (各个参数的使用说明)
  • 原文地址:https://blog.csdn.net/weixin_67531112/article/details/126816323