1. 程式人生 > 其它 >面向物件的雙下方法,元類

面向物件的雙下方法,元類

今日作業

  編寫元類規定物件的所有資料值轉大寫
    	eg:
        	obj.name = 'jason'
          print(obj.name)  # JASON

  class MyTypeClass(type):
      def __call__(self, *args, **kwargs):
          args1 = []
          kwargs1 = {}
          if args:
              for i in range(len(args)):
                  args1.append(args[i].upper())
              print(args1)
          if kwargs:
              for i in kwargs:
                  kwargs1[i] = kwargs[i].upper()
              print(kwargs1)
          return super().__call__(*args1, **kwargs1)

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

  my = MyClass('aaa')
  print(my.name)

面向物件的雙下方法

1.__ str__, __ repr__

  __str__(***) 裡邊必須有return "字串"型別  (優先順序高於__repr__)
    1.列印物件的時候,會觸發__str__方法
    2.直接str轉化也可以觸發
  class A:
      def __init__(self,name,age):
          self.name = name
          self.age =age

      def __str__(self):
          print(666)
          return f'姓名: {self.name} 年齡: {self.age}'

  a = A('二狗',35) 
  b = A('大狗',56)
  c = A('老狗',18)
  列印物件觸發__str__方法
  print(f'{a.name}  {a.age}') # 二狗  35
  print(f'{b.name}  {b.age}')
  print(f'{c.name}  {c.age}')
  print(a) # 666
             姓名: 二狗 年齡: 35
  print(b)
  print(c)
  直接str轉化也可以觸發.
  print(str(a))  # 666
                   姓名: 二狗 年齡: 35

  __repr__  裡邊必須有return "字串"型別
    列印物件的時候,會觸發__repr__方法
  class A:
      def __init__(self,name,age):
          self.name = name
          self.age =age

      def __repr__(self):
          print(666)
          return f'姓名: {self.name} 年齡: {self.age}'

  a = A('二狗',35)
  b = A('大狗',56)
  c = A('老狗',18)
  print(a)
  print(repr(a))


  __str__和__repr__ 一起使用時,只執行__str__的內容
  class A:

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

      def __str__(self):
          return '777'


      def __repr__(self):
          return '666'

  a = A('二狗',35)
  b = A('大狗',56)
  c = A('老狗',18)
  # print(a)
  print(a) # 777
  print(b) # 777
'''
   str()方法定義當被print()方法或者str()方法呼叫時的行為,repr()方法定義當被print()方法或者
repr()方法呼叫時的行為,一般來說,它們的功能都是實現類到字串的轉化,實現方式上也沒有特別的差
異。 但實際上,str()方法的返回結果可讀性應該要更強一些,repr()方法的返回結果要更加準確,也就是
說,str()方法的意義是得到便於人們閱讀的資訊,而__repr__()方法的目的在於開發者便於除錯。
   注意:當進行print()的時候,首先被執行的是__str__()方法,如果沒定義__str__()方法,repr()
方法才會被執行。
'''

2.__call__

  __call__(***)  物件()觸發物件從屬類(父類)的__call__方法
  class Foo:

      def __init__(self):
          pass

      def __call__(self, *args, **kwargs):
          print('__call__')

  obj = Foo()
  obj()
  當呼叫obj()時,call()方法會被呼叫,call()方法也可以接受引數
'''
  call()方法可以讓類的例項像函式一樣被呼叫
'''

3. __del__

  __del__當一個物件的生命週期結束被徹底銷燬的時候被調,__del__方法會被呼叫,不常用,一般不需要
進行重寫。
  class A:
      def __del__(self):
          print(666)

  obj = A()
  del obj

4.__getattr__,__getattribute__,__setattr__

  __getattr__表示的是當一個物件訪問一個屬性時,沒有從它定義的屬性當中查詢到就會呼叫這個方法。
  class A(object):
      def __init__(self, value):
          self.value = value

      def __getattr__(self, item):
          print("into __getattr__")       
          return "can not find"
  a = A(10)
  print(a.value)
  # 10
  print(a.name)
  # into __getattr__
  # can not find

  __getattribute__只要物件查詢名字無論名字是否存在都會執行該方法,如果類中有__getattribute__方
