Python魔法方法与单例模式实战:从__init__到__slots__详解

小岛来信
小岛来信
发布时间:2026-10-13 | 5人浏览过
原创

本文深入解析Python中双下划线的魔法方法,涵盖__init__、__getattr__、__slots__及__enter__/__exit__等核心机制,并演示如何通过类方法、装饰器及元类实现单例模式,帮助开发者优化内存使用并掌握高级面向对象编程技巧。

Python魔法方法基础

魔法方法

在Python类定义中,凡是以双下划线开头且以双下划线结尾的方法均被称为魔法方法(Magic Methods),也称为内置方法。这些特殊方法会在满足特定条件时由解释器自动调用,无需显式触发。

常用双下划线方法概览

  • __init__:在实例化类并调用括号时触发,用于初始化对象属性。
  • __delattr__:
  • __getattr__:仅当通过对象访问属性且该属性不存在时触发。注意,直接访问对象.__dict__[属性]不会触发此方法,而是抛出KeyError。
  • __getattribute__:在通过对象访问任何属性时均会触发,无论该属性是否存在。若同时定义了__getattr__,__getattribute__的优先级更高。
  • __setattr__:当执行“对象.属性 = 属性值”操作时触发,即添加或修改属性时会自动调用该方法。
  • __del__:当对象在内存中被垃圾回收器释放时自动触发,通常在其他清理操作之后执行。

属性访问与修改魔法方法


class Uderline_func:

  x = 100
  
  def __init__(self, y):
    print('类加括号调用的时候触发我!')
    self.y = y # 当与__setattr__方法同时存在时,self.y = y并不会被加载到对象的名称空间
    # self['y'] = y # TypeError: 'Uderline_func' object does not support item assignment
  def general_func(self):
  
    print('随便定义的一个函数!')


  # def __getattr__(self, item):
  #   print('只有对象获取一个没有的属性值得时候触发我!')
  
  def __getattribute__(self, item):
  
    print('类或对象无论获取的属性有没有都会触发我!且出现我,对象点一个没有的属性会覆盖掉__getattr__,还会导致__setattr__函数报错')
  
  def __setattr__(self, key, value):
  
    print('设置属性的时候触发我!')
    # self.a = '在对象名称空间增加一个值!' # 会一直触发__setattr__,出现递归调用
    self.__dict__['a'] = '在对象名称空间增加一个值!'
  def __delattr__(self, item):
  
    print('删除值得时候触发我!')
  
  def __del__(self):
  
    print('程序运行完,被Python解释器回收时,触发我!')
# print(Uderline_func.__dict__) # 类在定义阶段就已经创建好了类名称空间,将其内部变量名和函数名塞进去
u = Uderline_func(100) # 触发__init__
# print(u.__dict__) # {'y': 100}
# Uderline_func.z # 只会触发__getattribute__
u.z # 获取没有的属性触发__getattr__
# u.name = 'zhang' # 触发__setattr__
# del u.x # 对象不能删除掉类中的属性,但只要执行删除操作,都会触发__delattr__的执行
  • __str__:在打印对象(如使用print()函数)时触发,应返回字符串表示。
  • __call__:当实例对象被当作函数调用时触发(即对象())。
  • __new__:在__init__执行之前触发,负责创建并返回一个新的实例对象。

对象调用与字符串表示


class Uderline_func():
  x = 100
  # def __new__(cls, *args, **kwargs):
  #
  #   print('在__init__执行之前触发我,造一个空对象!')
  def __init__(self):
    print('类加括号调用的时候触发我!')
  def __call__(self, *args, **kwargs):
    print('对象加括号调用的时候触发我!')
  def __str__(self):
    print('对象被打印的时候触发我!')
    return '必须要写return返回一个字符串!不然报错"TypeError: __str__ returned non-string (type NoneType)"'
u = Uderline_func()
u()
print(u)

__setitem__、__getitem__、__delitem__:分别用于支持类似字典的键值对赋值、读取和删除操作。

字典式访问支持


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

  def __getitem__(self, item):
    print(self.__dict__[item])

  def __setitem__(self, key, value):
    self.__dict__[key]=value

    # self.age = value # 也可以给对象添加属性


  def __delitem__(self, key):
    print('del obj[key]时,我执行')
    self.__dict__.pop(key)
  def __delattr__(self, item):
    print('del obj.key时,我执行')
    self.__dict__.pop(item)
