【3.0】基础串联之魔法方法

【一】__init__方法

类实例化会触发

  • __init__是Python中的一个特殊方法,用于在创建一个对象时进行初始化操作。
    • 它是在类实例化(创建对象)时自动调用的。
  • __init__方法的作用是对新创建的对象进行初始化操作,可以在这个方法中设置对象的初始状态、定义属性等。
    • 该方法的定义格式如下:
def __init__(self, 参数列表):
    # 初始化操作
  • 其中
    • self表示当前对象的引用
    • self参数是在定义类方法时必须放在第一个位置的。
    • 它代表了类实例本身
    • 在方法内部可以使用self来访问对象的属性和方法。

__init__方法常用于执行以下操作:

  • 初始化对象的属性:在方法内部可以使用self.属性名来定义和初始化对象的属性。
  • 执行其他必要的初始化操作:例如打开文件、建立数据库连接等。

示例代码如下所示:

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

obj = MyClass("Alice", 20)
print(obj.name)  # 输出 "Alice"
print(obj.age)   # 输出 20
  • 在示例中
    • 我们定义了一个MyClass
    • 其中的__init__方法接收两个参数nameage
      • 并将它们分别赋值给对象的属性nameage
    • 然后通过实例化类
      • 并访问对象的属性
      • 可以得到正确的结果。
  • 通过定义__init__方法
    • 我们可以在创建对象时进行初始化操作
    • 为对象提供必要的属性和初始状态
    • 使其更易于使用和维护。

【二】__str__方法

打印对象会触发

  • __str__是Python中的一个特殊方法,用于返回对象的字符串表示。
    • 当我们在打印对象或使用str()函数时
    • Python会自动调用该方法并将其返回值作为字符串输出。
  • __str__方法的作用是定义对象的字符串表示,通常用于提供对象的可读性和描述性信息。
    • 我们可以在该方法中返回一个描述对象的字符串
    • 包括对象的属性值、状态等。

__str__方法的定义格式如下:

def __str__(self):
    # 返回对象的字符串表示
  • 其中,self表示当前对象的引用。
    • 在方法内部,可以使用self来访问对象的属性和方法。

示例代码如下所示:

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

    def __str__(self):
        return f"Name: {self.name}, Age: {self.age}"

obj = MyClass("Alice", 20)
print(obj)  # 输出 "Name: Alice, Age: 20"
  • 在示例中
    • 我们给MyClass类添加了一个__str__方法。
    • 该方法返回了一个描述对象的字符串,包括对象的姓名和年龄。
    • 通过在打印对象时自动调用__str__方法,我们可以获得对象的可读性较好的字符串表示。
  • 注意:
    • __str__方法应该返回一个字符串类型的值,并且不应该有任何副作用。
    • 如果需要有副作用的操作(例如改变对象状态),应该使用__repr__方法来定义对象的字符串表示。

【三】__call__方法

  • 对象()会触发
  • 类也是对象
    • 类()
    • 类的实例化过程调用元类的__call__方法
  • __call__方法是Python中的一个特殊方法,用于使对象具有像函数一样的调用行为。
    • 通过在类中实现__call__方法,我们可以直接对对象进行调用,就像调用函数一样。
  • 当我们对一个对象使用括号进行调用时,Python会自动调用该对象的__call__方法,并将传入的参数作为__call__方法的参数。

__call__方法的定义格式如下:

def __call__(self, *args, **kwargs):
    # 对象的调用行为
  • 其中
    • self表示当前对象的引用
    • argskwargs分别表示传入的位置参数和关键字参数。

示例代码如下所示:

class MyCallable:
    def __init__(self):
        pass
    
    def __call__(self, x, y):
        return x + y

