深入理解 python 虚拟机:花里胡哨的魔术方法

这篇具有很好参考价值的文章主要介绍了深入理解 python 虚拟机:花里胡哨的魔术方法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

深入理解 python 虚拟机:花里胡哨的魔术方法

在本篇文章当中主要给大家介绍在 cpython 当中一些比较花里胡哨的魔术方法,以帮助我们自己实现比较花哨的功能,当然这其中也包含一些也非常实用的魔术方法。

深入分析 hash 方法

在 Python 中,__hash__() 方法是一种特殊方法(也称为魔术方法或双下划线方法),用于返回对象的哈希值。哈希值是一个整数,用于在字典(dict)和集合(set)等数据结构中进行快速查找和比较。__hash__() 方法在创建自定义的可哈希对象时非常有用,例如自定义类的实例,以便可以将这些对象用作字典的键或集合的元素。

下面是一些需要注意的问题和示例来帮助理解 __hash__() 方法:

  • 如果两个对象相等(根据 __eq__() 方法的定义),它们的哈希值应该相等。即,如果 a == b 为真,则 hash(a) == hash(b) 也为真,这一点非常重要,因为我们在使用集合和字典的时候,就需要保证容器当中每种对象只能够有一个,如果不满足这个条还的话,那么就可能会导致同一种对象在容器当中会存在多个。
  • 重写 __hash__() 方法通常需要同时重写 __eq__() 方法,以确保对象的相等性和哈希值的一致性。
  • 如果对象没有定义 __eq__方法,那么也不要定义 __hash__方法,因为如果遇到哈希值相等的对象时候,如果无法对两个对象进行比较的话,那么也会导致容易当中有多个相同的对象。
import random


class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        return self.name == other.name and self.age == other.age

    def __hash__(self):
        return hash((self.name, self.age)) + random.randint(0, 1024)

    def __repr__(self):
        return f"[name={self.name}, age={self.age}]"


person1 = Person("Alice", 25)
person2 = Person("Alice", 25)


print(hash(person1))  
print(hash(person2))  


container = set()
container.add(person1)
container.add(person2)
print(container)

在上面代码当中我们重写了 __hash__ 函数,但是对象的哈希值每次调用的时候我们都加入一个随机数,因此即使 name 和 age 都相等,如果 hash 值不想等,那么可能会造成容器当中存在多个相同的对象,上面的代码就会造成相同的对象,上面的程序输出结果如下所示:

1930083569156318318
1930083569156318292
{[name=Alice, age=25], [name=Alice, age=25]}

如果重写上面的类对象:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __eq__(self, other):
        return self.name == other.name and self.age == other.age

    def __hash__(self):
        return hash((self.name, self.age))

    def __repr__(self):
        return f"[name={self.name}, age={self.age}]"

那么容器器当中只会有一个对象。

如果我们只重写了 __hash__方法的时候也会造成容器当中有多个相同的对象。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # def __eq__(self, other):
    #     return self.name == other.name and self.age == other.age

    def __hash__(self):
        return hash((self.name, self.age)) # + random.randint(0, 1024)

    def __repr__(self):
        return f"[name={self.name}, age={self.age}]"

这是因为如果哈希值相同的时候还需要在比较两个对象是否相等,如果相等那么就不需要将这个对象保存到容器当中,如果不相等那么将会将这个对象加入到容器当中。

bool 方法

在 Python 中,object.__bool__() 方法是一种特殊方法,用于定义对象的布尔值。它在使用布尔运算符(如 if 语句和逻辑运算)时自动调用。__bool__() 方法应该返回一个布尔值,表示对象的真值。如果 __bool__() 方法未定义,Python 将尝试调用 __len__() 方法来确定对象的真值。如果 __len__() 方法返回零,则对象被视为假;否则,对象被视为真。

下面是一些需要注意的事项来帮助理解 __bool__() 方法:

  • __bool__() 方法在对象被应用布尔运算时自动调用。例如,在 if 语句中,对象的真值由 __bool__() 方法确定。
  • __bool__() 方法应该返回一个布尔值(TrueFalse)。
  • 如果 __bool__() 方法未定义,Python 将尝试调用 __len__() 方法来确定对象的真值。
  • 当对象的长度为零时,即 __len__() 方法返回零,对象被视为假;否则,对象被视为真。
  • 如果既未定义 __bool__() 方法,也未定义 __len__() 方法,则对象默认为真。

