前言
魔术方法(Magic Method)是Python内置方法,格式为"方法名",不需要主动调用,存在目的是为了给Python的解释器进行调用,几乎每个魔术方法都有一个对应的内置函数,或者运算符,当我们对这个对象使用这些函数或者运算符时就会调用类中的对应的魔术方法,可以理解为重写这些python的内置函数。魔术方法可以是说Python的精华所在,这是其他语言所没有的。
分类
创建与销毁
__init__与__del__
hash
bool
可视化
运算符重载
容器和大小
可调用对象
上下文管理
反射
描述其
其他杂项
创建与销毁
__init__和__del__这两个魔术方法在对象的生命周期中起着重要的作用,"__init__"用于初始化对象的属性和状态,为对象赋予初始值,“__del__”则在对象不在被使用时执行清理操作,不过这个用的也不多,因为python有自己的垃圾回收机制。
代码示例:
class MyClass:
    def __init__(self, name):
        self.name = name
        print(f"Initializing {self.name}")
    
    def __del__(self):
        print(f"Destroying {self.name}")
    
    def greet(self):
        print(f"Hello, {self.name}!")
        
# 创建对象
obj1 = MyClass("John")
obj2 = MyClass("Alice")
# 调用对象方法
obj1.greet()
obj2.greet()执行结果:

hash
__hash__内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash
__eq__对应==操作符,判断2个对象是否相等,返回bool值
__hash__方法只是返回一个hash值作为set的key,但是去重,还需要__eq__来判断2个对象是否相等。
hash相等,只是hash冲突,不能说明两个对象是相等的。
演示代码示例:
class A:
    def __init__(self):
        self.a = 'a'
        self.b = 'b'
    def __hash__(self):
        return 1
print(hash(A()))
print(hash(A()))
s = [A(), A()]
print(set(s))执行结果:

因此,一般来说提供__hash__方法是为了作为set或者dict的key的,所以去重要同时提供__eq__方法。
演示代码示例:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    def __hash__(self):
        return hash((self.name, self.age))
    def __eq__(self, other):
        if isinstance(other, Person):
            return self.name == other.name and self.age == other.age
        return False
# 创建对象
person1 = Person("John", 30)
person2 = Person("Alice", 25)
# 输出哈希值
print(f"对象person1的hash值 {hash(person1)}")
print(f"对象person2的hash值 {hash(person2)}")
# 比较对象相等性
print(f"person1和person2相等是否成立  {person1 == person2}")执行结果:

思考:为什么list列表是不可被hash的?
本质上看list可不可hash,直接看类定义的__hash__,开发者工具上点击list直接追踪到源码

直接看__hash__,list是这样定义的

None是不可被调用的,也就是None()这样是不可以的,那断定list类型是不可被hash的
bool
__bool__内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。没有定义__bool__(),就找__len__()返回长度,非0为真,如果__len__()也没有定义,那么所有实例都返回真
代码示例:
class A:
    print("__bool__和__len__都未定义,bool调用恒为True")
print(bool(A()))
class B:
    def __bool__(self):
        print("__bool__返回False,bool调用恒为False")
        return False
print(bool(B()))
class C:
    def __len__(self):
        print("__bool__未定义,找到__len__,__len__返回值非0,bool调用恒为True")
        return 1
print(bool(C()))
# 定义空字典
a = {}
# 定义空列表
b = []
# 定义空元组
c = ()
# 以后判断这个3种数据类型是否有返回值直接通过bool方法来判断
if not bool(a):
    print("a为空字典")
if not bool(b):
    print("b为空列表")
if not bool(c):
    print("c为空元组")返回结果:

可视化
__repr__内建函数repr()对一个对象获取字符串表达式。如果一个类定义了__repr()但没有定义__str__,那么在请求该类的实例的"非正式"的字符串表示时也将调用__repr__() ,如果__repr__也没有定义,就直接返回object的定义就是显示内存地址信息
__str__str()函数,内建函数format,print()函数调用,需要返回对象的字符串表达。如果没有定义就去调用__repr__方法返回字符串表达,如果__repr__没有定义就直接返回对象的内存地址信息
__bytes__bytes的时候,返回一个对象的bytes表达,即返回bytes对象
实际应用,新建一个flask项目,在app.py在写一个简单的登入登出功能
import os
import pickle
from flask import Flask, request, redirect
from flask_login import LoginManager, login_user, UserMixin, login_required, current_user, logout_user
from redis import Redis
app = Flask(__name__)
# 尤其在涉及(flask-WTF)登陆页面提交敏感信息时,一定要设置密钥
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or 'westos secret key'
login_manager = LoginManager(app)  # Setup a Flask-Login Manager
login_manager.login_view = "login"  # required_login 校验不通过,默认跳转
user_con_redis = Redis(
    host='10.0.0.55',  # 服务器地址
    port=6379,  # 服务器端口
    password='',  # redis密码
    db=11
)
class User(UserMixin):
    """
    flask-login 必要 User类
    """
    def __init__(self, username):
        self.username = username
    def __repr__(self):
        return self.username
    def get_id(self):
        return self.username