f1=Foo('sb')
f1['age']=18
# print(f1.__dict__)
f1['age1']=19
# del f1.age1
# del f1['age']
f1['name']='alex'
f1.xxx = 111
print(f1.__dict__) # {'name': 'alex', 'age': 18, 'age1': 19, 'xxx': 111}

1.__slots__的作用:它是一个类变量,值可以是列表、元组、可迭代对象或字符串。若定义为字符串,则所有实例共享同一个数据属性结构。

2.原理引入:通过点号访问属性,本质上是访问类或对象的__dict__属性字典。类的__dict__是共享的,而每个实例的__dict__是独立的。

3.为何使用__slots__:字典占用大量内存。若类属性很少但实例数量巨大,使用__slots__可以显著节省内存空间,替代默认的字典存储方式。

实例的__dict__行为

定义__slots__后,实例将使用更紧凑的内部表示。实例通过一个固定大小的小数组构建,而非每个实例都创建一个字典。__slots__中列出的属性名会被映射到数组的特定索引上。使用__slots__的副作用是禁止给实例添加__slots__中未定义的属性,只能使用已声明的属性。

4.注意事项:__slots__的许多行为依赖于基于字典的实现。定义__slots__后,类将失去一些普通类的特性,例如多继承可能受限。建议在仅需创建数百万个实例的数据结构类中使用__slots__,以实现内存优化。

关于__slots__的一个常见误区是将其视为防止用户添加新属性的封装工具。虽然它能达到此效果,但这并非其初衷。__slots__的主要目的是作为内存优化工具。

__slots__内存优化机制


class Foo:
  __slots__ = 'x'
f1 = Foo()
f1.x = 1
f1.y = 2 # 报错
print(f1.__slots__) # f1不再有__dict__属性
print(f1.x) #依然能访问
class Bar:
  __slots__ = ['x', 'y']
n = Bar()
n.x, n.y = 1, 2
n.z = 3 # 报错

__doc__:用于查看类中的文档字符串(注释)。

类描述与模块信息


class Foo:
  '我是描述信息'
  pass
print(Foo.__doc__)
class Foo:
  '我是描述信息'
  pass

class Bar(Foo):
  pass
print(Bar.__doc__) #该属性无法继承给子类

__module__和__class__属性

__module__:表示当前操作的对象所属的模块名称。

__class__:表示当前操作的对象所属的类。

模块与类属性查询


class C:
  def __init__(self):
    self.name = ‘SB'
from lib.aa import C
obj = C()
print obj.__module__ # 输出 lib.aa,即:输出模块
print obj.__class__   # 输出 lib.aa.C,即:输出类

__enter__和__exit__:上下文管理协议方法

在操作文件对象时,我们常使用以下语法:

with open('a.txt') as f: '代码块'

这被称为上下文管理协议,即with语句。为了使对象兼容with语句,必须在类的定义中声明__enter__和__exit__方法。

上下文管理器实现


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

  def __enter__(self):
    print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
    # return self
  def __exit__(self, exc_type, exc_val, exc_tb):
    print('with中代码块执行完毕时执行我啊')

with Open('a.txt') as f:
  print('=====>执行代码块')
  # print(f,f.name)
  
'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
'''

__exit__方法的三个参数分别代表异常类型、异常值和追溯信息。若with语句中的代码块出现异常,且__exit__未处理,则with后的代码无法执行。


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

  def __enter__(self):
    print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

  def __exit__(self, exc_type, exc_val, exc_tb):
    print('with中代码块执行完毕时执行我啊')
    print(exc_type)
    print(exc_val)
    print(exc_tb)


with Open('a.txt') as f:
  print('=====>执行代码块')
  raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->不会执行


'''
出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
=====>执行代码块
with中代码块执行完毕时执行我啊
<class 'AttributeError'>
***着火啦,救火啊***
<traceback object at 0x000000000A001E88>
Traceback (most recent call last):
 File "G:/Python代码日常/第一阶段/1阶段/面向对象/test.py", line 52, in <module>
  raise AttributeError('***着火啦,救火啊***')
AttributeError: ***着火啦,救火啊***

