Python學習——02-Python基礎——【9-面向對象進階】——__doc__、__module__和__class__等


十一 __doc__

class Foo:
    '我是描述信息'
    pass

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

class Bar(Foo):
    pass
print(Bar.__doc__) #該屬性無法繼承給子類
該屬性無法被繼承

十二 __module__和__class__

  __module__ 表示當前操作的對象在那個模塊

  __class__     表示當前操作的對象的類是什么

#!/usr/bin/env python
# -*- coding:utf-8 -*-

class C:

    def __init__(self):
        self.name = ‘SB'

lib/aa.py
lib/aa.py
from lib.aa import C

obj = C()
print obj.__module__  # 輸出 lib.aa,即:輸出模塊
print obj.__class__      # 輸出 lib.aa.C,即:輸出類
index.py

十三  __del__

析構方法,當對象在內存中被釋放時,自動觸發執行。

注:如果產生的對象僅僅只是python程序級別的(用戶級),那么無需定義__del__,如果產生的對象的同時還會向操作系統發起系統調用,即一個對象有用戶級與內核級兩種資源,比如(打開一個文件,創建一個數據庫鏈接),則必須在清除對象的同時回收系統資源,這就用到了__del__

class Foo:

    def __del__(self):
        print('執行我啦')

f1=Foo()
del f1
print('------->')

#輸出結果
執行我啦
------->

簡單示范
簡單示范
class Foo:

    def __del__(self):
        print('執行我啦')

f1=Foo()
# del f1
print('------->')

#輸出結果
------->
執行我啦





#為何啊???

挖坑埋了你
挖坑埋了你

典型的應用場景:

創建數據庫類,用該類實例化出數據庫鏈接對象,對象本身是存放於用戶空間內存中,而鏈接則是由操作系統管理的,存放於內核空間內存中

當程序結束時,python只會回收自己的內存空間,即用戶態內存,而操作系統的資源則沒有被回收,這就需要我們定制__del__,在對象被刪除前向操作系統發起關閉數據庫鏈接的系統調用,回收資源

這與文件處理是一個道理:

f=open('a.txt') #做了兩件事,在用戶空間拿到一個f變量,在操作系統內核空間打開一個文件
del f #只回收用戶空間的f,操作系統的文件還處於打開狀態

#所以我們應該在del f之前保證f.close()執行,即便是沒有del,程序執行完畢也會自動del清理資源,於是文件操作的正確用法應該是
f=open('a.txt')
讀寫...
f.close()
很多情況下大家都容易忽略f.close,這就用到了with上下文管理

十四 __enter__和__exit__

我們知道在操作文件對象的時候可以這么寫

1 with open('a.txt') as f:
2   '代碼塊'

上述叫做上下文管理協議,即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)

上下文管理協議
上下文管理協議

__exit__()中的三個參數分別代表異常類型,異常值和追溯信息,with語句中代碼塊出現異常,則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) #------------------------------->不會執行
View Code

如果__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) #------------------------------->會執行
View Code
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__處理

練習:模擬Open
練習:模擬Open

用途或者說好處:

1.使用with語句的目的就是把代碼塊放入with中執行,with結束后,自動完成清理工作,無須手動干預

2.在需要管理一些資源比如文件,網絡連接和鎖的編程環境中,可以在__exit__中定制自動釋放資源的機制,你無須再去關系這個問題,這將大有用處

十五 __call__

對象后面加括號,觸發執行。

注:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象后加括號觸發的,即:對象() 或者 類()()

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 執行 __init__
obj()       # 執行 __call__
View Code

十六 metaclass

1知識儲備

exec:三個參數

參數一:字符串形式的命令

參數二:全局作用域(字典形式),如果不指定,默認為globals()

參數三:局部作用域(字典形式),如果不指定,默認為locals()
#可以把exec命令的執行當成是一個函數的執行,會將執行期間產生的名字存放於局部名稱空間中
g={
    'x':1,
    'y':2
}
l={}

exec('''
global x,z
x=100
z=200

m=300
''',g,l)

print(g) #{'x': 100, 'y': 2,'z':200,......}
print(l) #{'m': 300}

exec的使用
exec的使用

2引子(類也是對象)

class Foo:
    pass

f1=Foo() #f1是通過Foo類實例化的對象

python中一切皆是對象,類本身也是一個對象,當使用關鍵字class的時候,python解釋器在加載class的時候就會創建一個對象(這里的對象指的是類而非類的實例),因而我們可以將類當作一個對象去使用,同樣滿足第一類對象的概念,可以:

  • 把類賦值給一個變量

  • 把類作為函數參數進行傳遞

  • 把類作為函數的返回值

  • 在運行時動態地創建類 