obj = MyCallable()
result = obj(3, 5)
print(result)  # 输出 8
  • 在示例中
    • 我们给MyCallable类添加了一个__call__方法。
    • 该方法实现了对对象的调用行为,即将传入的两个参数相加并返回结果。
    • 通过对obj对象进行调用,就可以像函数一样计算两个数的和。
  • 需要注意的是
    • 通过实现__call__方法,我们可以将一个类的实例对象当作函数来使用。
    • 这种方式可以使得类的实例对象具有可调用的行为,从而增加了代码的灵活性。

【四】__new__方法

  • 在类实例化会触发,它比__init__
    • __new__造出裸体的人
    • __init__给裸体的人穿衣服
  • __new__方法是Python中的一个特殊方法,用于创建对象实例。
    • __init__方法不同,__new__方法在对象实例化之前被调用,用于创建并返回实例对象。

__new__方法的定义格式如下:

def __new__(cls, *args, **kwargs):
    # 创建并返回实例对象
  • 其中

    • cls表示当前类的引用
    • argskwargs分别表示传入的位置参数和关键字参数。
  • __new__方法需要返回创建的实例对象。

    • 这个返回值会作为参数传递给__init__方法
    • 并成为self参数的值。

示例代码如下所示:

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("__new__ is called")
        instance = object.__new__(cls)
        return instance
    
    def __init__(self, x, y):
        print("__init__ is called")
        self.x = x
        self.y = y

obj = MyClass(3, 5)
  • 在示例中
    • 我们给MyClass类添加了一个__new__方法。
    • 该方法通过调用object.__new__(cls)来创建并返回实例对象。
    • 然后,创建得到的实例对象将作为参数传递给__init__方法。

输出结果如下所示:

__new__ is called
__init__ is called
  • 从输出结果可以看出
    • __new__方法首先被调用,用于创建实例对象。
    • 然后,__init__方法被调用,用于对实例进行初始化。
  • 需要注意的是
    • 通常情况下不需要重写__new__方法,因为Python中的对象创建过程已经由内部的object.__new__方法完成了。
    • 只有在特殊情况下需要对对象的创建过程进行定制化时,才需要重写__new__方法。
  • 综上所述
    • __new__方法在对象实例化之前被调用,并用于创建并返回实例对象。
    • 通过重写__new__方法,我们可以对对象的创建过程进行定制化。

【五】__del__方法

对象,对象回收的时候触发

  • __del__方法是Python中的一个特殊方法,用于在对象被销毁时进行清理操作。
    • 当一个对象不再被使用时,Python的垃圾回收机制会自动销毁该对象。
    • 在对象销毁之前,如果定义了__del__方法,它会被调用。

【1】__del__方法的定义:

  • 在一个类中,可以定义__del__方法来实现对象销毁前的清理工作。
  • 该方法的命名规则是以双下划线开始和结束,例如__del__()

【2】垃圾回收机制与__del__方法的关系:

  • Python采用了自动垃圾回收机制来管理内存,当一个对象不再被引用时,垃圾回收机制会自动销毁该对象。
  • 在销毁对象之前,会调用对象的__del__方法进行清理操作。

【3】__del__方法的执行时机:

  • __del__方法的执行时机是在对象将要被销毁时(即引用计数减为0),而不是在对象被销毁之后。
  • 因此,在__del__方法中不能再访问这个对象的任何属性或方法,否则会引发异常。

【4】__del__方法的注意事项:

  • 不应该主动调用__del__方法,它由Python的垃圾回收机制自动触发。
  • __del__方法中不应该做过多的操作,以免影响程序性能。
  • 运行时环境和垃圾回收机制的具体实现方式可能会影响__del__方法的执行时间和顺序,因此不应该过于依赖该方法进行程序逻辑的设计。

【5】使用场景:

  • __del__方法可以用于执行一些资源释放的操作,比如关闭文件、释放网络连接、释放数据库连接等。这样可以确保在对象被销毁之前,相关的资源得到正确释放,避免资源泄漏或异常情况。

【6】综上所述

  • __del__方法是Python提供的用于在对象销毁前进行清理操作的特殊方法。
  • 合理地使用__del__方法可以确保资源的正确释放,但需要注意不要滥用此方法并了解其执行时机与局限性。

