导航菜单

  • 1.VSCode开发
  • 2.什么是Python?
  • 3.请详细解释Python代码的执行过程
  • 4.请详细解释解释型语言与编译型语言的主要区别
  • 5.你知道哪些Python的编码规范?
  • 6.数据类型
  • 7.Python中如何声明多个变量并赋值
  • 8.Python有哪些内置数据结构
  • 9.!=和is not运算符有什么区别?
  • 10.进制
  • 11.编码
  • 12.print
  • 13.Python中break、continue、pass有什么作用?
  • 14.namedtuple有什么作用?
  • 15.Python的range函数如何运用?
  • 16.Python中join()和split()函数有什么区别?
  • 17.Python中如何将字符串转换为小写?
  • 18.Python中如何删除字符串中的前置空格?
  • 19.Python中如何使用索引反转字符串
  • 20.什么是Python的成员运算符?
  • 21.请详细说明Python中逻辑运算符(`and`、`or`、`not`)
  • 22.什么是Python的关系运算符?
  • 23.什么是Python的赋值和算术运算符?请详细说明赋值运算符、算术运算符的种类、使用方法、优先级规则。
  • 24.请详细解释Python中整数除法、取模运算和幂运算三个运算符。
  • 25.如何在Python中表示和转换不同进制的数字
  • 26.什么是Python的位运算符?
  • 27.请详细说明Python中三元表达式(Ternary Expression)的工作原理
  • 28.Python中如何实现switch语句?
  • 29.什么是Python的负索引?
  • 30.Python中如何实现字符串替换操作?
  • 31.Python中append、insert和extend有什么区别?
  • 32.请详细说明Python中`enumerate()`函数的作用
  • 33.Python中remove、del和pop有什么区别?
  • 34.Python中如何更改列表元素的数据类型?
  • 35.请详细说明Python中列表(list)和元组(tuple)的区别
  • 36.什么是Python元组的解封装?
  • 37.详细说明Python字典
  • 38.Python中KeyError、TypeError和ValueError有什么区别?
  • 39.请详细解释Python中`read()`、`readline()`和`readlines()`三种文件读取方法
  • 40.Python中iterable、iterator和generator的区别与联系
  • 41.Python中如何读取大文件?
  • 42.请详细解释Python中浅拷贝(shallow copy)和深拷贝(deep copy)的区别
  • 43.什么是Python的Lambda函数?
  • 44.Python中的reduce函数有什么作用?
  • 45.Python的zip函数有什么作用?
  • 46.请详细解释Python中`any()`和`all()`内置函数的作用
  • 47.为什么Python中没有函数重载?
  • 48.请介绍Python中变量的作用域(Scope)?
  • 49.什么是Python的闭包
  • 50.请详细说明Python中的内存管理机制
  • 51.请详细说明Python程序退出时内存的释放情况
  • 52.Python中是否有严格意义上的main函数?
  • 53.什么是Python的pickling和unpickling?
  • 54.什么是Python的猴子补丁(monkey patching)?
  • 55.什么是Python的鸭子类型(Duck Typing)
  • 56.什么是Python中的面向对象编程
  • 57.Python是否支持多重继承
  • 58.请详细说明Python3中装饰器的用法
  • 59.什么是Python中的模块和包?
  • 60.你使用过哪些Python标准库模块?
  • 61.你知道哪些Python魔术方法
  • 62.讲一下Python多线程、多进程和线程池
  • 63.如何分析Python代码的执行性能?
  • 64.pip
  • 65.pip-m
  • 67.uv
  • utf8
  • ast
  • dis
  • 尾递归
  • MethodType
  • 1. 什么是描述符协议?
    • 1.1 协议方法简介
      • 1.1.1 自定义数据描述符
      • 1.1.2 非数据描述符
    • 1.2 属性查找优先级
    • 1.3 property 就是非数据描述符
  • 2. MethodType:运行时让“函数”变成“绑定方法”
    • 2.1 基本用法
    • 2.2 原理解析
    • 2.3 动态给类/实例添加方法对比
  • 3. 函数的 get :方法自动绑定的背后
    • 3.1 概念
    • 3.2 get
    • 3.3 MethodType
    • 3.3 类定义方法的本质
    • 3.4 综合理解

