当前位置: 首页 > 工具软件 > Magic > 使用案例 >

magic method in Python

钱建本
2023-12-01

link:

什么是魔术方法

  • python中一些内置的以双下划线开头和结尾的方法

构造和初始化

  • __init__
    • 类的初始化方法,可以给一些类的属性值进行初始化
    • 不允许有返回值return,否则会报错__init__() should return None,...
  • __new__(cls, *args, **kwargs)
    • 一个对象实例化时调用的第一个方法
    • 第一个参数是这个本身
    • 典型实现会调用super().__new__(cls, *args, **kwargs)返回一个新的实例
    • 如果返回一个cls实例,则新实例的__init__方法将会被调用
    • 如果未返回一个cls实例,则不会调用__init__方法
    • 目的是允许非可变类型的子类定制化实例的创建过程
  • __del__
    • 实例对象被销毁时调用
    • del x的区别: del x将x的引用计数器-1x.__del__()仅在引用计数器变为零时调用
    • 如果解释器退出时,对象仍然存在,则不能保证__del__会被执行

用于比较的方法

  • __cmp__(self, other)
    • python3中不再使用
  • __lt__, __gt__, __le__, __ge__, __eq__, __ne__
  • @total_ordering
    • 无需定义所有的比较方法

数值处理

  • 一元操作符
    • __pos__(self), __neg__, __abs__, __invert__
  • 算数操作符
    • __add__(self, other), __sub__, __mul__, __floordiv__, __div__
    • __mod__, __pow__, __lshift__, __rshift__, __or__, __xor__
  • 反运算
    • 上面的算数运算符加一个r,e.g. __radd__,实现反运算
  • 增量运算
    • 上面的算数运算符加一个i, e.g. __iadd__,实现增量运算
  • 类型转换
    • __int__, __float__, __long__, __complex__
    • __oct__, __hex__, __index__

字符串表示

  • __str__, __repr__
    • str()调用前者,如果没有实现则调用后者
    • 前者返回人类可读,后者返回机器可读
In[19]: class Base:
   ...:     greeting = "hello world"
   ...: 
   ...:     def __repr__(self):
   ...:         return f"{self.greeting} in English. -- by repr"
   ...:     
In[20]: b=Base()
In[21]: b
Out[22]: hello world in English. -- by repr

属性访问

  • __getattribute__(self, item)
    • 用户访问对象的属性时调用此方法,无论对象是否存在
    • 典型实现是调用return super(Base, self).__getattribute__(item)返回对象的属性
  • __getattr__(self, name)
    • 当用户访问的属性不存在时,会调用此方法
  • __setattr__(self, key, value), __delattr__(self, item)
    • 在使用.访问、设置、删除属性的时候会触发相应的魔法方法
    • 以上三个方法使用不当会导致无限递归
class Base(object):
    p = "hello world"

    def __getattr__(self, item):
        print("__getattr__")
        return "error"

    def __getattribute__(self, item):
        print('__getattribute__')
        return super(Base, self).__getattribute__(item)
    
    def __setattr__(self, key, value):
        # self.key = value
        self.__dict__[key] = value

    def __delattr__(self, item):
        # del self.item
        self.__dict__.pop(item)

if __name__ == '__main__':
    b = Base()
    print(b.k)

"""返回结果
__getattribute__
__getattr__
error
"""

自定义序列

不可变容器

  • __len__
  • __getitem__(self, item)
    • 可以使用[]的方式访问对象的属性

可变容器(包含不可变容器需要实现的方法)

  • __setitem__(self, key, value)
  • __delitem__(self, key)

迭代器

  • __iter__
    • 表明对象是一个Iterable,返回可迭代对象,可以是对象本身self
  • __next__
    • 表明对象是一个Iterator,返回下一个迭代对象,迭代结束返回StopIteration
  • __contains__
    • 当调用innot in来测试成员是否存在时候

反射

  • __instancecheck__(self, instance)
    • isinstance(c, Base) 会调用 type(Base)__instancecheck__方法,所以该方法一般用在元类中
  • __subclasscheck__(self, subclass)
    • 同上

可调用对象

  • __call__(self, *args, **kwargs)
    • 让类的实例的行为表现的像函数一样可以调用

会话管理

  • __enter__(self)
  • __exit__(self, exc_type, exc_val, exc_tb)

描述符