法 那麼就不會去執行__getattr__方法
  class A(object):
      def __init__(self, value):
          self.value = value

      def __getattribute__(self, item):
          print("into __getattribute__")
          return "__getattribute__"
  a = A(10)
  print(a.value)
  into __getattribute__
  __getattribute__
  print(a.name)
  into __getattribute__
  __getattribute__


  class A(object):
      def __init__(self, value):
          self.value = value

      def __getattribute__(self, item):
          print("into __getattribute__")
          return "__getattribute__"

      def __getattr__(self, item):
          print('into __getattr__')
          return '__getattr__'


  a = A(10)
  print(a.value)
  # into __getattribute__
  # __getattribute__
  print(a.name)
  # into __getattribute__
  # __getattribute__
'''
  使用歸納:

  1.__getattribute__方法優先順序比__getattr__高

  2.當同時定義__getattribute__和__getattr__時,__getattr__方法不會再被呼叫,除非顯示呼叫
__getattr__方法或引發AttributeError異常。

  3.如果是對不存在的屬性做處理,儘量把邏輯寫在__getattr__方法中

  4.如果非得重寫__getattribute__方法,需要注意兩點:第一是避免.操作帶來的死迴圈;第二是不要遺
忘父類的__getattribute__方法在子類中起的作用
'''

 __setattr__物件在執行新增屬性操作的時候自動觸發	>>>	obj.變數名=變數值
  class A(object):
      def __init__(self, value):
          print("into __init__")
          self.value = value

      def __setattr__(self, name, value):
          print("into __setattr__")
          if value == 10:
              print("from __init__")
          object.__setattr__(self, name, value)


  a = A(10)
  # into __init__
  # into __setattr__
  # from __init__
  print(a.value)
  # 10
  a.value = 100
  # into __setattr__
  print(a.value)
  # 100

5.__enter__,__exit__

  我們知道在操作檔案物件的時候可以這麼寫
  with open('a.txt') as f:
       '程式碼塊'
  上述叫做上下文管理協議,即with語句,為了讓一個物件相容with語句,必須在這個物件的類中宣告__enter__和__exit__方法

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

  如果__exit()返回值為True,那麼異常會被清空,就好像啥都沒發生一樣,with後的語句正常執行

  # coding=utf-8
  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中程式碼塊執行完畢時執行我啊')
          print(exc_type)
          print(exc_val)
          print(exc_tb)
          return True


  with Open('aa.txt') as f:
      print(f)
      print(f.name)
      print(sdsd)  # 觸發 __exit__ ,之後的程式碼就不會執行
      print('-------')

  print('End....')

  '''
  執行結果:
  出現with語句,物件的__enter__被觸發,有返回值則賦值給as宣告的變數
  <__main__.Open object at 0x0000016D7FA95BE0>
  aa.txt
  with中程式碼塊執行完畢時執行我啊
  <class 'NameError'>
  name 'sdsd' is not defined
  <traceback object at 0x0000016D7FA925C8>
  End....
  '''

  '''
  總結:
  __enter__在with語句後自動呼叫,可以給as後的變數賦值
  __exit__用於捕獲異常,返回boolean物件,如果為True異常被忽略,如果為False異常被丟擲
  三個引數分別是:
  1.exc_type 異常型別
  2.exc_value異常值
  3.traceback
  with obj as f:
      程式碼塊
  1. with obj ----> 觸發obj.__enter__(),拿到返回值
  2. as f --> f = 返回值
  3. with obj as f 等同於  f = obj.__enter__()
  4. 執行程式碼塊
   1> 沒有異常,整個程式碼塊執行完畢後去觸發__exit__(),它的三個引數都返回None
   2> 有異常的情況下,從異常出現的位置直接觸發__exit))
      a:如果__exit__返回值為True,代表吞掉了異常(不報異常)
      b:如果__exit__返回值不為True,代表吐出了異常(異常報錯)
      c:__exit__執行完畢,就代表整個with語句執行完畢
  '''

針對雙下方法的筆試題

  1.讓字典具備句點符查詢值的功能
  	# 1.定義一個類繼承字典
    class MyDict(dict):
        def __getattr__(self, item):
            return self.get(item)

        def __setattr__(self, key, value):
            self[key] = value
    '''要區別是名稱空間的名字還是資料k:v鍵值對'''
    obj = MyDict({'name':'jason','age':18})
    # 1.具備句點符取v
    # print(obj.name)
    # print(obj.age)
    # 2.具備句點符設k:v
    # obj['gender'] = 'male'
    obj.pwd = 123  # 給字典名稱空間新增名字  不是資料k:v
    print(obj)

  2.補全下列程式碼 使其執行不報錯
  	"""
  	class Context:
  			pass
  	with Context() as ctx:
  			ctx.do_something()
  	"""
    class Context:
      def __enter__(self):
          return self
      def __exit__(self, exc_type, exc_val, exc_tb):
          pass
      def do_something(self):
          pass
  	with Context() as ctx:
      ctx.do_something()

