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的引用计数器-1
,x.__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__
,实现反运算
- 上面的算数运算符加一个r,e.g.
- 增量运算
- 上面的算数运算符加一个i, e.g.
__iadd__
,实现增量运算
- 上面的算数运算符加一个i, e.g.
- 类型转换
__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__
- 当调用
in
和not 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)
描述符
- 一个描述符是一个有“绑定行为”的对象属性(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