魔法方法

魔法方法复习

魔法方法分类

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

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

1 构造与初始化

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

  • __ init __()
  • __ new __()
  • __ del __()

1.1 __ new __()

(1)__ new __(cls, [...]) 是在一个对象实例化的时候所调用的第一个方法,所以它才是真正意义上的构造方法。

(2)它的第一个参数是这个类,其他的参数是用来直接传递给 __ init __ 方法。

(3)__ new __ 决定是否要使用该 __ init __ 方法,因为 __ new __ 可以调用其他类的构造方法或者直接返回别的实例对象来作为本类的实例,如果 __ new __ 没有返回实例对象,则 __ init __ 不会被调用。

(4)__ new __ 主要是用于继承一个不可变的类型比如一个 tuple 或者 string。

例子:

class Person(object):
    
    def __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('wei', 24)

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

那么 __ 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,所以可以直接运算,非常方便

1.2 init()

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

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

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

3.3 del()

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

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

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

2 类的表示

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

__ str __() / __ repr __()

__ bool __()

2.1 __ str __() / __ repr __()

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

class Washer:
    def __int__(self):
        pass

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

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

a = Washer()
# print(a) <__main__.Washer object at 0x0000024AB1B360A0>
print(a)   # 我是__str__魔法方法!

我发现,要是同时写了这两个方法,只会调用__str__方法。都是用来描述类或对象的信息,那为啥要定义两个呢?

设计的目的是不一样的: 1. __repr__的目标是准确性,或者说,__repr__的结果是让解释器用的。 2. __str__的目标是可读性,或者说,__str__的结果是让人看的。更详细的信息参考:link。

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

3 访问控制

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

__ 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:
            return ValueError()
        super(Person, self).__setattr__(key, value)

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

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

4 比较操作

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

  • __ eq __()
  • __ ne __()
  • __ lt __()
  • __ gt __()

4.1 eq () / ne()

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

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

例子:

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

    def __eq__(self, other):
        return self.uid == other.uid
    
    def __ne__(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

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

4.2 __lt __() / gt()

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

补充 __ le __ 小于等于: <= __ ge __ 大于等于>=

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

5 容器类操作(重要)

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

  • __ 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中的容器类型都有哪些:

  • 字典
  • 元组
  • 列表
  • 字符串

因为他们都是可迭代的。可迭代是因为,他们都实现了容器协议,也就是下面要介绍到的魔法方法。

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 异常。

6 可调用对象

在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 __ 方法,就可以传入自定义参数实现自己的逻辑。

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

7 序列化

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)  # Attr

说明:

__ getstate __():这个例子首先初始了 Person 对象,其中包括 3 个属性:name、age、birthday。

当调用 pickle.dumps(person) 时,__ getstate __ 方法就会被调用,在这里忽略了 Person 对象的 age 属性,那么 person 在序列化时,就只会对其他两个属性进行保存。

__ setstate ():同样地,当调用 pickle.loads(pickled_person) 时, setstate __ 会被调用,其中传入的参数就是 __ getstate __ 返回的结果。

在 __ setstate __ 方法,我们从入参中取得了被序列化的 dict,然后从 dict 中取出对应的属性,就达到了反序列化的效果。

一些常用简化魔法方法理解

魔法方法:类中定义的双下方法都成为魔法方法
不需要认为调用 在特定的条件下会自动触发运行
 
__init__创建空对象之后自动触发给对象添加独有的数据
 
1.__init__
	对象添加独有数的时候自动触发
2.__str__
	对象被执行打印操作的时候自动触发
3.__call__
	对象加括号调用的时候自动触发
4.__getattr__
	对象点不存在的名字的时候自动触发
5.__getattribute__
	对象点名字就会自动触发 有它的存在就不会执行上面的__getattr__
6.__setattr__
	给对象添加或者修改数据的时候自动触发 对象.名字 = 值
7.__enter__
	当对象被当作with上下文管理操作的开始自动触发 并且该方法返回什么 as后面的变量名就会接收到什么
8.__exit__
	with上下文管理语法运行完毕之后自动触发(子代码结束)
posted @ 2023-03-09 15:16  性格如此w  阅读(77)  评论(0)    收藏  举报