1. 程式人生 > 程式設計 >Python面向物件魔法方法和單例模組程式碼例項

Python面向物件魔法方法和單例模組程式碼例項

魔法方法

​ 凡是在類內部定義,以“__開頭__結尾”的方法都稱之為魔法方法,又稱“類的內建方法”, 這些方法會在某些條件成立時觸發。

經常用到的雙下方法

  • __init__: 在呼叫類時觸發。
  • __delarttr__:
  • __getattr__: 會在物件.屬性時,“屬性沒有”的情況下才會觸發。物件.__dict__[屬性]不會觸發__getattr__,會報keyerror;
  • __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__: 會在列印物件時觸發。
  • __call__: 會在物件被呼叫時觸發。
  • __new__: 會在__init__執行前觸發。
class Uderline_func():
  x = 100
  # def __new__(cls,*args,**kwargs):
  #
  #   print('在__init__執行之前觸發我,造一個空物件!')
  def __init__(self):
    print('類加括號呼叫的時候觸發我!')
  def __call__(self,**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,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__屬性字典(類的字典是共享的,而每個例項的是獨立的)

3.為何使用__slots__:字典會佔用大量記憶體,如果你有一個屬性很少的類,但是有很多例項,為了節省記憶體可以使用__slots__取代

例項的__dict__

當你定義__slots__後,__slots__就會為例項使用一種更加緊湊的內部表示。例項通過一個很小的固定大小的陣列來構建,而不是為每個例項定義一個字典,這跟元組或列表很類似。在__slots__中列出的屬性名在內部被對映到這個陣列的指定小標上。使用__slots__一個不好的地方就是我們不能再給例項新增新的屬性了,只能使用在__slots__中定義的那些屬性名。

4.注意事項:__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語句,有返回值則賦值給as宣告的變數
=====>執行程式碼塊
with中程式碼塊執行完畢時執行我啊
'''

exit()中的三個引數分別代表異常型別,異常值和追溯資訊,with語句中程式碼塊出現異常,則with後的程式碼都無法執行

class Open:
  def __init__(self,有返回值則賦值給as宣告的變數')

  def __exit__(self,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語句,有返回值則賦值給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,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_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,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,**kwargs):
    # self=MySQL這個類
    if len(args) != 0 or len(kwargs) != 0:
      obj=self.__new__(self)
      self.__init__(obj,**kwargs)
      return obj
    else:
      return self.__instance

class MySQL(metaclass=Mymeta): #MySQL=Mymeta(...)
  def __init__(self,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()

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。