【六】__setattr__/__getattr__方法

  • 拦截方法
  • 当对象.属性
    • 赋值会触发__setattr__
    • 如果取值会触发__getattr__
  • __setattr____getattr__是Python中的两个特殊方法,用于处理属性的设置和获取操作。
  • 它们提供了灵活的方式来自定义对象属性的行为。

【1】__setattr__方法:

  • __setattr__方法在给对象设置属性时被调用。
  • 当属性被设置时,Python会自动寻找并调用该方法。
  • 通过在类中定义__setattr__方法,我们可以自定义属性设置的逻辑,例如检查属性值的有效性、记录属性变化等。

【2】__getattr__方法:

  • __getattr__方法在访问不存在或无法访问,就会被调用。
  • __getattr__方法的返回值将作为属性访问的结果。

【3】__setattr____getattr__的注意事项:

  • 定义这两个方法时,需要注意避免无限递归调用。
    • 因为在这两个方法中访问同一个属性可能会再次触发方法本身的调用。
  • 为避免递归调用,在__setattr__方法中,可以使用super().__setattr__(name, value)来调用基类的__setattr__方法,而不是直接赋值。
  • __getattr__方法中,如果返回的是AttributeError异常,Python会抛出AttributeError错误。
    • 如果要实现默认属性值或动态生成属性的功能,可以在方法中进行处理。

【4】使用场景:

  • __setattr__方法可以用于对属性的赋值进行控制和约束
    • 例如检查属性值的类型、范围或有效性。
  • __getattr__方法可以用于动态生成属性,或在属性不存在时返回默认值或执行特定的逻辑。

下面是一个简单的示例代码:

class MyClass:
    def __setattr__(self, name, value):
        # 自定义属性设置逻辑
        print(f"Setting attribute {name} to {value}")
        # 调用基类的__setattr__方法
        super().__setattr__(name, value)
        
    def __getattr__(self, name):
        # 自定义属性获取逻辑
        print(f"Attribute {name} does not exist")
        return None

【5】综上所述

  • __setattr____getattr__方法是Python中的两个特殊方法,用于自定义对象属性的设置和获取逻辑。
  • 通过合理使用这两个方法,可以灵活地控制属性的行为和处理方式。

【七】__getitem__/__setitem__方法

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

    def __setitem__(self, key, value):
        # 反射方法设置值
        setattr(self, key, value)

    def __getitem__(self, item):
        # 反射方法取值
        return getattr(self, item)


p = Person("dream")
p.name = "aaa"

# 正常情况下,该方法会报错,但是可以通过重写 __setitem__ 方法完成操作
p["name"] = 10

print(p.name)  # 10

# 正常情况下,该方法会报错,但是可以通过重写 __getitem__ 方法完成操作
print(p["name"])  # 10
  • __getitem____setitem__方法是Python中的特殊方法
  • 用于实现对象的索引操作和赋值操作。

【1】__getitem__方法:

  • __getitem__方法在使用索引访问对象时被调用。
  • 当对象通过索引访问时,Python会自动寻找并调用该方法。
  • __getitem__方法接收一个参数,即要获取的索引值。
  • 返回值将作为索引访问的结果。

【2】__setitem__方法:

  • __setitem__方法在对对象进行索引赋值时被调用。
  • 当对象通过索引赋值时,Python会自动寻找并调用该方法。
  • __setitem__方法接收两个参数,第一个参数为要设置的索引值,第二个参数为要赋的值。

【3】使用场景:

  • __getitem____setitem__方法常用于自定义序列类型(例如列表、字典)等。
  • 可以根据索引值进行不同的处理,如获取指定位置的元素、切片操作、赋值等。

【4】示例代码:

class MyList:
    def __init__(self, *args):
        self.data = list(args)

    def __getitem__(self, index):
        # 自定义索引访问逻辑
        return self.data[index]

    def __setitem__(self, index, value):
        # 自定义索引赋值逻辑
        self.data[index] = value

