【python】进阶之魔法方法(五)

5 魔法方法

5-1init 和 del 【重点】

  • 为什么叫魔法方法? 只有在某种条件下才触发,就像魔法一样
  • __init__ :当你定义的类,被实例化的时候,自动执行,给对象初始化属性,这个方法里面放self.xxx属性=xxx
    然后这个xxx由init方法入参,所以你在实例化对象的时候,就需要传这个参数进去,这个参数就是对象的属性
  • 这个 __init__ 默认是返回None的,默认不写
  • 这里要说一下__del__析构方法,当对象被回收的时候出发执行(程序结束、对象引用技术为0称为垃圾时)
    这里要回想下python的垃圾回收机制的一种“引用计数”,就是当引用计数为0的时候,会被定义为垃圾,会被回收
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Student:
def __init__(self, name, age):
print("init...")
self.name = name
self.age = age
return
# return 123 # 注意:方法必须返回None,默认不写return语句

# 析构
class Student:
def __init__(self, name, age):
self.name = name
self.age = age

def __del__(self):
print("del...")


stu1 = Student("jack", 19)
del stu1 # 触发执行 __del__


def foo():
print("----- 函数内")
stu2 = Student("jack", 19)
print(stu2.__dict__)


foo() # foo函数执行结束,触发执行 __del__

示例:执行程序,猜一猜打印结果。

1
2
3
4
5
class Foo:
def __del__(self):
print('执行我啦')

print('------->')

5-2 new方法【重点】

  • 当类被调用实例化对象时第一个被处罚的函数,用来实例化并返回一个空对象
  • __new__方法在init方法前执行,__new__函数会新建一个空对象,然后这个空对象会交给__init__函数初始化。这个方法结束的时候,把这个空对象return出去
  • 这里说说实例化对象背后发生的三件事:
    • 1、__new__函数先会新建一个空对象,然后返回出去;给__init__函数
    • 2、该空对象执行__init__函数,就初始化了对象的属性;
    • 3、返回初始化完成后的对象,即实例化后的对象。
  • __new__魔法方法的一些应用场景:
    • 实现单例模式(Singleton):使用__new__方法来确保只有一个实例,并在需要时返回同一个实例。这个可以在多线程环境下确保只有一个全局实例,
    • 定制不可变类型:如果你希望创建以恶搞不可变的自定义对象,可以通过在__new__方法中重写对象创建的逻辑来实现(不了解)
    • 继承不可变类型:当继承python内置的不可变类型(如元组、字符串)时,你可以通过重写__new__方法来创建定制过程(不了解)
    • 定制元类(Metaclass):在元类中可以重写__new__方法,用于控制类的创建和实例化过程。(不了解,这个可以了解一下)
    • 对象池管理:在某些情况下,你可能需要使用对象池来重用已有的对象实例,可以通过new方法来实现对象池的管理。(不了解,这个可以了解一下)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 单例模式
class Singleton:
_instance = None

def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance


# 创建 Singleton 类的实例
obj1 = Singleton() # 1622254056400
obj2 = Singleton() # 1622254056400

print(obj1 is obj2) # 输出:True,因为 obj1 和 obj2 引用同一个实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 了解new 方法
class Student:
def __init__(self, name, age):
print("init:", id(self)) # new: 2176264649120
self.name = name
self.age = age

def __new__(cls, *args, **kwargs):
obj = object.__new__(cls) # init: 2176264649120
print("new:", id(obj))
return obj


stu1 = Student("jack", 19)

5.3 call【重点】

  • 类中的__call__在对象被调用时出发。就是当对象加括号被调用时触发
  • 对象或者变量,只有实现了__call__方法,才是可调用对象,才可以被执行,否则就会报错, object is not callable
  • 定义了__call__后,你实例化的对象可以像函数一样被调用,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CallableClass:
def __init__(self, value):
self.value = value

def __call__(self, x):
return self.value * x

# 创建一个可调用的对象
callable_obj = CallableClass(5)

# 调用对象,就像调用函数一样
result = callable_obj(10)
print(result) # 输出:50

总结就是,可以把类当方法直接调用

场景呢?

  • 当你希望某个对象能像函数一样调用且获得返回值时
  • __call__方法并结合with语句和__enter____exit__方法。实现上下文管理
  • __call__实现装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyDecorator:
def __init__(self, func):
self.func = func # 函数作为入参,传进来作为属性

def __call__(self, *args, **kwargs):
print("Before function is called") # 调用属性
result = self.func(*args, **kwargs) # 这是被装饰的方法
print("After function is called")
return result

@MyDecorator # @的本质就是在做 MyDecorator(my_function)
def my_function(x, y):
return x + y

result = my_function(3, 5)
print("Result:", result)
Before function is called
After function is called
Result: 8

  • 装饰器实现,两种方式
    • 第一个是常规的,闭包,嵌套函数
    • 第二个就是定义带__call__的类。讲函数作为__init__入参传进来

5.4 str和repr

  • __str__:当对象被访问打印时触发执行,他必须有一个字符串类型的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __str__(self):