@login_manager.user_loader
def load_user(id):
    """
    flask-login必要,重要的回调函数
    从redis中返回User类对象,或者None
    :param id:
    :return:
    """
    redis_id = user_con_redis.get(id)
    if redis_id is None:
        return None
    else:
        try:
            user = pickle.loads(redis_id)  # 使用 pickle.loads 方法 将 存储在redis中的序列化对象读出来,还原成User对象
            return user
        except Exception as E:
            return None
def save_user(username):
    """
    登录成功后创建User类对象,并序列化保存在redis中
    :param username:
    :return:user
    """
    user = User(username)
    print(f"初始化User类的时候返回:  {user}")
    user_dump = pickle.dumps(user)  # 使用 pickle.dumps 方法 将 User 对象序列化存储在redis中
    user_con_redis.set(username, user_dump)
    return user
@app.route('/')
@login_required
def index():
    return "hello world"
@app.route('/login', methods=["GET", "POST"])
def login():
    username = request.args.get('user', None)
    if not username:
        return "user参数不能为空"
    else:
        user = save_user(username)  # 保存登录对象
        login_user(user)  # 登录操作
        return redirect('/')
@login_required
@app.route('/logout')
def logout():
    if current_user.is_authenticated:
        user_id = current_user.username
        # --start-- 本地系统logout
        logout_user()
        user_con_redis.delete(user_id)
    return redirect('/')
if __name__ == '__main__':
    app.run(debug=True)流量器访问/login?user=linghuchong,会拿到user参数值进行登陆保存,调用到save_user,save_user里会初始化User类,然后通过__repr__返回一个username给user