my_list = MyList(1, 2, 3, 4, 5)
print(my_list[0])      # 输出:1
print(my_list[2])      # 输出:3
print(my_list[1:3])    # 输出:[2, 3]

my_list[0] = 10        # 索引赋值
print(my_list[0])      # 输出:10
  • 在示例代码中
    • MyList类定义了__getitem____setitem__方法
    • 使得可以通过索引访问data属性中的元素,并且可以通过索引进行值的赋值。

【5】综上所述

  • __getitem____setitem__方法是Python中的特殊方法,在对象索引访问和赋值时被调用。
  • 通过这两个方法,我们可以自定义对象的索引操作和赋值操作,从而实现对对象内容的灵活访问和修改。

【八】__repr__方法

在交互时环境下打印对象

  • __repr__方法是Python中的一个特殊方法,用于返回对象的字符串表示形式。

    • 它被称为"official string representation",即官方字符串表示形式。
    • 在执行repr(obj)时,实际上是调用了obj.__repr__()方法来获取对象的字符串表示。
  • __repr__方法的目的是为了提供一个明确、准确且可读性较高的字符串表示形式,使开发者能够快速了解对象的类型和状态。

  • 一般情况下,__repr__方法的返回值应该是可以通过eval()函数重新创建相同对象的字符串。

示例:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x}, {self.y})'

p = Point(2, 3)
print(p)  # 输出:Point(2, 3)
  • 在上面的例子中,Point类实现了__repr__方法,它返回了一个以Point(x, y)形式表示的字符串。
  • 通过定义了__repr__方法,我们可以直接打印Point对象,并获得它的字符串表示形式,从而方便地了解对象的内容。
  • 需要注意的是,__repr__方法通常应该返回一个能够被解释器接受的字符串,而不是一个人类友好的描述。
  • 如果想要提供一个人类友好的描述,应该使用__str__方法。
  • 总结:
    • __repr__方法是一个特殊方法,用于返回对象的官方字符串表示形式。
    • 它的目的是为了提供一个明确、准确且可读性较高的字符串表示,方便开发者了解对象的类型和状态。

【九】__enter__/__exit__方法

上下文管理器

  • __enter____exit__是Python中用于上下文管理的特殊方法。

  • 上下文管理器可以通过with语句来管理资源的获取和释放

    • 例如文件的打开和关闭、锁的获取和释放等。
  • __enter__方法定义了进入上下文管理器时的行为,它在进入with代码块之前被调用,并且该方法可以返回一个值,供with语句中的as子句使用。

  • 一般情况下,__enter__方法用于获取资源,并返回该资源或者一些用于操作资源的对象。

  • __exit__方法定义了离开上下文管理器时的行为,它在退出with代码块之后被调用。

  • __exit__方法接收三个参数

    • 分别是异常类型
    • 异常值
    • 追溯信息。
  • 如果with代码块内部发生了异常,则这些异常信息会传递给__exit__方法。

  • __exit__方法通常用于释放资源,关闭文件、释放锁以及处理异常等操作。

下面是一个使用上下文管理器的示例:

class FileHandler:
    def __init__(self, filename):
        self.filename = filename

    def __enter__(self):
        # 在 with 管理的时候,会触发
        # 进入with 语句块时执行此方法,此方法如果有返回值就会赋值给as声明的变量
        self.file = open(self.filename, 'w')
        return self.file

    def __exit__(self, exc_type, exc_value, traceback):
        # 退出 with 代码时,执行此方法
        self.file.close()