元類

元類就是用於建立類的“東西”。

在理解元類之前,我們必須先掌握Python中的類(class)。

和大多數語言一樣,Python中的類是用來描述如何“生成一個物件”:
class A(object):
    pass

a = A()
print(a)
# <__main__.A object at 0x01DFFDD8>

但是,在Python中,類不僅能用來描述如何生成一個物件,類本身也是物件。

在你使用關鍵詞 class 的時候,Python就會執行它,並建立一個物件。
class A(object):
    pass
當我們執行這兩行程式碼時在記憶體中建立了一個名為“A”的物件。
這個物件(類)本身具有建立物件(例項)的能力,因此它也是一個類。你可以對它做以下操作:

1.將其分配給變數
2.複製它
3.為其新增屬性
4.將其作為函式引數傳遞

我們可以對A新增屬性等等,還可以對A使用反射
class A(object):
    pass

A.name = 1
print(A) # <class '__main__.A'>
print(hasattr(A,'name')) # True
A實際也是一個物件,但它又可以例項出物件。

使用class關鍵字時,Python會幫你自動建立此物件,但是,Python同樣也提供了一種手動建立的方法,那就
是type函式。
type函式最經典的用法是返回物件的型別。但是很少人知道,它還能接受引數並手動建立類。
type(name, bases, attrs)
  
name: 類名
bases: 元組,父類名
attrs: 字典,類屬性值
A = type('A', (), {})
print(A) # <class '__main__.A'>

class Foo(object):
    bar = True
等同於Foo = type('Foo', (), {'bar':True})

繼承
FooChild = type('FooChild', (Foo,), {})
print(FooChild)
# <class '__main__.FooChild'>
print(FooChild.bar) 
# True

可見通過 type() 函式建立的類和直接寫class是完全一樣的。
因為Python直譯器遇到class定義時,僅僅是掃描一下class定義的語法,然後呼叫 type() 函式創建出class。

產生類的兩種表現形式(本質是一種)

  1.class關鍵字
  	class C1(object):
      pass
  	print(C1)  # <class '__main__.C1'>
  	
  2.type元類
  	type(類名,父類,類的名稱空間)
    	res = type('C1', (), {})
  		print(res)  # <class '__main__.C1'>
      
  """
  學習元類的目的
  	元類能夠控制類的建立 也就意味著我們可以高度定製類的行為
  		eg:掌握了物品的生產過程 就可以在過程中做任何的額外操作
  		
  	比如:要求類的名字必須首字母大寫
  		思考在哪裡編寫定製化程式碼
  			類的產生過程目前還比較懵 	  元類裡面的__init__方法
  			物件的產生過程呢 			     類裡面的__init__方法
  		方法:由已知推未知
  """

metaclass自定義元類

  元類一般用於建立類。在執行類定義時,直譯器必須要知道這個類的正確的元類。直譯器會先尋找類屬性
__metaclass__,如果此屬性存在,就將這個屬性賦值給此類作為它的元類。如果此屬性沒有定義,它會向上查
找父類中的__metaclass__.如果還沒有發現__metaclass__屬性,直譯器會檢查名字為
__metaclass__的全域性變數,如果它存在,就使用它作為元類。否則, 這個類就是一個傳統類,並用 
types.ClassType 作為此類的元類。
  注意:Python3中不再有__metaclass__屬性以及模組級別的__metaclass__屬性。
  在執行類定義的時候,將檢查此類正確的(一般是預設的)元類,元類(通常)傳遞三個引數(到構造器): 類名,
從基類繼承資料的元組,和(類的)屬性字典。

  class MyTypeClass(type):
      def __init__(cls, cls_name, cls_bases, cls_dict):
          # print(cls, cls_name, cls_bases, cls_dict)
          if not cls_name.istitle():
              raise Exception("類名的首字母必須大寫 你個SD")
          super().__init__(cls_name, cls_bases, cls_dict)

  class C1(metaclass=MyTypeClass):
      school = '清華大學'


  class a(metaclass=MyTypeClass):
      school = '清華大學'