上例可以看出f1是由Foo這個類產生的對象,而Foo本身也是對象,那它又是由哪個類產生的呢?

1 #type函數可以查看類型,也可以用來查看對象的類,二者是一樣的
2 print(type(f1)) # 輸出:<class '__main__.Foo'>     表示,obj 對象由Foo類創建
3 print(type(Foo)) # 輸出:<type 'type'>  

元類是類的類,是類的模板

元類是用來控制如何創建類的,正如類是創建對象的模板一樣,而元類的主要目的是為了控制類的創建行為

元類的實例化的結果為我們用class定義的類,正如類的實例為對象(f1對象是Foo類的一個實例Foo類是 type 類的一個實例)

type是python的一個內建元類,用來直接控制生成類,python中任何class定義的類其實都是type類實例化的對象

 

 

4創建類的兩種方式

 方式一:使用class關鍵字

class Chinese(object):
    country='China'
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def talk(self):
        print('%s is talking' %self.name)

方式二:就是手動模擬class創建類的過程):將創建類的步驟拆分開,手動去創建

#准備工作:

#創建類主要分為三部分
類名
類的父類
類體


#類名
class_name='Chinese'
#類的父類
class_bases=(object,)
#類體
class_body="""
country='China'
def __init__(self,name,age):
    self.name=name
    self.age=age
def talk(self):
    print('%s is talking' %self.name)
"""

步驟一(先處理類體->名稱空間):類體定義的名字都會存放於類的名稱空間中(一個局部的名稱空間),我們可以事先定義一個空字典,然后用exec去執行類體的代碼(exec產生名稱空間的過程與真正的class過程類似,只是后者會將__開頭的屬性變形),生成類的局部名稱空間,即填充字典

class_dic={}
exec(class_body,globals(),class_dic)


print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}

步驟二:調用元類type(也可以自定義)來產生類Chinense

Foo=type(class_name,class_bases,class_dic) #實例化type得到對象Foo,即我們用class定義的類Foo


print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''

我們看到,type 接收三個參數:

  • 第 1 個參數是字符串 ‘Foo’,表示類名

  • 第 2 個參數是元組 (object, ),表示所有的父類

  • 第 3 個參數是字典,這里是一個空字典,表示沒有定義屬性和方法

補充:若Foo類有繼承,即class Foo(Bar):.... 則等同於type('Foo',(Bar,),{})

5 自定義元類控制類的行為

#一個類沒有聲明自己的元類,默認他的元類就是type,除了使用元類type,用戶也可以通過繼承type來自定義元類(順便我們也可以瞅一瞅元類如何控制類的行為,工作流程是什么)
#!!!如果你拷貝不注明出處的話,以后老子都不寫了!!!







#知識儲備:
    #產生的新對象 = object.__new__(繼承object類的子類)








#步驟一:如果說People=type(類名,類的父類們,類的名稱空間),那么我們定義元類如下,來控制類的創建
class Mymeta(type):  # 繼承默認元類的一堆屬性
    def __init__(self, class_name, class_bases, class_dic):
        if '__doc__' not in class_dic or not class_dic.get('__doc__').strip():
            raise TypeError('必須為類指定文檔注釋')

        if not class_name.istitle():
            raise TypeError('類名首字母必須大寫')

        super(Mymeta, self).__init__(class_name, class_bases, class_dic)


class People(object, metaclass=Mymeta):
    country = 'China'

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

    def talk(self):
        print('%s is talking' % self.name)








#步驟二:如果我們想控制類實例化的行為,那么需要先儲備知識__call__方法的使用
class People(object,metaclass=type):
    def __init__(self,name,age):
        self.name=name
        self.age=age

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


# 調用類People,並不會出發__call__
obj=People('egon',18)

# 調用對象obj(1,2,3,a=1,b=2,c=3),才會出發對象的綁定方法obj.__call__(1,2,3,a=1,b=2,c=3)
obj(1,2,3,a=1,b=2,c=3) #打印:<__main__.People object at 0x10076dd30> (1, 2, 3) {'a': 1, 'b': 2, 'c': 3}

#總結:如果說類People是元類type的實例,那么在元類type內肯定也有一個__call__,會在調用People('egon',18)時觸發執行,然后返回一個初始化好了的對象obj







#步驟三:自定義元類,控制類的調用(即實例化)的過程
class Mymeta(type): #繼承默認元類的一堆屬性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError('類名首字母必須大寫')

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}

        #1、實例化People,產生空對象obj
        obj=object.__new__(self)


        #2、調用People下的函數__init__,初始化obj
        self.__init__(obj,*args,**kwargs)


        #3、返回初始化好了的obj
        return obj

class People(object,metaclass=Mymeta):
    country='China'

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

    def talk(self):
        print('%s is talking' %self.name)