__str__这里我举例自定义异常类
class DuplicateError(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return "DuplicateError {} is invalid input".format(repr(self.value))
# 模拟下数据保存,这里有一个列表lst 为[1, 2, 3],然后我们拿到一个值为3的变量x,如果x在这个列表里的话则退出,否则将x加入到列表lst中
def add_item():
    lst = [1, 2, 3]
    x = 3
    if x in lst:
        return {"success": False, "msg": "{}".format(DuplicateError("列表lst中 为{}的元素已存在".format(x)))}
    else:
        lst.append(x)
        return {"success": True, "msg": lst}
print(add_item())执行返回:

运算符重载
operrator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作
| 运算 | 特殊方法 | 含义 | 
| <,<=,==,>,>=,!= | __lt__,__le__,__eq__,__gt__,__ge__,__ne__ | 比较运算符 | 
| +,-,*,/,%,//,** divmod | __add_,__sub__,__mul__,__truediv__,__mod__,__floordiv__,__pow__,__divmod__ | 算数运算符,移位,位运算符 | 
| +=,-=,*=,/=,%-,//=,**= | __iadd__,__isub__,__imul__,__itrudiv__,__imod__,__ifloordiv__,__ipow__ | 
代码示例:
class A:
    def __init__(self, x):
        self.x = x
    def __sub__(self, other):
        return A(self.x - other.x)
    def __eq__(self, other):
        return self.x == other.x
    def __ne__(self, other):
        return self.x != other.x
    def __lt__(self, other):
        return self.x < other.x
    def __repr__(self):
        return str(self.x)
    def __iadd__(self, other):
        return A(self.x + other.x)
a1 = A(4)
a2 = A(5)
a3 = A(6)
# 算数运算-示例
print(a1 - a2)
# 等同于执行
print(a1.__sub__(a2))
print("================================================")
# 比较运算==和!=示例
print(a1 == a2)
print(a1 != a2)
print("================================================")
# 比较运算<示例
lst = [a1, a2, a3]
print(sorted(lst))
print("===================================================")
a1 += a2
print(a1)返回结果:

容器相关方法
| 方法 | 意义 | 
| __len__ | 内建函数len(),返回对象的长度(>=0的整数),其实即使把对象当作容器类型看,就如同list或者dict. bool()函数调用的时候,如果没有__bool__()方法则会看__len__()方法是否存在,存在返回非0为真 | 
| __iter__ | 迭代容器时,调用,返回一个新的迭代器对象 | 
| __contains__ | in成员运算符,没有实现,就调用__iter__方法遍历 | 
| __getitem__ | 实现self[key]访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为hanshable。 key不在引发KeyError异常 | 
| __setitem__ | 和__getitem__的访问类似,是设置值的方法 | 
| __missing__ | 字典使用__getitem__()调用时,key不存在执行该方法 | 
模拟购物车类Cart
class Item:
    """
    商品
    """
    def __init__(self, name, **kwargs):
        self.name = name
        self._spec = kwargs
    def __repr__(self):
        return "{} = {}".format(self.name, self._spec)
class Cart:
    def __init__(self):
        self.items = []
    def __len__(self):
        return len(self.items)
    def additem(self, item):
        """
        购物车添加商品
        :param item:
        :return:
        """
        self.items.append(item)
    def __add__(self, other):
        if isinstance(other, Item):
            self.items.append(other)
            return self
    def __getitem__(self, index):  # index
        return self.items[index]
    def __setitem__(self, key, value):
        self.items[key] = value
    def __iter__(self):
        return iter(self.items)
    def __missing__(self, key):
        print("missing key" + key)
cart = Cart()
cart.additem('a')
print(len(cart))
for x in cart:
    print(x)
cart[0] = 500
print(cart[0])执行返回:

可调用对象
Python中一切皆对象,函数也不例外。
| 方法 | 意义 | 
| __call__ | 类中实现该方法,实例就可以像函数一样调用 | 
def foo():
	print(foo.__module__, foo.__name__)
  
 foo()
 #等价于
 foo.__call__()函数即对象,对象foo加上(),就是调用调用对象的__call__()方法
写一个游戏加载功能简单示例:
import requests
class Gm_Reload:
	"""
    热加载接口
	"""
    def __call__(self, *args, **kwargs):  # 使实例可作为函数调用
        # 拿到服务器ip和端口
        self.serverip, self.port = args
        if self.port != '' and self.serverip != '':
            url = "http://%s:%d/reload" % (self.serverip, self.port)
            # print(url)
            s = requests.session()
            s.auth = ('xxx', 'xxx')
            data = {'sign': 'xxxxx'}
            try:
                req = s.post(url=url, data=data, timeout=10).json()
                if not req["success"]:
                    return {"success": False, "msg": f"${self.serverip} ${self.port}加载失败,请检查"}
                else:
                    return {"success": True, "msg": f"${self.serverip} ${self.port}加载成功,请检查"}
            except Exception as e:
                return {"success": False, "msg": str(e)}
if __name__ == "__main__":
	# Gm_Reload()还是一个对象
    print(Gm_Reload()("127.0.0.1", 8001))  # 调用的时候传入参数上下文管理
文件IO操作可以对文件对象使用上下文管理,使用with..as 语法。
with open("test") as f:
	pass上下文管理对象
当一个对象同时实现了__enter__()和__exit__()方法,它就属于上下文管理的对象
| 方法 | 意义 | 
| __enter__ | 进入与此对象相关的上下文。如果存在该方法,with语法会把该方法的返回值 作为绑定到as子句中指定的变量上 | 
| __exit__ | 退出与此对象相关的上下文 | 
这里我写一个用于pymysql数据库会话管理的一个封装类
import pymysql
class Session:
    def __init__(self, conn: pymysql.connections.Connection):
        # 接受一个pymysql的连接对象作为参数,并初始化连接和游标。
        self.conn = conn
        self.cursor = None
    def execute(self, query, args=None):
        # 接受一个查询字符串 query 和查询参数 args,并使用游标执行查询操作
        if self.cursor is None:
            self.cursor = self.conn.cursor()
        self.cursor.execute(query, args)
    def __enter__(self):
        # 支持上下文管理器,返回一个游标对象,使得可以使用with 语句来管理数据库会话。
        self.cursor = self.conn.cursor()
        return self.conn.cursor()
    def __exit__(self, exc, exc_val, exc_tb):
        # 在上下文管理器退出时自动提交或回滚事务。它接收三个参数,分别是异常类型、异常值和异常追踪信息。
        # 如果异常类型不为 None则回滚事务;否则提交事务。然后关闭游标
        if exc:
            self.conn.rollback()
        else:
            self.conn.commit()
        self.cursor.close()
# 使用
# 创建一个pymysql 连接对象
conn = pymysql.connect(host='10.0.0.55',
                       user='root',
                       password='123456',
                       db='shop')
# 查询语句
query = "select * from users_myuser;"
with Session(conn) as session:
    session.execute(query)
    result = session.fetchall()执行结果:

实例化对象的时候,并不会调用enter,进入with语句调用__enter__方法,然后执行语句体,最后离开with语句块的时候调用__exit__方法。
上下文管理的安全性
看看异常对上下文的影响
演示代码示例:
class Point:
    def __init__(self):
        print("init")
    def __enter__(self):
        print("enter  " + self.__class__.__name__)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit  " + self.__class__.__name__)
with Point() as f:
    raise Exception("error")
    print("do sth.")执行返回:

可以看出enter和exit照样执行,上下文管理是安全的
__enter__方法和__exit__方法的参数
__enter__方法没有其他参数
__exit__方法有3个参数:
def exit(self, exc_type, exc_val, exc_tb)
这三个参数都与异常有关。
如果该上下文退出时没有异常,这3个参数都为None。
如果有异常,参数意义如下
exc_type,异常类型
exc_value,异常的值
traceback,异常的追踪信息
__exit__方法返回一个等效True的值,则压制异常;否则继续抛出异常
拿上面的例子演示:
class Point:
    def __init__(self):
        print("init")
    def __enter__(self):
        print("enter  " + self.__class__.__name__)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print("exit  " + self.__class__.__name__)
        return True
with Point() as f:
    raise Exception("error")
    print("do sth.")
print("outer")
执行返回:

上下文应用场景
1,增强功能
在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
2,资源管理
打开了资源需要关闭,例如文件对象,网络连接,数据库连接等
3,权限验证
在执行代码之前,做权限的验证,在__enter__中处理
反射
概述:
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义的信息。
一个对象能够在运行时,像照镜子一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其tyoe,class,attribute或method的能力,成为反射或者自省。
具有反射能力的函数有;type(),isinstance(),callable(),dir(),getattr()
反射相关的函数和方法
| 内建函数 | 意义 | 
| getattr(object, name[,default]) | 通过name返回object的属性值。当属性不存在,将使用default 返回,如果没有default,则抛出AttributeError。name必须为字符串 | 
| setattr(object, name, value) | object的属性存在,则覆盖,不存在,新增 | 
| hasattr(object, name) | 判断对象是否有这个名字的属性,name必须为字符串 | 
代码示例:接下来,我们创建一个flask项目

目录结构:
app.py
import os
from flask import Flask
from flask import request
from extension import db
from ActDB import is_in
from ActDB import db_update
from models import Book
app = Flask(__name__)
# 尤其在涉及(flask-WTF)登陆页面提交敏感信息时,一定要设置密钥
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY') or 'westos secret key'
db.init_app(app)
app.config['SQLALCHEMY_DATABASE_URI'] = "sqlite:///books.sqlite"
# 是否自动提交、
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
@app.route("/book/update/<int:id>")
def update_book(id):
    """
    更新书本信息
    :param id:
    :return:
    """
    book_name = '荷塘月色'
    book_type = '散文'
    author = '朱自清'
    isinbook = is_in(Book, obj=True, id=id)
    if isinbook["success"]:
        objbook = isinbook["msg"]
        res = db_update(objbook, "update", book_name=book_name, book_type=book_type, author=author)
        if res["msg"]:
            return {"success": True, "msg": res["msg"]}
        else:
            return {"success": False, "msg": res["msg"]}
    else:
        return {"success": False, "msg": isinbook["msg"]}
@app.route("/book/insert")
def insert_book():
    rets = [
        (1, '001', '活着', '小说', 39.90, '余华', '某某出版社'),
        (2, '002', '三体', '科幻', 99.8, '刘慈欣', '重庆出版社')
    ]
    try:
        for ret in rets:
            book = Book(*ret)
            db.session.add(book)
        db.session.commit()
        return {"success": True, "msg": "测试数据插入完毕"}
    except Exception as e:
        return {"success": False, "msg": "插入测试数据失败: {}".format(str(e))}
@app.route("/book/delete/<int:id>")
def delete_book(id):
    """
    删除书本信息
    :param id:
    :return:
    """
    isinbook = is_in(Book, obj=True, id=id)
    if isinbook["success"]:
        objbook = isinbook["msg"]
        res = db_update(objbook, "delete")
        if res["msg"]:
            return {"success": True, "msg": res["msg"]}
        else:
            return {"success": False, "msg": res["msg"]}
    else:
        return {"success": False, "msg": isinbook["msg"]}
if __name__ == '__main__':
    app.run(debug=True)
models.py
# -*- coding: utf-8 -*-
from extension import db
class Book(db.Model):
    __tablename__ = "book"
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    book_number = db.Column(db.String(255), nullable=False)
    book_name = db.Column(db.String(255), nullable=False)
    book_type = db.Column(db.String(255), nullable=False)
    book_prize = db.Column(db.Float, nullable=False)
    author = db.Column(db.String(255))
    book_publisher = db.Column(db.String(255))
    def __init__(self, id, book_number, book_name, book_type, book_prize, author, book_publisher):
        """
        :param is:
        :param book_number:
        :param book_name:
        :param book_type:
        :param book_prize:
        :param author:
        :param book_publisher:
        """
        self.book_id = id
        self.book_number = book_number
        self.book_name = book_name
        self.book_type = book_type
        self.book_prize = book_prize
        self.author = author
        self.book_publisher = author
        self.book_publisher = book_publisher
    def to_json(self):
        return {
            "book_id": self
        }
extension.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
manager.py
from flask_migrate import Migrate
from app import app
from extension import db
migrate = Migrate(app, db, render_as_batch=True)ActDB.py
from models import *
import inspect
from extension import db
"""
is 开头表示 查询某项资源是否存在
"""
def get_all_model():
    """
    获取 model 中的所有class类
    :return:
    """
    modelclasses = []
    for cls in globals().values():
        if inspect.isclass(cls) and cls.__module__ == 'models':
            modelclasses.append(cls)
    return modelclasses
def is_in(model, obj=False, **kwargs):
    """
    model为model类,如Book
    :param model:
    :param obj:
    :param kwargs:
    :return:
    """
    modelclasses = get_all_model()
    if model in modelclasses:
        pass
    else:
        return {"success": False, "msg": "未知模型"}
    query = model.query
    for key, value in kwargs.items():
        if hasattr(model, key):  # 防止参数中存在模型中不存在的参数
            column = getattr(model, key)
            query = query.filter(column == value)
    result = query.all()
    if len(result) == 1 and obj:  # obj为True且返回长度为1时,返回对象
        return {"success": True, "msg": result[0]}
    if len(result) > 0:
        return {"success": True, "msg": "数据存在 " + model.__tablename__}
    else:
        return {"success": False, "msg": "数据不存在"}
def db_update(obj, db_opt, **kwargs):
    """
    根据已有的对象更新或删除数据
    :param obj:
    :param db_opt:
    :param kwargs:
    :return:
    """
    try:
        # 操作类型判断
        if db_opt == "update":  # 更新数据
            for key, value in kwargs.items():
                if hasattr(obj, key):
                    setattr(obj, key, value)
            db.session.commit()
            return {"success": True, "msg": "数据更新成功"}
        elif db_opt == "delete":  # 删除数据
            db.session.delete(obj)
            db.session.commit()
            return {"success": True, "msg": "数据删除成功"}
        else:
            return {"success": False, "msg": "未知操作{}".format(db_opt)}
    except Exception as e:
        return {"success": False, "msg": str(e)}
使用 set FLASK_APP=manager命令,然后进行数据库迁移,这个就不多说了,生成表格

然后先测试插入数据


测试更新


测试删除


反射相关的魔术方法
__getattr__(),__setattr__(),__delattr__()这三个魔术方法
class Base:
    n = 5
class A(Base):
    m = 6
    def __init__(self, x):
        self.x = x
    def __getattr__(self, item):
        return '__getattr__', item
print(A(10).x)
print(A(10).n)
print(A(10).y)执行返回:

通过上面的代码示例发现一个类的属性会按照继承关系找,如果找不到,就会执行__getattr__()方法,如果没有这个方法就会抛出AttributeError异常表示找不到属性。
查找属性顺序为:
instance.dict->instance.class.dict->继承的祖先类(直到object)的dict->调用getattr()
代码示例:
class Base:
    n = 5
class A(Base):
    m = 6
    def __init__(self, x):
        self.x = x
    def __getattr__(self, item):
        return '__getattr__', item
    def __setattr__(self, key, value):
        print('__setattr__', key, value)
        self.__dict__[key] = value
    # def __delattr__(self, item):
    #     print('delattr__', item)
a = A(10)
print(a.y)
a.y = 100
print(a.y)执行返回:

__setattr__()方法,可以拦截对实例属性的增加,修改操作,如果要设置生效,需要自己操作实例的__dict__.
代码示例:
class Base:
    n = 5
class A(Base):
    m = 6
    def __init__(self, x):
        self.x = x
    def __getattr__(self, item):
        return '__getattr__', item
    def __setattr__(self, key, value):
        print('__setattr__', key, value)
        self.__dict__[key] = value
    def __delattr__(self, item):
        print('delattr__', item)
a = A(10)
del a.x
print(a.__dict__)
print(A.__dict__)
del A.m
print(A.__dict__)执行返回:

可以看到__delattr__可以阻止通过实例删除属性的操作,但是通过类依然可以删除属性。
代码示例:
class Base:
    n = 5
class A(Base):
    m = 6
    def __init__(self, x):
        self.x = x
    def __getattribute__(self, item):
        print("__getattribute__", item)
        raise AttributeError(item)
    def __getattr__(self, item):
        return '__getattr__', item
    def __setattr__(self, key, value):
        print('__setattr__', key, value)
    def __delattr__(self, item):
        print('delattr__', item)
a = A(10)
print(a.x)执行结果:

从执行返回上看,实例的所有的属性访问,第一个都会调用__getattribute__方法,它阻止了属性的查找,该方法返回值或者抛出一个AttributeError异常。
它的return值将作为属性查找的结果,如果抛出AttributeError异常,则会直接调用__getattr__方法,因为表示属性没有找到。
总结
| 魔术方法 | 意义 | 
| __getattr__() | 当通过搜索实例,实例的类及祖先类查不到属性,就会调用此方法 | 
| __setattr__() | 通过.访问实例属性,进行增加,修改都要调用它 | 
| __delattr__() | 当通过实例来删除属性时调用此方法 | 
| __getattribute__ | 实例所有的属性调用都从这个方法开始 | 
属性查找顺序
实例调用__getattribute__()->instance.__dict__->instance.__class__.__dict__-->继承的祖先类(直到object)的__dict__-->调用__getattr__()
描述器定义
Python中,一个类实现了__get__,__set__,__delete__三个方法中的任何一个方法,就是描述器。
如果仅实现了__get__,就是非数据描述符non-data descriptor;
同时实现了__get__,__set__ 就是数据描述符data descriptor.
如果一个类的类属性设置为描述器,那么它被称为owner属主。
属性的访问顺序
实例的__dict__优先于非数据描述器,数据描述器优先于实例的__dict__
class A:
    def __init__(self):
        print(111111111111111111)
        self.a1 = 'a1'
        print('A.init')
    def __get__(self, instance, owner):
        print(22222222222222222222)
        print("A.__get__{},{},{}".format(self, instance, owner))
        return self  # 解决返回值None的问题
    def __set__(self, instance, value):
        print(33333333333333333333333)
        print('A.__set__ {},{},{}'.format(self, instance, value))
        self.data = value
class B:
    x = A()
    def __init__(self):
        print(4444444444444444444444)
        print('B.init')
        self.x = 'b.x'  # 增加实例属性x
print('=' * 20)
b = B()
print(b.x)
print(b.x.a1) 执行返回:

Python中的描述器
描述器在python中应用非常广泛,python的方法都实现为非数据描述器;因此,实例可以重新定义和覆盖方法,这允许单个实例获取与同一个类的其他实例不同的行为
property()函数实现为一个数据描述器,因此,实例不能覆盖属性的行为
代码示例:
class A:
    @classmethod
    def foo(cls):  # 非数据描述器
        pass
    @staticmethod  # 非数据描述器
    def bar():
        pass
    @property  # 数据描述器
    def z(self):
        return 5
    def getfoo(self):  # 非数据描述器
        return self.foo
    def __init__(self):  # 非数据描述器
        self.foo = 100
        self.bar = 200
        #self.z = 300
a = A()
print(a.__dict__)foo,bar都可以在实例中覆盖,但是z不可以
描述器要真正理解起来还是比较麻烦的,再者项目中实际需要用到描述器的魔术方法我看下了下基本上没那么写过,可能是个人技术视野的局限性吧,如果有在python web项目中有用到描述器魔术方法的可以分享出来