元類進階操作

1.回想__call__方法
	物件加括號會自動執行產生該物件的類裡面的__call__,並且該方法返回什麼物件加括號就會得到什麼
  推導:類加括號會執行元類的裡面的__call__該方法返回什麼其實類加括號就會得到什麼
  """類裡面的__init__方法和元類裡面的__call__方法執行的先後順序"""
  class MyTypeClass(type):
      def __call__(self, *args, **kwargs):
          print('__call__ run')
          super().__call__(*args, **kwargs)
  class MyClass(metaclass=MyTypeClass):
      def __init__(self, name):
          print('__init__ run')
          self.name = name
  obj = MyClass('jason')
  
# 定製物件的產生過程
  class MyTypeClass(type):
      def __call__(self, *args, **kwargs):
          # print('__call__ run')
          # print(args,kwargs)
          if args:
              raise Exception('必須全部採用關鍵字引數')
          super().__call__(*args, **kwargs)

  class MyClass(metaclass=MyTypeClass):
      def __init__(self, name):
          # print('__init__ run')
          self.name = name

	"""強制規定:類在例項化產生物件的時候 物件的獨有資料必須採用關鍵字引數"""
  # obj1 = MyClass('jason')
  obj2 = MyClass(name='jason')
"""
如果你想高度定製類的產生過程
	那麼編寫元類裡面的__init__方法
如果你想高度定製物件的產生過程
	那麼編寫元類裡面的__call__方法
"""

__new__

  new()方法是在新式類(新式類和經典類的區別)中的方法。object為所有新式類的基類,在object中,
new()方法被定義為靜態方法,並且至少需要傳遞一個引數cls,cls表示需要例項化的類。

  在建立一個類物件例項的過程中,new()方法作用在構造方法__init__()之前。new()函式執行後會返回實
例物件(self),然後將self作為第一個引數傳給該類的初始化方法__init__()方法。

  可以這麼理解,如果設計一個A類,A類中__new__()方法負責返回一個例項物件(不一定是A類物件),而
__init__()方法負責將類物件初始化(設定各種屬性之類)。在init()呼叫之前,new()可以決定是否要使
用init()方法,因為new()可以呼叫其他類的構造方法或者直接返回別的物件來作為本類(A類)的例項。
  #類A
  class A(object):
      def __init__(self):
          self.name = 'A' #設定name屬性

      def __new__(cls, *args, **kwargs):
          # 執行object的__new__()函式,傳入cls說明返回當前類(A類)的例項物件
          return object.__new__(cls)

  #類B
  class B(object):
      def __init__(self):
          self.name = 'B' #設定name屬性

      def __new__(cls, *args, **kwargs):
          #執行object的__new__()函式,傳入引數A說明返回(A類)的例項物件
          return object.__new__(A)

  a = A() 	#建立A類例項, 相當於隱式執行了A類的__new__()和__init__()
  b = B() 	#建立B類例項, 只執行了B類的__new__()
  print(a.name) 		#  A
  #print(b.name)    # 出錯:AttributeError: 'A' object has no attribute 'name'
  b.__init__( )        #顯示呼叫__init(),完成b的初始化
  print(b.name)     #  A  正常輸出
  print(type(a))     # <class '__main__.A'>
  print(type(b))     # <class '__main__.A'>


  #第一個print(b.name)報錯的原因是,想要建立B類例項的時候,new方法卻直接返回了A類例項物件
  #因此,沒有執行B類的init方法,也沒有執行A類的init方法,也就導致b物件沒有name屬性
  #在顯示呼叫init方法之後,b才有了name屬性,所以第二個的print(b.name)正常輸出

  """
  注意:並不是所有的地方都可以直接呼叫__new__ 該方法過於底層
  	如果是在元類的__new__裡面 可以直接呼叫
  		class Meta(type):
            def __new__(cls, *args, **kwargs):
                obj = type.__new__(cls,*args,**kwargs)
                return obj
  	如果是在元類的__call__裡面 需要間接呼叫
  		class Mate(type):
              def __call__(self, *args, **kwargs):
                  obj = object.__new__(self) # 建立一個空物件
                  self.__init__(obj,*args,**kwargs) # 讓物件去初始化
                  return obj
  """