return f"Person(name={self.name}, age={self.age})"

person = Person("Alice", 30)
print(person) # Output: Person(name=Alice, age=30)

sut = Student("name","age")
print(sut) # 这个时候打印出来的内容,就是str方法return出来的内容

好这个我知道了,那应用的场景呢?

  • 总之,__str__ 魔法方法适用于需要自定义对象的可读性、用户友好性和调试信息的情况。它使你能够以更有意义的方式呈现对象,并提供有关对象状态和属性的重要信息。

  • repr,本质和srt一样,都是对象被打印时显示的内容,但是

  • __str__的区别: __str__ 应该返回更友好、易读的字符串,而 __repr__ 则应该返回更详细、可复制的字符串,通常包含足够的信息来重新创建对象。

5.5 比较系列 eq nq it

两个对象是可以比较的,但是比较的结果可能不是我们预期的,此时可以重写比较系列的魔法方法,实现自定义比较逻辑

  • eq
    • __eq__ 方法是 Python 中的一个魔法方法,用于定义对象的相等性比较操作。当你在自定义类中实现了 __eq__
      方法时,你可以自定义对象之间的相等性判断规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __eq__(self, other):
if isinstance(other, Person):
print("eq被执行了") # 只有当你在判断两个对象是否相等时,这个方法才会被触发
return self.name == other.name and self.age == other.age
return False


person1 = Person("Alice", 25)
person2 = Person("Bob", 30)
person3 = Person("Alice", 25)
# print(person1.name)
print(person1 == person2) # False,根据 __eq__ 方法的定义,person1 和 person2 不相等
print(person1 == person3) # True,根据 __eq__ 方法的定义,person1 和 person3 相等
# eq被执行了
# False
# eq被执行了
# True
  • 同样的道理,其他比较系列的放啊
  • nq,实现的是是够不相等的逻辑。如果没有实现,则默认是eq的结果取反。
  • 该系列其他魔法方法:__lt__、__gt__、__le__、__ge__分别表示小于、大于、小于等于和大于等于。

5.6 attr系列 【了解】

  • 这个系列就和之前学习反射的时候是一样的道理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person:

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

def __getattr__(self, item):
print('调用不存在的属性会触发我')
return self.__dict__.get(item) #
# return self.item # 不能使用,会造成递归。

def __setattr__(self, key, value):
print('设置修改对象属性时触发我') # 这个在实例化对象的时候会被触发,可以有其他用处
self.__dict__[key] = value

def __delattr__(self, item):
print('删除对象属性时触发我')
self.__dict__.pop(item)


p = Person('jack') # 触发__setattr__
p.name = 'mack' # 触发__setattr__
print(p.age) # 触发__getattr__
p.age = 18 # 触发__setattr__
print(p.age)
del p.age # __delattr__
  • 示例二,让字典,实现像对象一样的点操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyDict(dict):
abc = "abc"

def __getattr__(self, item):
print("字典对象通过点访问不存在的属性时触发")
return self.get(item)

def __setattr__(self, key, value):
if not isinstance(value, str):
raise ValueError('值必须是字符串类型')
self[key] = value


d = MyDict({'name': 'jack'})
print(d.name) # 触发 __getattr__
print(d.abc) # 没有触发 __getattr__

5.7 item系列 ,和attr一样用【重点】

实现普通对象像字典一样通过 [ ] 操作 和attr一样理解

item:项目

attr: attribute: 属性的英文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person(object):

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

def __setitem__(self, key, value):
print("[]设置值时触发")
setattr(self, key, value)

def __getitem__(self, item):
print("[]取值时触发")
return getattr(self, item)

def __delitem__(self, key):
print("del p[key]时触发", key)


p = Person('jack')
p['name'] = 'mack' # 需要__setitem__才可以
print(p['name']) # 需要__getitem__才可以
print(p.__dict__)

del p["name"] # 需要__delitem__才可以

5.8 enter和exit【了解】

  • 上下文管理器
    • 执行代码块前后的操作,
    • 文件也是代码块
  • with :顺着
    • 顺着上下文管理器
  • with open(file , mode操作模式) mode就是r读取和w写入的模式选择
    • 这里的with open中的open( )函数所返回出来的对象就是一个上下文管理器,这个对象实现了enter方法和exit方法 用于对文件的自动打开和关闭,
    • 如果没有open,那我们的代码应该是怎么写的?
      • 就像数据库一样
      • file = open ( ‘file’ , mode )
      • file.write(“hello,world”)
      • file.close( )
      • 但是这样写不好看很麻烦,with open 就自动帮我们实现了打开和写入和关闭的操作
  • 好,说到这里,就能理解enter和exit了
  • 这个代码里的MyOpen就是我们的自己定义的上下文管理器,这个类所返回出来的对象就是一个上下文管理器,实现了enter和exit方法打开和关闭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyOpen:
def __init__(self, file_name: str, mode="r"):
self.file = open(file_name, mode)

def __enter__(self):
print("进入with语句块时触发")
return self.file # 返回值赋值给 as后面的接收值

def __exit__(self, exc_type, exc_val, exc_tb):
print("退出with语句块时触发,不论with语句块内是够有异常报错,__exit__都会被执行")
self.file.close()



with MyOpen("text.txt", "w") as f:
f.write("hello world")

5.9 iter和next【重点】

见名知意,这两个方法是实现迭代器功能的,比如实现一个range

这样我们就知道了range原理

1
2
3
# 普通range
for i in range(10):
print(i)
  • 通过自己定义这两个魔法方法实现自己的range
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import time

class MyRange():
def __init__(self,total: int,step: int=1):
self.count = 0 # 这里都是定义属性
self.total = total
self.step = step
def __iter__(self): #
print("1111")
return self # for循环第一个进入时执行一次,需要返回一个实现了__next__的迭代器对象
def __next__(self):
time.sleep(0.5)
self.count += self.step # 没次运行加1或者自定义的数
if self.count == self.total:
reise StopIteration # 如果count计数等于total总数时引发迭代异常,不是抛出,这个迭代异常表示迭代结束
return self.count

for i in MyRange(10,2):
print(i)
  • 总结一下,
  • iter 当一个类,定义了iter魔法方法,这个类的实例,就被认为是可迭代的(iterable)
    • iter方法应返回一个迭代器对象,可以是self,也可以是另一个实现了next方法的对象
  • next 方法:这个方法用于获取迭代中的下一个值。
    • 如果没有更多的值可迭代,应该要引发StopIteration异常,表示迭代结束

在 Python 中,迭代器(Iterator)和生成器(Generator)是用于处理可迭代对象的重要概念。它们提供了一种有效的方式来逐个访问和处理大型数据集或无限序列,同时节省内存和提高性能。

迭代器(Iterator)是一个实现了迭代协议的对象。它具有两个核心方法:

  1. __iter__() 方法:返回迭代器对象自身。用于支持迭代协议,使迭代器可以在 for 循环等上下文中使用。
  2. __next__() 方法:返回迭代器的下一个元素。如果没有更多元素可供迭代,抛出 StopIteration 异常。

迭代器的工作原理是通过迭代协议来逐个返回元素,只在需要时生成和提供数据,从而避免一次性加载和处理整个数据集或序列。

生成器(Generator)是一种特殊类型的迭代器,可以使用函数和 yield 语句来创建。生成器函数在被调用时返回一个生成器对象,它可以被迭代,每次迭代都会执行函数体中的代码,直到遇到 yield 语句。

生成器的优点在于它们的定义更简洁,并且它们以惰性的方式生成数据,只在需要时才生成和提供数据。这使得生成器非常适用于处理大型数据集或无限序列,因为它们可以逐步生成数据,而不会一次性占用大量内存。

以下是一个简单的示例,展示了迭代器和生成器的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 迭代器示例
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0

def __iter__(self):
return self

def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value

# 使用迭代器
my_iterator = MyIterator([1, 2, 3, 4, 5])
for num in my_iterator:
print(num)

# 生成器示例
def my_generator(data):
for num in data:
yield num

# 使用生成器
my_generator = my_generator([1, 2, 3, 4, 5])
for num in my_generator:
print(num)

5.10其他魔法方法

getattribute系列 attribute就是属性的意思

  • 调用对象不存在的属性的时候,会访问getattr
  • 访问对象的属性不管存在不存在都会触发getattribute方法
  • 除非getattribute抛出AttributeError异常才会触发getattr
  • 所以当你访问对象的属性不管存在不存在都会触发getattribute异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Foo:
def __init__(self,x):
self.x = x

def __getattr__(self, item):
print('执行的是我')

def __getattribute__(self, item):
print('不管是否存在,我都会执行')
raise AttributeError('哈哈')


f1=Foo(10)

f1.x
f1.y

module、class、name

  • __module__:模块,表示当前错爱早的对象在哪个模块
  • __class__:类 表示当前操作对象的类是什么
  • __name__: 表示当前对象的名字是什么

slot、all

  • __slot__:控制对象在实例化后可以持支持哪些属性
  • 是一个用于限制对象属性的特殊属性,它是一种用于优化内存占用和属性访问速度的机制。通过在类中定义 __slots__
    属性,你可以指定一个属性名称的列表,从而限制类的实例只能具有指定列表中的属性。
  • all:
  • 在模块文件中设置 __all__ 变量,当其它文件以“from 模块名 import *”的形式导入该模块时,该文件中只能使用 __all__
    列表中指定的成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tools.py

def add(a: int, b: int):
return a + b

def mul(a: int, b: int):
return a * b


def xsum(nums: list):
return sum(nums)


__all__ = ["add", "mul"]
  • 第四组:asyncio异步系列的魔法函数(了解)

【python】进阶之魔法方法(五)
http://example.com/2024/03/18/605python进阶之魔法方法(五)/
作者
Wangxiaowang
发布于
2024年3月18日
许可协议