# 使用上下文管理器写入文件
with FileHandler('example.txt') as file:
    file.write('Hello, World!')
  • 在上面的例子中

    • 我们定义了一个FileHandler类作为上下文管理器。
    • __enter__方法中
      • 我们打开文件并返回该文件对象。
    • __exit__方法中
      • 我们关闭文件。
    • 通过使用with语句,并将上下文管理器赋值给变量file,我们可以在代码块内部使用这个文件对象进行操作。
    • 在with代码块结束后,__exit__方法会被调用,确保文件被关闭。
  • 总结:

    • __enter____exit__是Python中用于上下文管理的特殊方法。
    • __enter__方法在进入with代码块之前被调用,用于获取资源并返回对象。
    • __exit__方法在退出with代码块之后被调用,用于释放资源、处理异常等操作。
    • 通过使用上下文管理器,可以更方便地管理资源的获取和释放。

【十】__eq__方法

  • __eq__ 方法是一个魔法方法
    • 用于定义对象的相等性比较操作。
  • 它在对象之间进行相等性比较时被调用。

【1】功能:

  • __eq__ 方法用于比较两个对象是否相等。
  • 它定义了对象相等的条件和逻辑,并返回一个布尔值,表示对象是否相等。

【2】用法:

  • 在自定义的类中,可以通过重写 __eq__ 方法来定义对象的相等性比较操作。
  • 通常,我们希望相等的对象具有相同的属性值,因此在 __eq__ 方法中,我们可以使用对象的属性进行比较。

【3】示例

class MyClass:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        if isinstance(other, MyClass):
            return self.value == other.value
        return False

# 使用示例
obj1 = MyClass(10)
obj2 = MyClass(20)
obj3 = MyClass(10)

print(obj1 == obj2)  # 输出 False
print(obj1 == obj3)  # 输出 True
  • 在上述示例代码中
    • 我们定义了一个 MyClass 类,该类具有一个属性 value
    • 我们重写了 __eq__ 方法,使其在比较对象相等性时,以对象的属性值作为判断依据。
  • 在使用示例中
    • 我们创建了三个 MyClass 对象 obj1obj2obj3,它们具有不同的属性值。
    • 通过使用 == 运算符进行比较
      • 我们可以看到 obj1obj2 的属性值不相等,返回 False
      • obj1obj3 的属性值相等,返回 True。
  • 需要注意的是
    • 当我们重写 __eq__ 方法时,通常也应同时重写 __ne__ 方法,以确保对象的不等性比较也能正确工作。
    • __ne__ 方法用于定义对象的不等性比较操作。在上述示例中,未实现 __ne__ 方法,因此会默认使用 __eq__ 方法与 not 运算符一起来实现不等性比较。

【补充】习题一

写一个类,有个name属性,如果name赋值为字符串,就不让放

【1】方式一

class MyClass:
    def __init__(self):
        self._name = None

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        if not isinstance(value, str):
            raise ValueError("Name must be a string.")
        self._name = value
  • 在上述代码中
    • 我们定义了一个名为MyClass的类,它具有一个name属性。
    • 在name属性的setter方法中,我们首先检查赋值给name的值是否为字符串
      • 如果不是,则抛出一个ValueError的异常。
      • 如果是一个字符串,才将其赋值给_name成员变量。
  • 通过以上代码
    • 当您尝试将非字符串的值赋给name属性时
    • 将会得到一个合适的错误提示。
    • 示例如下:
obj = MyClass()
obj.name = "John"  # 正确赋值

obj.name = 123  # 试图将非字符串赋值给name属性
# 输出:ValueError: Name must be a string.
  • 请注意
    • 我们使用@property装饰器和setter方法来实现对name属性的访问控制
    • 使得赋值操作符(=)能够触发setter方法进行验证。

【2】方式二

class MyClass:
    def __init__(self):
        self._name = None

    def __setattr__(self, name, value):
        if name == 'name' and not isinstance(value, str):
            raise ValueError("Name must be a string.")
        object.__setattr__(self, name, value)

    def __getattr__(self, name):
        if name == 'name':
            return self._name
        else:
            raise AttributeError(f"'MyClass' object has no attribute '{name}'")
  • 在上述代码中
    • 我们重写了__setattr__()方法,当尝试给name属性赋值时,会首先检查该值是否为字符串类型。
      • 如果不是字符串类型,则抛出一个ValueError异常。
      • 如果是字符串类型,才通过调用父类的__setattr__()方法来完成赋值操作。
  • 我们还重写了__getattr__()方法
    • 以便获取name属性的值。
    • 如果尝试获取其他属性
    • 将会抛出一个AttributeError的异常。
  • 使用以上代码
    • 您可以对name属性进行控制,并得到适当的错误提示。
    • 示例如下:
obj = MyClass()
obj.name = "John"  # 正确赋值

obj.name = 123  # 试图将非字符串赋值给name属性
# 输出:ValueError: Name must be a string.

print(obj.name)  # 获取name属性的值
# 输出:John

print(obj.age)  # 试图获取其他属性
# 输出:AttributeError: 'MyClass' object has no attribute 'age'

【补充】习题二

通过上下文管理器写个MySQL的链接,通过with管理

【1】方式一

  • 当使用上下文管理器来管理 MySQL 数据库连接时,可以使用 contextlib 模块的 contextmanager 装饰器。
  • 下面是一个使用上下文管理器连接 MySQL 数据库的示例代码:
import pymysql
from contextlib import contextmanager

@contextmanager
def mysql_connection(host, port, username, password, database):
    conn = pymysql.connect(
        host=host,
        port=port,
        user=username,
        password=password,
        database=database
    )
    try:
        yield conn
    finally:
        conn.close()

# 使用示例:
# 请修改以下参数为您实际的 MySQL 连接信息
host = 'localhost'
port = 3306
username = 'your_username'
password = 'your_password'
database = 'your_database_name'

with mysql_connection(host, port, username, password, database) as conn:
    # 在这个代码块中,可以执行需要使用数据库连接的操作
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM your_table_name")
    rows = cursor.fetchall()
    for row in rows:
        print(row)
  • 在上述示例代码中
    • 我们首先使用 contextmanager 装饰器装饰了一个生成器函数 mysql_connection
    • 在该生成器函数中,我们创建了一个 MySQL 数据库连接,并将其作为上下文管理器的结果进行返回。
  • 然后
    • 在使用上下文管理器的代码块中,我们可以直接使用 conn 变量得到数据库连接。
    • 在这个代码块中,您可以执行需要使用数据库连接的操作。
    • 完成后,无论是正常执行还是发生异常,上下文管理器都会确保数据库连接在最后被正确关闭。

【2】方式二

  • 使用魔法方法 __enter____exit__
  • 我们可以实现一个支持上下文管理的 MySQL 数据库连接类。
  • 下面是使用魔法方法完成的代码示例:
import pymysql

class MySQLConnection:
    def __init__(self, host, port, username, password, database):
        self.host = host
        self.port = port
        self.username = username
        self.password = password
        self.database = database
        self.conn = None

    def __enter__(self):
        self.conn = pymysql.connect(
            host=self.host,
            port=self.port,
            user=self.username,
            password=self.password,
            database=self.database
        )
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.conn:
            self.conn.close()

# 使用示例:
# 请修改以下参数为您实际的 MySQL 连接信息
host = 'localhost'
port = 3306
username = 'your_username'
password = 'your_password'
database = 'your_database_name'

with MySQLConnection(host, port, username, password, database) as conn:
    # 在这个代码块中,可以执行需要使用数据库连接的操作
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM your_table_name")
    rows = cursor.fetchall()
    for row in rows:
        print(row)
  • 在上述示例代码中,我们定义了一个 MySQLConnection 类,该类包含了 __enter____exit__ 方法。
  • __enter__ 方法中,我们创建了一个 MySQL 数据库连接,并返回该连接对象。
  • __exit__ 方法中,我们在代码块执行完毕后关闭了数据库连接。
  • 通过使用这个自定义的上下文管理器类,您可以在 with 语句块中方便地使用数据库连接,并确保在代码块执行完毕后自动关闭连接。
posted @ 2023-07-26 10:39  Chimengmeng  阅读(23)  评论(0编辑  收藏  举报