Python 描述符(Descriptor) 附实例

  • 一个描述符是一个有“绑定行为”的对象属性(object attribute),它的访问控制会被描述器协议方法重写。
  • 任何定义了__get__, __set__ 或者 __delete__任一方法的类称为描述符类,其实例对象便是一个描述符,这些方法称为描述符协议
  • 当对一个实例属性进行访问时,Python 会按 obj.__dict__ → type(obj).__dict__ → type(obj)的父类.__dict__(实例属性->类属性->父类属性) 顺序进行查找,如果查找到目标属性并发现是一个描述符,Python 会调用描述符协议来改变默认的控制行为
  • 描述符是@property, @classmethod, @staticmethod 和 super的底层实现机制。
  • 同时定义了__get__ 和 __set__的描述符称为数据描述符(data descriptor)
  • 仅定义了__get__的称为非数据描述符(non-data descriptor)
  • 两者区别在于:如果obj.__dict__中有与描述符同名的属性,若描述符是数据描述符,则优先调用描述符,若是非数据描述符,则优先使用obj.__dict__中属性。
  • 描述符协议必须定义在类的层次上,否则无法被自动调用。

描述符协议

  • __get__(self, instance, owner):
    • self: 描述符类的实例对象
    • instance: 使用描述符的类的实例对象
    • owner: 使用描述符的类
  • __set__(self, instance, value)
    • self instance 同上
    • value 设置的值
  • __delete__(self, instance)

示例

# !/usr/bin/env python
# -*- coding: utf-8 -*-


class Base:
    p = "hello base."

    def __init__(self, data):
        self.data = data

    def __get__(self, instance, owner):
        print("Base __get__")
        print(f"self is {self}, instance is {instance}, owner is: {owner}")
        return self.data

    def __set__(self, instance, value):
        print("Base __set__")
        print(f"self is {self}, instance is {instance}, value is: {value}")

    def __getattribute__(self, item):
        print('Base __getattribute__')
        return super(Base, self).__getattribute__(item)


class Child:
    c = Base("I am child")

    # def __init__(self):
    #     self.c = "I am child in init."


if __name__ == '__main__':
    t = Child()
    print(t.c)

'''
Base __get__
self is <__main__.Base object at 0x1087a1ef0>, instance is <__main__.Child object at 0x1087a1b70>, owner is: <class '__main__.Child'>
Base __getattribute__
I am child
'''
  • t.c 按照访问顺序,先找类的实例t的属性c -> Child的属性c -> Base的属性c,找到了Child的属性c
  • 发现属性是一个描述符,调用描述符协议改变调用行为,__get__ -> self.data -> __getattribute__(只要访问属性都会调用)
# !/usr/bin/env python
# -*- coding: utf-8 -*-


class Base:
    p = "hello base."

    def __init__(self, data):
        self.data = data

    def __get__(self, instance, owner):
        print("Base __get__")
        print(f"self is {self}, instance is {instance}, owner is: {owner}")
        return self.data

    def __set__(self, instance, value):
        print("Base __set__")
        print(f"self is {self}, instance is {instance}, value is: {value}")

    def __getattribute__(self, item):
        print('Base __getattribute__')
        return super(Base, self).__getattribute__(item)


class Child:
    c = Base("I am child")

    def __init__(self):
        self.c = "I am child in init."


if __name__ == '__main__':
    t = Child()
    print(t.c)

'''
Base __set__
self is <__main__.Base object at 0x108dc9a58>, instance is <__main__.Child object at 0x108dc9ef0>, value is: I am child in init.
Base __get__
self is <__main__.Base object at 0x108dc9a58>, instance is <__main__.Child object at 0x108dc9ef0>, owner is: <class '__main__.Child'>
Base __getattribute__
I am child
'''
  • 先找到实例属性c,实例属性和描述符属性同名,且描述符属于数据描述符,所以调用描述符协议改变默认控制行为
  • self.c -> __set__ -> __get__ -> __getattribute__

class Base:
    p = "hello base."

    def __init__(self, data):
        self.data = data

    def __get__(self, instance, owner):
        print("Base __get__")
        print(f"self is {self}, instance is {instance}, owner is: {owner}")
        return self.data

    # def __set__(self, instance, value):
    #     print("Base __set__")
    #     print(f"self is {self}, instance is {instance}, value is: {value}")

    def __getattribute__(self, item):
        print('Base __getattribute__')
        return super(Base, self).__getattribute__(item)

    def __getattr__(self, item):
        print('Base __getattr__')
        return "error."


class Child:
    c = Base("I am child")

    def __init__(self):
        self.c = "I am child in init."


if __name__ == '__main__':
    t = Child()
    print(t.c)

'''
I am child in init.
'''
  • 同上,但此时Base为非数据描述符,优先使用实例属性

描述符应用

  • ORM
 类似资料:

相关阅读

相关文章

相关问答