'''

如果__exit__方法返回True,则异常会被视为已处理(清空),仿佛未发生异常,with后的代码将正常继续执行。


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

  def __enter__(self):
    print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

  def __exit__(self, exc_type, exc_val, exc_tb):
    print('with中代码块执行完毕时执行我啊')
    print(exc_type)
    print(exc_val)
    print(exc_tb)
    return True
  with Open('a.txt') as f:
  print('=====>执行代码块')
  raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行

class Open:
  def __init__(self,filepath,mode='r',encoding='utf-8'):
    self.filepath=filepath
    self.mode=mode
    self.encoding=encoding

  def __enter__(self):
    # print('enter')
    self.f=open(self.filepath,mode=self.mode,encoding=self.encoding)
    return self.f

  def __exit__(self, exc_type, exc_val, exc_tb):
    # print('exit')
    self.f.close()
    return True 
  def __getattr__(self, item):
    return getattr(self.f,item)

with Open('a.txt','w') as f:
  print(f)
  f.write('aaaaaa')
  f.wasdf #抛出异常,交给__exit__处理

用途与优势:

1.使用with语句的目的是将代码块放入with中执行,with结束后自动完成清理工作,无需手动干预资源释放。

2.在需要管理资源(如文件、网络连接、锁)的编程环境中,可以在__exit__中定制自动释放资源的机制,从而简化代码并提高安全性。

单例模式

单例模式:确保一个类在整个应用程序中多次实例化的结果始终指向同一个实例对象。

实现方式1:类方法

单例模式多种实现方案


# @classmethod(用类绑定方法)

import settings

class MySQL:
  __instance=None
  def __init__(self, ip, port):
    self.ip = ip
    self.port = port
  @classmethod
  def from_conf(cls):
    if cls.__instance is None:
      cls.__instance=cls(settings.IP, settings.PORT)
    return cls.__instance
obj1=MySQL.from_conf()
obj2=MySQL.from_conf()
obj3=MySQL.from_conf()
# obj4=MySQL('1.1.1.3',3302)
print(obj1)
print(obj2)
print(obj3)
# print(obj4)

实现方式2:装饰器


# 用类装饰器
import settings

def singleton(cls):
 _instance=cls(settings.IP,settings.PORT)
 def wrapper(*args,**kwargs):
   if len(args) !=0 or len(kwargs) !=0:
     obj=cls(*args,**kwargs)
     return obj
   return _instance
 return wrapper

@singleton #MySQL=singleton(MySQL) #MySQL=wrapper
class MySQL:
 def __init__(self, ip, port):
   self.ip = ip
   self.port = port

# obj=MySQL('1.1.1.1',3306) #obj=wrapper('1.1.1.1',3306)
# print(obj.__dict__)

obj1=MySQL() #wrapper()
obj2=MySQL() #wrapper()
obj3=MySQL() #wrapper()
obj4=MySQL('1.1.1.3',3302) #wrapper('1.1.1.3',3302)
print(obj1)
print(obj2)
print(obj3)
print(obj4)

实现方式3:元类


# 调用元类
import settings

class Mymeta(type):
  def __init__(self,class_name,class_bases,class_dic):
    #self=MySQL这个类
    self.__instance=self(settings.IP,settings.PORT)

  def __call__(self, *args, **kwargs):
    # self=MySQL这个类
    if len(args) != 0 or len(kwargs) != 0:
      obj=self.__new__(self)
      self.__init__(obj,*args, **kwargs)
      return obj
    else:
      return self.__instance

class MySQL(metaclass=Mymeta): #MySQL=Mymeta(...)
  def __init__(self, ip, port):
    self.ip = ip
    self.port = port
obj1=MySQL()
obj2=MySQL()
obj3=MySQL()
obj4=MySQL('1.1.1.3',3302)
print(obj1)
print(obj2)
print(obj3)
print(obj4)

实现方式4:模块级变量(或其他变体)


# 利用模块多次导入只产生一次名称空间,多次导入只沿用第一次导入成果。
def f1():
  from singleton import instance
  print(instance)

def f2():
  from singleton import instance,My
  SQL
  print(instance)
  obj=MySQL('1.1.1.3',3302)
  print(obj)

f1()
f2()

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表