下面是一个示例,展示了如何在自定义类中使用 __bool__() 方法:

class NonEmptyList:
    def __init__(self, items):
        self.items = items
    
    def __bool__(self):
        return len(self.items) > 0

my_list = NonEmptyList([1, 2, 3])

if my_list:
    print("The list is not empty.")
else:
    print("The list is empty.")

对象的属性访问

在Python中,我们可以通过一些特殊方法来定制属性访问的行为。本文将深入介绍这些特殊方法,包括__getitem__()__setitem__()__delitem__()__getattr__()方法,以帮助更好地理解属性访问的机制和应用场景。

__getitem__()方法是用于索引操作的特殊方法。当我们通过索引访问对象的属性时,Python会自动调用该方法,并传入索引值作为参数。我们可以在该方法中实现对属性的获取操作,并返回相应的值。

class MyList:
    def __init__(self):
        self.data = []
    
    def __getitem__(self, index):
        return self.data[index]

my_list = MyList()
my_list.data = [1, 2, 3]

print(my_list[1])  # 输出: 2

在上面的例子中,我们定义了一个名为MyList的类,它具有一个属性data,该属性是一个列表。通过重写__getitem__()方法,我们使得可以通过索引来访问MyList对象的data属性。当我们使用my_list[1]的形式进行索引操作时,Python会自动调用__getitem__()方法,并将索引值1作为参数传递给该方法。

__setitem__()方法用于属性的设置操作,即通过索引为对象的属性赋值。当我们使用索引操作并赋值给对象的属性时,Python会自动调用__setitem__()方法,并传入索引值和赋值的值作为参数。

class MyList:
    def __init__(self):
        self.data = [0 for i in range(2)]

    def __setitem__(self, index, value):
        self.data[index] = value


my_list = MyList()

my_list[0] = 1
my_list[1] = 2

print(my_list.data)  # 输出: [1, 2]

在上述示例中,我们重写了__setitem__()方法来实现对对象属性的设置操作。当我们执行my_list[0] = 1和my_list[1] = 2的赋值操作时,Python会自动调用__setitem__()方法,并将索引值和赋值的值传递给该方法。在__setitem__()方法中,我们将值赋给了对象的data属性的相应索引位置。

__delitem__()方法用于删除对象属性的特殊方法。当我们使用del语句删除对象属性时,Python会自动调用__delitem__()方法,并传入要删除的属性的索引值作为参数。

class MyDict:
    
    def __init__(self):
        self.data = dict()

    def __delitem__(self, key):
        print("In __delitem__")
        del self.data[key]


obj = MyDict()
obj.data["key"] = "val"
del obj["key"] # 输出 In __delitem__

__getattr__() 是一个特殊方法,用于在访问不存在的属性时自动调用。它接收一个参数,即属性名,然后返回相应的值或引发 AttributeError 异常。

class MyClass:
    def __getattr__(self, name):
        if name == 'color':
            return 'blue'
        else:
            raise AttributeError(f"'MyClass' object has no attribute '{name}'")

my_obj = MyClass()
print(my_obj.color)  # 输出: blue
print(my_obj.size)   # 引发 AttributeError: 'MyClass' object has no attribute 'size'

在上面的示例中,当访问 my_obj.color 时,由于 color 属性不存在,Python 会自动调用 __getattr__() 方法,并返回预定义的值 'blue'。而当访问 my_obj.size 时,由于该属性也不存在,__getattr__() 方法会引发 AttributeError 异常。

__setattr__() 是一个特殊方法,用于在设置属性值时自动调用。它接收两个参数,即属性名和属性值。我们可以在该方法中对属性进行处理、验证或记录。

class MyClass:
    def __init__(self):
        self.color = 'red' # 输出:Setting attribute 'color' to 'red'

    def __setattr__(self, name, value):
        print(f"Setting attribute '{name}' to '{value}'")
        super().__setattr__(name, value)


my_obj = MyClass()
my_obj.color = 'blue'  # 输出: Setting attribute 'color' to 'blue'

当我们使用 . 的方式去访问对象属性的时候,首先会调用对象的 __getattribute__ 函数,如果属性不存在才会调用 __getattr__。当 __getattribute__ 方法无法找到指定的属性时,Python 会调用 __getattr__ 方法。以下是在之前的示例类 CustomClass 上添加 __getattr__ 方法的代码:

class CustomClass:
    def __init__(self):
        self.attribute = "Hello, world!"

    def __getattribute__(self, name):
        print(f"Accessing attribute: {name}")
        return super().__getattribute__(name)

    def __getattr__(self, name):
        print(f"Attribute {name} not found")
        return None

在这个示例中,我们在 CustomClass 中添加了 __getattr__ 方法。当 __getattribute__ 方法无法找到指定的属性时,会自动调用 __getattr__ 方法,并打印出属性名称 "attribute" 以及未找到属性的提示信息。

我们执行下面的代码:

obj = CustomClass()
print(obj.attribute)
print(obj.nonexistent_attribute)

输出结果如下所示:

Accessing attribute: attribute
Hello, world!
Accessing attribute: nonexistent_attribute
Attribute nonexistent_attribute not found
None

首先,我们访问存在的属性 attribute,此时 __getattribute__ 方法被调用,并打印出属性名称 "attribute",然后返回属性的实际值 "Hello, world!"。接着,我们尝试访问不存在的属性 nonexistent_attribute,由于 __getattribute__ 方法无法找到该属性,因此会调用 __getattr__ 方法,并打印出属性名称 "nonexistent_attribute" 以及未找到属性的提示信息,然后返回 None

上下文管理器

当我们需要在特定的代码块执行前后进行一些操作时,上下文管理器是一种非常有用的工具。上下文管理器可以确保资源的正确分配和释放,无论代码块是否出现异常。在Python中,我们可以通过实现 __enter____exit__ 方法来创建自定义的上下文管理器。

下面是一个简单的上下文管理器示例,展示了如何使用 object.__enter__object.__exit__ 方法来创建一个文件操作的上下文管理器:

class FileContextManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        self.file.close()

with FileContextManager('example.txt', 'w') as file:
    file.write('Hello, world!')

在上述示例中,FileContextManager 类实现了 __enter____exit__ 方法。在 __enter__ 方法中,我们打开文件并返回文件对象,这样在 with 语句块中就可以使用该文件对象。在 __exit__ 方法中,我们关闭文件。

无论代码块是否抛出异常,__exit__ 方法都会被调用来确保文件被正确关闭。这样可以避免资源泄露和文件锁定等问题。使用上下文管理器可以简化代码,并提供一致的资源管理方式,特别适用于需要打开和关闭资源的情况,如文件操作、数据库连接等。

上述上下文管理器的 __exit__ 方法有三个参数:exc_typeexc_valuetraceback。下面是对这些参数的详细介绍:

  • exc_type(异常类型):这个参数表示引发的异常的类型。如果在上下文管理器的代码块中没有引发异常,它的值将为 None。如果有异常被引发,exc_type 将是引发异常的类型。

  • exc_value(异常值):这个参数表示引发的异常的实例。它包含了关于异常的详细信息,如错误消息。如果没有异常被引发,它的值也将为 None

  • traceback(回溯信息):这个参数是一个回溯对象,它包含了关于异常的堆栈跟踪信息。它提供了导致异常的代码路径和调用关系。如果没有异常被引发,它的值将为 None

总结

在本篇文章当中主要给大家介绍了一些常用和比较重要的魔术方法,这些方法在我们平时用的可能也比较多,比如 hash 和 eq 还有对象的属性访问,为了方便异常处理可以使用 exit 和 enter 这个方法,其实还有很多其他的魔术方法,这些方法在 python 官网都有介绍,可以直接访问 https://docs.python.org/3/reference/datamodel.html 。


本篇文章是深入理解 python 虚拟机系列文章之一,文章地址:https://github.com/Chang-LeHung/dive-into-cpython

更多精彩内容合集可访问项目:https://github.com/Chang-LeHung/CSCore

关注公众号:一无是处的研究僧,了解更多计算机(Java、Python、计算机系统基础、算法与数据结构)知识。文章来源地址https://www.toymoban.com/news/detail-456464.html