obj=People('egon',18)
print(obj.__dict__) #{'name': 'egon', 'age': 18}







#步驟四:
class Mymeta(type): #繼承默認元類的一堆屬性
    def __init__(self,class_name,class_bases,class_dic):
        if not class_name.istitle():
            raise TypeError('類名首字母必須大寫')

        super(Mymeta,self).__init__(class_name,class_bases,class_dic)

    def __call__(self, *args, **kwargs):
        #self=People
        print(self,args,kwargs) #<class '__main__.People'> ('egon', 18) {}

        #1、調用self,即People下的函數__new__,在該函數內完成:1、產生空對象obj 2、初始化 3、返回obj
        obj=self.__new__(self,*args,**kwargs)

        #2、一定記得返回obj,因為實例化People(...)取得就是__call__的返回值
        return obj

class People(object,metaclass=Mymeta):
    country='China'

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

    def talk(self):
        print('%s is talking' %self.name)


    def __new__(cls, *args, **kwargs):
        obj=object.__new__(cls)
        cls.__init__(obj,*args,**kwargs)
        return obj


obj=People('egon',18)
print(obj.__dict__) #{'name': 'egon', 'age': 18}






#步驟五:基於元類實現單例模式,比如數據庫對象,實例化時參數都一樣,就沒必要重復產生對象,浪費內存
class Mysql:
    __instance=None
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port

    @classmethod
    def singleton(cls,*args,**kwargs):
        if not cls.__instance:
            cls.__instance=cls(*args,**kwargs)
        return cls.__instance


obj1=Mysql()
obj2=Mysql()
print(obj1 is obj2) #False

obj3=Mysql.singleton()
obj4=Mysql.singleton()
print(obj3 is obj4) #True

#應用:定制元類實現單例模式
class Mymeta(type):
    def __init__(self,name,bases,dic): #定義類Mysql時就觸發
        self.__instance=None
        super().__init__(name,bases,dic)

    def __call__(self, *args, **kwargs): #Mysql(...)時觸發

        if not self.__instance:
            self.__instance=object.__new__(self) #產生對象
            self.__init__(self.__instance,*args,**kwargs) #初始化對象
            #上述兩步可以合成下面一步
            # self.__instance=super().__call__(*args,**kwargs)

        return self.__instance
class Mysql(metaclass=Mymeta):
    def __init__(self,host='127.0.0.1',port='3306'):
        self.host=host
        self.port=port


obj1=Mysql()
obj2=Mysql()

print(obj1 is obj2)

峰哥5步帶你學會元類
富哥5步帶你學會元類

6 練習題

 練習一:在元類中控制把自定義類的數據屬性都變成大寫

class Mymetaclass(type):
    def __new__(cls,name,bases,attrs):
        update_attrs={}
        for k,v in attrs.items():
            if not callable(v) and not k.startswith('__'):
                update_attrs[k.upper()]=v
            else:
                update_attrs[k]=v
        return type.__new__(cls,name,bases,update_attrs)

class Chinese(metaclass=Mymetaclass):
    country='China'
    tag='Legend of the Dragon' #龍的傳人
    def walk(self):
        print('%s is walking' %self.name)


print(Chinese.__dict__)
'''
{'__module__': '__main__',
 'COUNTRY': 'China', 
 'TAG': 'Legend of the Dragon',
 'walk': <function Chinese.walk at 0x0000000001E7B950>,
 '__dict__': <attribute '__dict__' of 'Chinese' objects>,                                         
 '__weakref__': <attribute '__weakref__' of 'Chinese' objects>,
 '__doc__': None}
'''
View Code

練習二:在元類中控制自定義的類無需__init__方法

  1.元類幫其完成創建對象,以及初始化操作;

  2.要求實例化時傳參必須為關鍵字形式,否則拋出異常TypeError: must use keyword argument

  3.key作為用戶自定義類產生對象的屬性,且所有屬性變成大寫

class Mymetaclass(type):
    # def __new__(cls,name,bases,attrs):
    #     update_attrs={}
    #     for k,v in attrs.items():
    #         if not callable(v) and not k.startswith('__'):
    #             update_attrs[k.upper()]=v
    #         else:
    #             update_attrs[k]=v
    #     return type.__new__(cls,name,bases,update_attrs)

    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError('must use keyword argument for key function')
        obj = object.__new__(self) #創建對象,self為類Foo

        for k,v in kwargs.items():
            obj.__dict__[k.upper()]=v
        return obj

class Chinese(metaclass=Mymetaclass):
    country='China'
    tag='Legend of the Dragon' #龍的傳人
    def walk(self):
        print('%s is walking' %self.name)


p=Chinese(name='egon',age=18,sex='male')
print(p.__dict__)
View Code

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM