乐趣区

关于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、计算机系统根底、算法与数据结构)常识。

退出移动版