到了这里,关于深入理解 python 虚拟机:花里胡哨的魔术方法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 一款适合程序员的 Markdown 简历模版,拒绝花里胡哨

    一款适合 IT 行业的 Markdown 简约简历模版,拒绝花里胡哨 (1)安装 Markdown 编辑软件,推荐使用 Typora 编辑器(免费版 Typora 百度网盘下载点此下载)。 (2)下载本项目,修改 Resume.md 文件即可。 (3)将 style 文件夹下的所有的.css 复制到 Typora 的主题文件夹 (4)主题文件

    2024年02月06日
    浏览(54)
  • Ef Core花里胡哨系列(5) 动态修改追踪的实体、动态查询

    同样还是 IModelCacheKeyFactory ,不过这次要采用主动刷新的方式。 动态实体,根据配置等生成动态类型来当作数据库实体使用,当配置修改时,可以调用 DynamicModelCacheKeyFactory.Refresh() 刷新DbContext。 动态构建部分不提供,我们将在其它的地方进行讨论。 我这里做了简化处理,直

    2024年02月03日
    浏览(38)
  • MATLAB | 这些各种各样的花里胡哨的折线填充图咋画

    这些各种各样的花里胡哨的折线填充图咋画? 折线下面填充纯色的话area函数很容易做到,但上面那些各种花里胡哨的填充图就没那么容易做到了,本期就来讲讲这些玩意都是咋画的: 事先说明,为了绘图好看本文绝大多数图像都使用如下函数进行修饰: 二维填充所用到的数

    2023年04月16日
    浏览(48)
  • 如何在C语言中将文字显示为花里胡哨的多种颜色

            Hello大家好!最近刚刚做完C语言的课程设计。在评分标准中有一项是展示界面美观整洁。学前端的我对花里胡哨的界面情有独钟,这里为大家简单介绍三种改变字体颜色的方法。         注意:本文中所有演示截图均来自VS 2022。 目录  改变C语言显示界面字体

    2024年02月03日
    浏览(45)
  • 【一步教学,一步到位】花里胡哨的3D翻页卡片,隔壁产品都馋哭

    with(cardShadowSizeFunc!!) { inParamMin = 0F inParamMax = 180F outParamMax = 50F outParamMin = 0F initValue = 10F } cardShadowDistanceFunc = CardShadowDistanceFunc() with(cardShadowDistanceFunc!!) { inParamMin = 0F inParamMax = 180F outParamMax = 50F outParamMin = 0F initValue = 10F } } 复制代码 2.5.3 阴影变化 为了更好地模拟3D效果,卡片阴影

    2024年04月14日
    浏览(42)
  • 深入理解 python 虚拟机:魔术方法之数学计算

    在本篇文章当中主要给大家介绍在 python 当中一些常见的魔术方法,本篇文章主要是关于与数学计算相关的一些魔术方法,在很多科学计算的包当中都使用到了这些魔术方法。 当我们在Python中定义自己的类时,可以通过重写一些特殊方法来改变对象的比较行为。这些特殊方法

    2024年02月05日
    浏览(41)
  • 深入理解 python 虚拟机:字节码教程(3)——深入剖析循环实现原理

    在本篇文章当中主要给大家介绍 cpython 当中跟循环相关的字节码,这部分字节码相比起其他字节码来说相对复杂一点,通过分析这部分字节码我们对程序的执行过程将会有更加深刻的理解。 我们使用各种例子来理解和循环相关的字节码: 上面的代码对应的字节码如下所示:

    2023年04月15日
    浏览(40)
  • 深入理解python虚拟机:程序执行的载体——栈帧

    栈帧(Stack Frame)是 Python 虚拟机中程序执行的载体之一,也是 Python 中的一种执行上下文。每当 Python 执行一个函数或方法时,都会创建一个栈帧来表示当前的函数调用,并将其压入一个称为调用栈(Call Stack)的数据结构中。调用栈是一个后进先出(LIFO)的数据结构,用于管

    2023年04月25日
    浏览(39)
  • 深入理解python虚拟机:调试器实现原理与源码分析

    调试器是一个编程语言非常重要的部分,调试器是一种用于诊断和修复代码错误(或称为 bug)的工具,它允许开发者在程序执行时逐步查看和分析代码的状态和行为,它可以帮助开发者诊断和修复代码错误,理解程序的行为,优化性能。无论在哪种编程语言中,调试器都是一

    2023年04月26日
    浏览(51)
  • 深入理解 python 虚拟机:破解核心魔法——反序列化 pyc 文件

    在前面的文章当中我们详细的对于 pyc 文件的结构进行了分析,pyc 文件主要有下面的四个部分组成:魔术、 Bite Filed 、修改日期和 Code Object 组成。在前面的文章当中我们已经对前面三个部分进行了字节角度的分析,直接从 pyc 文件当中读取对应的数据并且打印出来了。而在本

    2024年02月05日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包