1. 什么是描述符协议? #

描述符(Descriptor)是能自定义属性访问行为的对象,是掌控Python中调用逻辑、属性校验、缓存、事件通知等“横切关注点”的标准工具。

心智模型:

  • 普通属性:存/取时直连实例字典(instance.__dict__)。 实例字典指的是每个对象自身的属性存储空间,也就是通过 obj.attr = ... 动态赋值时,属性会写入这里。如果这里存有与描述符同名的属性,会优先返回这里的值(除非是数据描述符)。
  • 描述符属性:存/取会间接调用该属性的特殊方法(由类级描述符对象代理)。

常见作用:属性型验证、缓存、延迟计算(lazy)、访问日志、联动通知等。

1.1 协议方法简介 #

__get__(self, instance, owner=None)  # 读取属性时执行
__set__(self, instance, value)       # 设置属性时执行
__delete__(self, instance)           # 删除属性时执行

只需实现其中之一,你的类就成为“描述符”。

  • 同时有 __get__ & __set__ :数据描述符(Data Descriptor)(如 property, @property装饰器)
  • 只有 __get__ :非数据描述符(Non-data Descriptor)(如普通函数、类方法、静态方法)

1.1.1 自定义数据描述符 #

# 定义一个数据描述符类
class DataDescriptor:
    # 初始化方法,设置初始值
    def __init__(self, initial_value=None):
        self.value = initial_value
    # 获取属性时调用的方法
    def __get__(self, instance, owner):
        print(f"Getting value: {self.value}")
        return self.value
    # 设置属性时调用的方法
    def __set__(self, instance, value):
        print(f"Setting value to: {value}")
        self.value = value

# 定义一个包含描述符属性的类
class MyClass:
    attr = DataDescriptor("初值")

# 创建MyClass的实例
obj = MyClass()
# 访问attr属性,触发__get__方法,会输出“Getting ...”
print(obj.attr)    # 出现“Getting ...”提示
# 设置attr属性,触发__set__方法,会输出“Setting ...”
obj.attr = "新值" # 出现“Setting ...”提示
// 定义一个类
class MyClass {
    // 构造函数,初始化_attr_value属性为"初值"
    constructor() {
        this._attr_value = "初值";
    }
}

// 在原型上定义属性"attr",设置getter和setter以拦截获取和赋值操作
Object.defineProperty(MyClass.prototype, "attr", {
    // getter方法,在获取attr属性时调用
    get: function () {
        // 输出获取提示,并返回属性值
        console.log(`Getting value: ${this._attr_value}`);
        return this._attr_value;
    },
    // setter方法,在设置attr属性时调用
    set: function (value) {
        // 输出设置提示,并更新属性值
        console.log(`Setting value to: ${value}`);
        this._attr_value = value;
    },
    // 属性可配置
    configurable: true,
    // 属性可枚举
    enumerable: true
});

// 创建MyClass类的实例
const obj = new MyClass();
// 访问attr属性,触发getter,会输出获取提示
console.log(obj.attr);
// 设置attr属性,触发setter,会输出设置提示
obj.attr = "新值";

1.1.2 非数据描述符 #

class NonDataDescriptor:
    def __get__(self, instance, owner):
        return "只读虚拟值"

class MyClass:
    nd = NonDataDescriptor()

obj = MyClass()
print(obj.nd)  # 输出 只读虚拟值
# obj.nd = "whatever"  # 会直接在实例字典写值,覆盖描述符效果

1.2 属性查找优先级 #

仔细理解下列规则:

  1. 数据描述符(定义了 __set__)
  2. 实例字典(instance.__dict__)
  3. 非数据描述符(仅有 __get__)
  4. 类字典/父类字典
  5. __getattr__

数据描述符永远具有最高优先级,非数据描述符会被实例同名属性覆盖。

# __getattr__指的是Python中的一个特殊方法,用于在正常属性查找失败时被调用
class DynamicClass:
    def __init__(self):
        self.existing_attr = "我是已存在的属性"

    def __getattr__(self, name):
        """当属性找不到时调用此方法"""
        print(f"__getattr__ 被调用,查找属性: {name}")
        return f"动态创建的属性: {name}"

obj = DynamicClass()

# 访问已存在的属性 - 不会触发 __getattr__
print(obj.existing_attr)  # 输出: "我是已存在的属性"

# 访问不存在的属性 - 触发 __getattr__
print(obj.any_name)       # 输出: "__getattr__ 被调用,查找属性: any_name"
                          # 输出: "动态创建的属性: any_name"
print(obj.other_attr)     # 同样触发 __getattr__

1.3 property 就是非数据描述符 #

# 定义一个名为DemoObj的类
class DemoObj:
    # 定义初始化方法,接收参数v并赋值给实例变量_v
    def __init__(self, v):
        self._v = v
    # 使用@property装饰器,将v方法变成属性
    @property
    # 定义v属性的获取方法
    def v(self):
        # 获取v属性时输出提示信息
        print("触发 property __get__")
        # 返回实例变量_v的值
        return self._v

# 创建DemoObj类的实例,传入66作为初始值
d = DemoObj(66)
# 打印d的v属性,输出属性值,并带有提示信息
print(d.v)  # 输出 66(带提示)

2. MethodType:运行时让“函数”变成“绑定方法” #

理解了描述符协议后,Python通过 types.MethodType(或 function.__get__)让任意普通函数在运行时绑定为“某个实例的方法”,赋予它自动补全 self/cls 参数的能力。

2.1 基本用法 #

# 导入types模块,用于绑定方法
import types
# 定义一个空的Animal类
class Animal: pass
# 定义一个函数,带有self参数
def say_hi(self):
    return f"Hi! I am {self.name}"

# 创建Animal类的实例duck
duck = Animal()
# 给duck实例添加name属性
duck.name = "鸭鸭"
# 将say_hi函数绑定为duck实例的方法
duck.say_hi = types.MethodType(say_hi, duck)
# 调用绑定后的say_hi方法,自动传入self,输出 Hi! I am 鸭鸭
print(duck.say_hi())   # 自动补 self,输出 Hi! I am 鸭鸭

等价写法(利用描述符协议 get )

# 导入types模块,用于绑定方法
import types
# 定义一个空的Animal类
class Animal: pass
# 定义一个函数,带有self参数
def say_hi(self):
    return f"Hi! I am {self.name}"

# 创建Animal类的实例duck
duck = Animal()
# 给duck实例添加name属性
duck.name = "鸭鸭"
# 将say_hi函数绑定为duck实例的方法
duck.say_hi = say_hi.__get__(duck, Animal)
# 调用绑定后的say_hi方法,自动传入self,输出 Hi! I am 鸭鸭
print(duck.say_hi())   # 自动补 self,输出 Hi! I am 鸭鸭

2.2 原理解析 #

  • types.MethodType(f, obj) ≈ f.__get__(obj, type(obj))
  • 作用:返回一个“绑定方法”对象,其 __self__ 指向 obj,__func__ 指向 f
  • 这样调用 say_hi() 时 self 自动是 duck,不用手动传参。

2.3 动态给类/实例添加方法对比 #

# 导入 types 模块用于动态绑定方法
import types

# 定义一个空的 Cat 类
class Cat:
    pass

# 定义一个 meow 方法,接收 self 参数
def meow(self):
    return f"{self.name} 喵~"

# 创建两个 Cat 的实例 cat1 和 cat2
cat1, cat2 = Cat(), Cat()
# 设置 cat1 和 cat2 的 name 属性
cat1.name, cat2.name = "小白", "小黑"
# 将 meow 方法动态绑定到 cat1 的 speak 属性上
cat1.speak = types.MethodType(meow, cat1)
# 将 meow 方法动态绑定到 cat2 的 speak 属性上
cat2.speak = types.MethodType(meow, cat2)
# 调用 cat1 的 speak 方法并打印结果
print(cat1.speak())
# 调用 cat2 的 speak 方法并打印结果
print(cat2.speak())

仅对目标实例生效,其他实例无影响。

3. 函数的 get :方法自动绑定的背后 #

3.1 概念 #

Python的所有函数本身都是非数据描述符,都有一个 __get__ 方法,这让“函数作为类属性”时,能在读取时自动变成“绑定方法”。

