Python魔术方法详解
  yx99X8RMvAE0 2023年11月02日 73 0

前言

魔术方法(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()

执行结果:

Python魔术方法详解_魔术方法


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

执行结果:

Python魔术方法详解_魔术方法_02

因此,一般来说提供__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}")

执行结果:

Python魔术方法详解_魔术方法_03

思考:为什么list列表是不可被hash的?

本质上看list可不可hash,直接看类定义的__hash__,开发者工具上点击list直接追踪到源码

Python魔术方法详解_魔术方法_04

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

Python魔术方法详解_魔术方法_05

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为空元组")

返回结果:

Python魔术方法详解_魔术方法_06


可视化

__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

Python魔术方法详解_魔术方法_07

__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())

执行返回:

Python魔术方法详解_魔术方法_08

运算符重载

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)

返回结果:

Python魔术方法详解_魔术方法_09


容器相关方法

方法

意义

__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魔术方法详解_魔术方法_10


可调用对象

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()


执行结果:

Python魔术方法详解_魔术方法_11


实例化对象的时候,并不会调用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.")

执行返回:

Python魔术方法详解_魔术方法_12

可以看出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")

执行返回:

Python魔术方法详解_魔术方法_13


上下文应用场景

1,增强功能

在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。

2,资源管理

打开了资源需要关闭,例如文件对象,网络连接,数据库连接等

3,权限验证

在执行代码之前,做权限的验证,在__enter__中处理


反射

概述:

运行时,区别于编译时,指的是程序被加载到内存中执行的时候。

反射,reflection,指的是运行时获取类型定义的信息。

一个对象能够在运行时,像照镜子一样,反射出其类型信息。

简单说,在Python中,能够通过一个对象,找出其type,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项目

Python魔术方法详解_魔术方法_14

目录结构:

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.book_id
        }

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命令,然后进行数据库迁移,这个就不多说了,生成表格

Python魔术方法详解_魔术方法_15

然后先测试插入数据

Python魔术方法详解_魔术方法_16

Python魔术方法详解_魔术方法_17

测试更新

Python魔术方法详解_魔术方法_18

Python魔术方法详解_魔术方法_19

测试删除

Python魔术方法详解_魔术方法_20

Python魔术方法详解_魔术方法_21


反射相关的魔术方法

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

执行返回:

Python魔术方法详解_魔术方法_22

通过上面的代码示例发现一个类的属性会按照继承关系找,如果找不到,就会执行__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)

执行返回:

Python魔术方法详解_魔术方法_23

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

执行返回:

Python魔术方法详解_魔术方法_24

可以看到__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)

执行结果:

Python魔术方法详解_魔术方法_25

从执行返回上看,实例的所有的属性访问,第一个都会调用__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魔术方法详解_魔术方法_26


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项目中有用到描述器魔术方法的可以分享出来

【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  yx99X8RMvAE0   2023年11月02日   74   0   0 魔术方法
yx99X8RMvAE0