# 定义一个函数,需要一个self参数
def intro(self):
    return f"我是 {self.name}"
# 定义一个空类P
class P:
    pass
# 创建P类的实例对象
p = P()
# 动态为p实例添加name属性
p.name = "张三"
# 将函数intro直接赋值给p的intro属性(此时只是一个函数引用,还未绑定self)
p.intro = intro  # 只是函数引用
# # 下面这一行如果解除注释会报错:TypeError: 缺少self参数
# p.intro()  # TypeError: 缺少 self
# 使用__get__方法把函数intro绑定到p实例上,变为一个方法
p.intro = intro.__get__(p, P)  # 手动绑定
# 调用绑定后的方法,输出“我是 张三”
print(p.intro())

3.2 get #

# 导入types模块,用于方法绑定
from types import MethodType

# 定义一个函数,参数为self
def intro(self):
    return f"我是 {self.name}"

# 定义一个空类P
class P:
    pass

# 创建P类的实例对象
p = P()

# 为实例p动态添加name属性,赋值为"张三"
p.name = "张三"

# 将函数intro赋值给p的intro属性,仅为函数引用
p.intro = intro  # 只是函数引用

# 下面这一行如果取消注释会报错:TypeError: 缺少self参数
# p.intro()  # TypeError: 缺少 self

# 定义__get__方法,用于函数绑定为方法
def __get__(self, instance, owner):
    # 如果instance为None,返回函数本身
    if instance is None:
        return self
    # 否则返回一个types.MethodType对象,将函数绑定到instance上
    return MethodType(self, instance)

# 将__get__方法绑定为intro的__get__属性
intro.__get__ = __get__

# 使用intro的__get__方法将intro绑定到实例p上,变为方法
p.intro = intro.__get__(intro, p, P)  # 手动绑定

# 调用绑定后的方法,输出“我是 张三”
print(p.intro())

3.3 MethodType #

# 导入types模块,用于方法绑定
#from types import MethodType

# 定义一个函数,参数为self
def intro(self):
    return f"我是 {self.name}"

# 定义一个空类P
class P:
    pass

# 创建P类的实例对象
p = P()

# 为实例p动态添加name属性,赋值为"张三"
p.name = "张三"

# 将函数intro赋值给p的intro属性,仅为函数引用
p.intro = intro  # 只是函数引用

# 下面这一行如果取消注释会报错:TypeError: 缺少self参数
# p.intro()  # TypeError: 缺少 self
# 自定义一个函数,将函数与实例绑定为方法
def MethodType(func, instance):
    # 定义绑定后实际调用的方法
    def bound(*args, **kwargs):
        return func(instance, *args, **kwargs)
    return bound
# 定义__get__方法,用于函数绑定为方法
def __get__(self, instance, owner):
    # 如果instance为None,返回函数本身
    if instance is None:
        return self
    # 否则返回一个types.MethodType对象,将函数绑定到instance上
    return MethodType(self, instance)

# 将__get__方法绑定为intro的__get__属性
intro.__get__ = __get__

# 使用intro的__get__方法将intro绑定到实例p上,变为方法
p.intro = intro.__get__(intro, p, P)  # 手动绑定

# 调用绑定后的方法,输出“我是 张三”
print(p.intro())

3.3 类定义方法的本质 #

# 定义一个名为People的类
class People:
    # 定义实例方法hello,返回向name属性打招呼的字符串
    def hello(self):
        return f"Hi, {self.name}!"

# 创建People类的实例p
p = People()
# 给p实例动态添加name属性,赋值为"李四"
p.name="李四"
# 调用p的hello方法,并打印结果
# 实际等价于:People.hello.__get__(p, People)()
print(p.hello())  # 其实是等价于:People.hello.__get__(p, People)()

3.4 综合理解 #

  • function.__get__ 是最核心的自动装配机制:让函数+实例→绑定方法。
  • types.MethodType 提供了自己“动态装配”的能力。
  • 一切“方法”的魔法(包括装饰器、@classmethod/@staticmethod)都基于此。

访问验证

请输入访问令牌

Token不正确,请重新输入