本篇將詳細介紹Python 類的成員、成員修飾符、類的特殊成員。還有兩個類的綜合運用實例。 環境為:python3.5.1 類的成員 類的成員包括三大類:欄位、方法和屬性 最重要的是:所有成員中,只有普通欄位的內容保存在對象中,即:根據此類創建了多少對象,在記憶體中就有多少個普通欄位。而其他的成員,則 ...
本篇將詳細介紹Python 類的成員、成員修飾符、類的特殊成員。還有兩個類的綜合運用實例。
環境為:python3.5.1
類的成員
類的成員包括三大類:欄位、方法和屬性
最重要的是:所有成員中,只有普通欄位的內容保存在對象中,即:根據此類創建了多少對象,在記憶體中就有多少個普通欄位。而其他的成員,則都是保存在類中,即:無論對象的多少,在記憶體中只創建一份。
一、欄位
欄位包括:普通欄位和靜態欄位,他們在定義和使用中有所區別,而最本質的區別是記憶體中保存的位置不同。
- 普通欄位屬於對象。不實例化對象就不會創建欄位。每個對象都有一份自己的普通欄位。使用對象名.欄位名的方式進行訪問。
- 靜態欄位屬於類。所有對象共用一份,在代碼載入時就創建了,使用類名.欄位名的方式訪問。(也可以用對象名.類欄位訪問,但不建議這麼用。)
欄位有點像類和對象包含的變數,要理解欄位的含義:可以參考函數式編程中的局部變數和全部變數,但切切不可混為一談。
欄位的創建和調用方式:
class Province: # 靜態欄位 country = '中國' def __init__(self, name): # 普通欄位 self.name = name # 直接訪問普通欄位 obj = Province('河北省') print obj.name # 直接訪問靜態欄位 Province.country
【普通欄位需要通過對象來訪問】,【靜態欄位通過類訪問】,在使用上也可以看出普通欄位和靜態欄位的歸屬是不同的。其在內容的存儲方式類似如下圖:
反覆強調的是:
- 靜態欄位在記憶體中只保存一份
- 普通欄位在每個對象中都要保存一份
那麼什麼時候使用靜態欄位呢?類似全局變數的應用方式: 通過類創建對象時,如果每個對象都具有相同的欄位,那麼就使用靜態欄位。
二、方法
方法包括:普通方法、靜態方法和類方法,三種方法無論是在代碼中還是記憶體中都歸屬於類,區別在於傳入的參數和調用方式不同。
- 普通方法:由對象調用;至少一個self參數;執行普通方法時,自動將調用該方法的對象賦值給self;
- 類方法: 由類調用,採用@classmethod裝飾,至少闖入一個cls(代指類本身,類似self)參數;執行類方法時,自動將調用該方法的類賦值給cls;與對象無關。類方法是python語言獨有的方法類型,建議只使用類名.類方法的調用方式。(雖然也可以使用對象名.類方法的方式調用)
- 靜態方法:由類調用;無預設參數。將普通方法參數中的self去掉,然後在方法定義上方加上@staticmethod,就成為靜態方法。它屬於類,和對象無關。建議只使用類名.靜態方法的調用方式。(雖然也可以使用對象名.靜態方法的方式調用)
下麵是三種方法的定義和調用方式
class Foo: def __init__(self, name): self.name = name def ord_func(self): """ 定義普通方法,至少有一個self參數 """ print('普通方法') @classmethod def class_func(cls): """ 定義類方法,至少有一個cls參數 """ print('類方法') @staticmethod def static_func(): """ 定義靜態方法 ,無預設參數""" print('靜態方法') # 調用普通方法 f = Foo() f.ord_func() # 調用類方法 Foo.class_func() # 調用靜態方法 Foo.static_func()
三、屬性
Python中的屬性其實是普通方法的變種。
對於屬性,有兩個知識點:
- 屬性的基本使用
- 屬性的兩種定義方式
1、屬性的基本使用
# ############### 定義 ############### class Foo: def func(self): pass # 定義屬性 @property def prop(self): pass # ############### 調用 ############### foo_obj = Foo() foo_obj.func() foo_obj.prop #調用屬性
從本質上而言,屬性其實就是假裝成欄位的普通方法!
由屬性的定義和調用可以發現屬性的幾個特點:
- 定義時,在普通方法的基礎上添加 @property 裝飾器;
- 定義時,屬性僅有一個self參數
- 調用時,無需括弧(也就是類似欄位的調用方式)
方法:foo_obj.func()
屬性:foo_obj.prop
欄位:foo_obj.name
屬性存在意義是:訪問屬性時可以製造出和訪問欄位完全相同的假象.至於有什麼用,需要實踐中去發現。屬性由普通方法變種而來,如果Python中沒有屬性,方法完全可以代替其功能。
2、屬性的兩種定義方式
屬性的定義有兩種方式:
- 裝飾器 即:在方法上應用裝飾器
- 靜態欄位 即:在類中定義值為property對象的靜態欄位
裝飾器方式:在類的普通方法上應用@property裝飾器
# ############### 定義 ############### class Goods(object): @property def price(self): print '@property' @price.setter def price(self, value): print '@price.setter' @price.deleter def price(self): print '@price.deleter' # ############### 調用 ############### obj = Goods() obj.price # 自動執行 @property 修飾的 price 方法,並獲取方法的返回值 obj.price = 123 # 自動執行 @price.setter 修飾的 price 方法,並將 123 賦值給方法的參數 del obj.price # 自動執行 @price.deleter 修飾的 price 方法
仔細分析上面的代碼,你會發現關鍵是這麼三個東西@property、@方法名.setter、@方法名.deleter修飾。他們其實共同定義了一個屬性,只不過是針對該屬性的三種不同操作。
最基本的@property下麵定義的代碼塊,決定了類似“result = obj.price"執行什麼代碼;
@方法名.setter下麵定義的代碼塊,決定了類似“obj.price = xxx"這樣的賦值語句執行什麼代碼;
@方法名.deleter下麵定義的代碼塊,決定了類似“del obj.price "這樣的語句具體執行什麼代碼;(註意,這裡的del並非必須是刪除某個東西的功能,僅僅是調用方式)
他們分別將三個方法定義為對同一個屬性:獲取、修改和刪除。其實就是price這個屬性的一體三面!

class Goods(object): def __init__(self): # 原價 self.original_price = 100 # 折扣 self.discount = 0.8 @property def price(self): # 實際價格 = 原價 * 折扣 new_price = self.original_price * self.discount return new_price @price.setter def price(self, value): self.original_price = value @price.deltter def price(self, value): del self.original_price obj = Goods() obj.price # 獲取商品價格 obj.price = 200 # 修改商品原價 del obj.price # 刪除商品原價實例
感覺似乎在哪見過這種類似的東西?setattr()、getattr()、delattr()?還有__setitem__、__getitem__、__delitem__?你是不是發現在python的哲學里處處都有這種東西?我們再來看一下屬性的靜態欄位方式,你的感覺就會更加強烈了!
靜態欄位方式,創建值為property對象的靜態欄位
class Foo: def get_bar(self): return 'wupeiqi' BAR = property(get_bar) obj = Foo() reuslt = obj.BAR # 自動調用get_bar方法,並獲取方法的返回值 print(reuslt)
property類的構造方法中有個四個參數
- 第一個參數是方法名,調用
對象.屬性
時自動觸發執行方法 - 第二個參數是方法名,調用
對象.屬性 = XXX
時自動觸發執行方法 - 第三個參數是方法名,調用
del 對象.屬性
時自動觸發執行方法 - 第四個參數是字元串,調用
對象.屬性.__doc__
,此參數是該屬性的描述信息
class Foo: def get_bar(self): return 'jack' # *必須兩個參數 def set_bar(self, value): return return 'set value' + value def del_bar(self): return 'jack' BAR = property(get_bar, set_bar, del_bar, 'description...') obj = Foo() obj.BAR # 自動調用第一個參數中定義的方法:get_bar obj.BAR = "andy" # 自動調用第二個參數中定義的方法:set_bar方法,並將“andy”當作參數傳入 del Foo.BAR # 自動調用第三個參數中定義的方法:del_bar方法 obj.BAE.__doc__ # 自動獲取第四個參數中設置的值:description...

class Goods(object): def __init__(self): # 原價 self.original_price = 100 # 折扣 self.discount = 0.8 def get_price(self): # 實際價格 = 原價 * 折扣 new_price = self.original_price * self.discount return new_price def set_price(self, value): self.original_price = value def del_price(self, value): del self.original_price PRICE = property(get_price, set_price, del_price, '價格屬性描述...') obj = Goods() obj.PRICE # 獲取商品價格 obj.PRICE = 200 # 修改商品原價 del obj.PRICE # 刪除商品原價實例
讓我們看一下property構造方法的源代碼:
def __init__(self, fget=None, fset=None, fdel=None, doc=None): # known special case of property.__init__ """ property(fget=None, fset=None, fdel=None, doc=None) -> property attribute fget is a function to be used for getting an attribute value, and likewise fset is a function for setting, and fdel a function for del'ing, an attribute. Typical use is to define a managed attribute x: class C(object): def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") Decorators make defining new properties or modifying existing ones easy: class C(object): @property def x(self): "I am the 'x' property." return self._x @x.setter def x(self, value): self._x = value @x.deleter def x(self): del self._x # (copied from class doc) """ pass
看見fget=None, fset=None, fdel=None這三個預設參數麽?其實就是@property、@方法名.setter、@方法名.deleter。
無論是裝飾函數還是靜態欄位,都只是定義屬性的兩種方式而已。在實際的項目中,Python WEB框架 Django 的視圖中 request.POST 就是使用的靜態欄位的方式創建的屬性。

class WSGIRequest(http.HttpRequest): def __init__(self, environ): script_name = get_script_name(environ) path_info = get_path_info(environ) if not path_info: # Sometimes PATH_INFO exists, but is empty (e.g. accessing # the SCRIPT_NAME URL without a trailing slash). We really need to # operate as if they'd requested '/'. Not amazingly nice to force # the path like this, but should be harmless. path_info = '/' self.environ = environ self.path_info = path_info self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/')) self.META = environ self.META['PATH_INFO'] = path_info self.META['SCRIPT_NAME'] = script_name self.method = environ['REQUEST_METHOD'].upper() _, content_params = cgi.parse_header(environ.get('CONTENT_TYPE', '')) if 'charset' in content_params: try: codecs.lookup(content_params['charset']) except LookupError: pass else: self.encoding = content_params['charset'] self._post_parse_error = False try: content_length = int(environ.get('CONTENT_LENGTH')) except (ValueError, TypeError): content_length = 0 self._stream = LimitedStream(self.environ['wsgi.input'], content_length) self._read_started = False self.resolver_match = None def _get_scheme(self): return self.environ.get('wsgi.url_scheme') def _get_request(self): warnings.warn('`request.REQUEST` is deprecated, use `request.GET` or ' '`request.POST` instead.', RemovedInDjango19Warning, 2) if not hasattr(self, '_request'): self._request = datastructures.MergeDict(self.POST, self.GET) return self._request @cached_property def GET(self): # The WSGI spec says 'QUERY_STRING' may be absent. raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '') return http.QueryDict(raw_query_string, encoding=self._encoding) # ############### 看這裡看這裡 ############### def _get_post(self): if not hasattr(self, '_post'): self._load_post_and_files() return self._post # ############### 看這裡看這裡 ############### def _set_post(self, post): self._post = post @cached_property def COOKIES(self): raw_cookie = get_str_from_wsgi(self.environ, 'HTTP_COOKIE', '') return http.parse_cookie(raw_cookie) def _get_files(self): if not hasattr(self, '_files'): self._load_post_and_files() return self._files # ############### 看這裡看這裡 ############### POST = property(_get_post, _set_post) FILES = property(_get_files) REQUEST = property(_get_request)Django源碼
類成員的修飾符
對於每一個類的成員而言都有兩種形式:
- 公有成員,在任何地方都能訪問
- 私有成員,只有在類的內部才能方法
私有成員和公有成員的定義不同:私有成員命名時,前兩個字元是下劃線。(特殊成員除外,例如:__init__、__call__、__dict__等)
class C: def __init__(self): self.name = '公有欄位' self.__foo = "私有欄位"
私有成員和公有成員的訪問限制不同:
靜態欄位
- 公有靜態欄位:類可以訪問;類內部可以訪問;派生類中可以訪問
- 私有靜態欄位:僅類內部可以訪問;

class C: name = "公有靜態欄位" def func(self): print C.name class D(C): def show(self): print C.name C.name # 類訪問 obj = C() obj.func() # 類內部可以訪問 obj_son = D() obj_son.show() # 派生類中可以訪問公有靜態欄位

class C: __name = "公有靜態欄位" def func(self): print C.__name class D(C): def show(self): print C.__name C.__name # 類訪問 ==> 錯誤 obj = C() obj.func() # 類內部可以訪問 ==> 正確 obj_son = D() obj_son.show() # 派生類中可以訪問 ==> 錯誤私有靜態欄位
普通欄位
- 公有普通欄位:對象可以訪問;類內部可以訪問;派生類中可以訪問
- 私有普通欄位:僅類內部可以訪問;
ps:如果想要強制訪問私有欄位,可以通過 【對象._類名__私有欄位明 】訪問(如:obj._C__foo),不建議強制訪問私有成員。

class C: def __init__(self): self.foo = "公有欄位" def func(self): print self.foo # 類內部訪問 class D(C): def show(self): print self.foo # 派生類中訪問 obj = C() obj.foo # 通過對象訪問 obj.func() # 類內部訪問 obj_son = D(); obj_son.show() # 派生類中訪問公有普通欄位

class C: def __init__(self): self.__foo = "私有欄位" def func(self): print self.foo # 類內部訪問 class D(C): def show(self): print self.foo # 派生類中訪問 obj = C() obj.__foo # 通過對象訪問 ==> 錯誤 obj.func() # 類內部訪問 ==> 正確 obj_son = D(); obj_son.show() # 派生類中訪問 ==> 錯誤私有普通欄位
方法、屬性的訪問於上述方式相似,即:私有成員只能在類內部使用
ps:非要訪問私有屬性的話,可以通過 對象._類__屬性名
類的特殊成員
上面介紹了Python的類成員以及成員修飾符,從而瞭解到類中有欄位、方法和屬性三大類成員,並且成員名前如果有兩個下劃線,則表示該成員是私有成員,私有成員只能由類內部調用。無論人或事物往往都有不按套路出牌的情況,Python的類成員也是如此,存在著一些具有特殊含義的成員,詳情如下:
1. __doc__
表示類的描述信息。python自建,無需自定義。
class Foo: """ 描述類信息,可被自動收集 """ def func(self): pass print(Foo.__doc__) #輸出:類的描述信息
2. __module__ 和 __class__
__module__ 表示當前操作的對象在那個模塊
__class__ 表示當前操作的對象的類是什麼
python自建,無需自定義。
#!/usr/bin/env python # -*- coding:utf-8 -*- from f1 import Foo obj = Foo() print(obj.__module__) 運行結果: f1
from lib.aa import C obj = C() print(obj.__module__) # 輸出 lib.aa,即:輸出模塊 print(obj.__class__) # 輸出 lib.aa.C,即:輸出類
3. __init__
構造方法,通過類創建對象時,自動觸發執行。
class Foo: def __init__(self, name): self.name = name self.age = 18 obj = Foo(jack') # 自動執行類中的 __init__ 方法
4. __del__
析構方法,當對象在記憶體中被釋放時,自動觸發執行。
註:此方法一般無須定義,因為Python是一門高級語言,程式員在使用時無需關心記憶體的分配和釋放,因為此工作都是交給Python解釋器來執行,所以,析構函數的調用是由解釋器在進行垃圾回收時自動觸發執行的。
class Foo: def __del__(self): print("我被回收了!")
5. __call__
對象後面加括弧,觸發執行。前提是用戶在類中定義了該方法。
註:構造方法的執行是由創建對象觸發的,即:對象 = 類名() ;而對於 __call__ 方法的執行是由對象後加括弧觸發的,即:對象() 或者 類()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print('__call__') obj = Foo() # 執行 __init__ obj() # 執行 __call__
6. __dict__
列出類或對象中的所有成員!非常重要和有用的一個方法,python自建,無需用戶自己定義。
上文中我們知道:類的普通欄位屬於對象;類中的靜態欄位和方法等屬於類,即:
class Province: country = 'China' def __init__(self, name, count): self.name = name self.count = count def func(self, *args, **kwargs): print('func') # 獲取類的成員,即:靜態欄位、方法、 print(Province.__dict__) # 輸出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None} obj1 = Province('HeBei',10000) print(obj1.__dict__) # 獲取 對象obj1 的成員 # 輸出:{'count': 10000, 'name': 'HeBei'} obj2 = Province('HeNan', 3888) print(obj2.__dict__) # 獲取 對象obj1 的成員 # 輸出:{'count': 3888, 'name': 'HeNan'}
7. __str__
如果一個類中定義了__str__方法,那麼在列印對象時,預設輸出該方法的返回值。
這也是一個非常重要的方法,需要用戶自己定義。
class Foo: def __str__(self): return 'jack' obj = Foo() print(obj) # 輸出:wupeiqi
8、__getitem__、__setitem__、__delitem__
用於索引操作,如字典。以上分別表示獲取、設置、刪除數據。
這也是非常重要的方法,需要用戶自己定義。
我們知道,變數名後面加圓括弧,通常代表執行或調用的意思;而在變數名後面加中括弧[],通常代表取值的意思。python設計了這三個特殊成員,用於執行與中括弧有關的動作。
類似上面屬性的定義方法,也分獲取、設置和刪除三種操作。
a = 變數名[] : 執行__getitem__方法
變數名[] = a : 執行__setitem__方法
del 變數名[] : 執行__delitem__方法
#!/usr/bin/env python # -*- coding:utf-8 -*- class Foo(object): def __getitem__(self, key): print('__getitem__',key) def __setitem__(self, key, value): print('__setitem__',key,value) def __delitem__(self, key): print('__delitem__',key) obj = Foo() result = obj['k1'] # 自動觸發執行 __getitem__ obj['k2'] = 'jack' # 自動觸發執行 __setitem__ del obj['k1'] # 自動觸發執行 __delitem__
9、__getslice__、__setslice__、__delslice__
這三個方法用於分片操作,也就是類似list[1:7:2]。但是在python3中已經被__getitem__、__setitem__、__delitem__所替代。
那麼__getitem__、__setitem__、__delitem__是如何區分你是要取值呢?還是要分片呢?通過傳入參數的類型!
class Foo: def __init__(self): pass def __getitem__(self, item): print(type(item)) def __setitem__(self, key, value): pass def __delitem__(self, key): pass obj = Foo() result = obj[1] result = obj["key"] result = obj[1:8] 運行結果: <class 'int'> <class 'str'> <class 'slice'>
看到沒有?類似obj[1:8]方式調用的時候,傳入的參數是個slice類型!
當__getitem__方法接收到一個slice類型的時候就知道要進行切片的相關處理,於是它提供了start、stop和step三個變數,分別表示起始、終止和步長。
class Foo: def __init__(self): pass def __getitem__(self, item): print(type(item)) print("start= ", item.start) print("stop= ", item.stop) print("step= ", item.step) def __setitem__(self, key, value): pass def __delitem__(self, key): pass obj = Foo() result = obj[1:8:2] 運行結果: <class 'slice'> start= 1 stop= 8 step= 2
__setitem__和__delitem__方法對切麵的處理方式類似。
10. __iter__
迭代器方法!之所以列表、字典、元組可以進行for迴圈,是因為類型內部定義了 __iter__這個方法。如果用戶想讓它自定義的類的對象可以被迭代,那麼就需要在類中定義這個方法,並且讓該方法返回值是一個可迭代的對象。當在代碼中利用for迴圈對對象進行遍歷時,就會調用類的這個__iter__方法。看起來很繞,下麵的三步能讓你感覺清晰。
第一步:
class Foo(object): pass obj = Foo() for i in obj: print(i) # 報錯:TypeError: 'Foo' object is not iterable
# 原因是Foo對象不可迭代
第二步:
class Foo(object): def __iter__(self): pass obj = Foo() for i in obj: print(i) # 報錯:TypeError: iter() returned non-iterator of type 'NoneType' #原因是 __iter__方法沒有返回一個可迭代的對象
第三步:
class Foo(object): def __init__(self, sq): self.sq = sq def __iter__(self): return iter(self.sq) obj = Foo([11,22,33,44]) for i in obj: print(i) # 這下沒問題了!
以上步驟可以看出,for迴圈迭代的其實是 iter([11,22,33,44]) ,所以執行流程可以變更為:
#!/usr/bin/env python # -*- coding:utf-8 -*- obj = iter([11,22,33,44]) for i in obj: print(i)
其實For迴圈語法內部也就是:
#!/usr/bin/env python # -*- coding:utf-8 -*- obj = iter([11,22,33,44]) while True: val = obj.next() print(val)
python有個yield的強大語法,可以將函數變成生成器,生成的就是個迭代器。
class Foo: def __init__(self): pass def __iter__(self): yield 1 yield 2 yield 3 obj = Foo() for i in obj: print(i) 運行結果: 1 2 3
11. __new__ 和 __metaclass__
閱讀以下代碼:
class Foo(object): def __init__(self): pass obj = Foo() # obj是通過Foo類實例化的對象 print(type(obj)) # 輸出:<class '__main__.Foo'> 表示,obj 對象由Foo類創建 print(type(Foo)) # 輸出:<type 'type'> 表示,Foo類對象由 type 類創建
有一個概念必須牢記,那就是在Python中一切事物都是對象!。上述代碼顯示,obj 是通過 Foo 類實例化的對象,Foo類本身也是一個type類創建的對象。obj對象是通過執行Foo類的構造方法創建的,Foo類對象也是通過type類的構造方法創建得。所以,obj對象是Foo類的一個實例,Foo類對象是 type 類的一個實例。
按照上面的理論,那麼,創建類就可以有兩種方式:
a). 普通方式
class Foo: def __init__(self): pass
b).特殊方式(type類的構造函數)
def func(self): print("i am jack") Foo = type('Foo', (object,), {'func': func}) #type第一個參數:類名 #type第二個參數:當前類的基類 #type第三個參數:類的成員
那麼問題來了,類預設是由 type 類實例化產生,type類中如何實現的創建類?類又是如何創建對象?
答:類中有一個屬性 __metaclass__,用來表示該類由誰來實例化創建,所以,我們可以為 __metaclass__ 設置一個type類的派生類,從而查看類創建的過程。
而對於__new__:
在python2.X中
繼承自object的新式類才有__new__
__new__至少要有一個參數cls,代表要實例化的類,此參數在實例化時由Python解釋器自動提供
__new__必須要有返回值,返回實例化出來的實例,這點在自己實現__new__時要特別註意,可以return父類__new__出來的實例,或者直接是object的__new__出來的實例
__init__有一個參數self,就是這個__new__返回的實例,__init__在__new__的基礎上可以完成一些其它初始化的動作,__init__不需要返回值
若__new__沒有正確返回當前類cls的實例,那__init__是不會被調用的,即使是父類的實例也不行
由於博主使用的是python3.5,因此上面的代碼需要微小的調整一下,主要是object類不接收參數。
class MyType(type): def __init__(self, what, bases=None, dict=None): super(MyType, self).__init__(what, bases, dict) def __call__(self, *args, **kwargs): obj = self.__new__(self, *args, **kwargs) self.__init__(obj) class Foo(object): __metaclass__ = MyType def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): return object.__new__(cls) Foo_obj = Foo("jack") print(Foo_obj.name)
要理解上這一切,核心要點是:類也是由type這個”老祖宗”實例化的對象!
上面的代碼執行可以分為兩個階段:
第一階段:Foo類的實例化過程,它處於obj = Foo("jack")這句代碼之前。
1. 在python從上往下順序執行的過程中發現了__metaclass__ = MyType這麼個東西,於是明白了,Foo類需要由MyType這麼一個自定義的繼承了type類的類來實例化。
2. 自動調用MyType類中的__init__(self, what, bases=None, dict=None)方法創建了類Foo這麼個實例化對象。(Foo在MyType眼中就是個對象)
第二階段:程式執行到obj = Foo("jack")這句的時候
1. 發現這是個對象+括弧的語法,還記得前面的__call__麽,對了,它就會自動去調用MyType的__call__方法;
2.來到obj = self.__new__(self, *args, **kwargs)這句,右邊的self.__new__指向的是誰呢?self就是Foo這個對象,當然是Foo.__new__了。於是程式又返回了。
3.程式發現Foo.__new__()里只有這麼一句return object.__new__(cls),這是利用頂級父類object創建一個對象,並將它作為返回值。好吧,我們接著回到MyType;
4.這時候obj = object.__new__(cls)了,開始執行下一句self.__init__(obj)。這是讓程式執行Foo的構造方法,傳入的參數就是我們剛纔獲得的返回值obj;
5. 最後self.name = name這一句接收了“jack”參數,於是最終外圍的Foo_obj對象被創建了!
很複雜吧?!
一些與類相關的知識點
一、isinstance(obj, cls)
檢查是否obj是否是類 cls 或者cls的父類的對象。是則返回True,否則返回False。
class Foo(object): pass obj = Foo() isinstance(obj, Foo)
二、issubclass(sub, super)
檢查sub類是否是 super 類的派生類。返回True或False。
class Foo(object): pass class Bar(Foo): pass issubclass(Bar, Foo)
三、父類的調用方式
先看代碼:
class A: def __init__(self, name): self.name = name def show(self): print(self.name) return 123 class B(A): def __init__(self, name, age): super(B, self).__init__(name=name) self.age = age def show(self): ret = super(B, self).show() print(self.age) return ret obj = B("jack", 18) ret = obj.show() print(ret) 運行結果: jack 18 123
我們都知道,在子類中如果有與父類同名的成員,那就會覆蓋掉父類里的成員。那如果你想強制調用父類的成員呢?使用super函數!這是一個非常強大的函數!
super的語法是類似這樣的:super(B, self).show(),需要傳入的是子類名和self,調用的是父類里的方法(在這裡是show),按父類的方法傳入參數。
類綜合運用實例一:擴展源代碼
我們在實際運用中,往往會碰到需要對源代碼進行功能擴展的情況。通常我們不希望修改源代碼,因此裝飾器的做法就無法利用。一般我們會使用繼承父類,並使用super調用父類的方法來實現需求。
有如下模塊結構:
其中,backend包下的commons模塊是我們的源代碼,或者說是很NB,功能很強大的某個現成的框架,假設它的代碼如下:
commons.py:
#!/usr/bin/env python # -*- coding:utf-8 -*- class Foo: def f1(self): print("Foo.f1")
有一個Foo類,類里有一個f1函數,它輸出一條信息。
settings模塊是配置文件,裡面有一個ClassName變數,值是個字元串,指向commons里的類名Foo
settings.py:
#!/usr/bin/env python # -*- coding:utf-8 -*- ClassName = "Foo"
而最關鍵的index模塊是我們的程式入口。
index.py:
#!/usr/bin/env python # -*- coding:utf-8 -*- from settings import ClassName from backend import commons def execute(): cls = getattr(commons, ClassName) obj = cls() obj.f1() if __name__ == '__main__': execute()
通過getattr的反射功能,我們很容易的動態獲取到commons模塊里的Foo類,並實例化了一個obj對象,然後調用f1函數。這是我們正常情況下利用源代碼或類似django這種框架的方法。
那麼現在,我要擴展上面的源代碼也就是commons里的功能,希望在f1函數執行前後分別進行一定的操作。要怎麼辦呢?不能用裝飾器,它會修改commons模塊。解決思路就是利用上面的繼承和super方法。
首先我們創建個新的模塊lib.py:
#!/usr/bin/env python # -*- coding:utf-8 -*- from backend.commons import Foo class MyFoo(Foo): def f1(self): print("before do something!") super(MyFoo, self).f1() print("after do something!")
它導入了源碼的Foo類,並創建了一個MyFoo類繼承Foo,同樣也定義了一個f1方法,但是在裡面使用super調用了原來Foo類中的f1。兩條print語句模擬了前後的操作。
其次我們分別修改了另外兩個文件:
setting.py:
#!/usr/bin/env python # -*- coding:utf-8 -*- Path = "lib" ClassName = "MyFoo"
配置文件增加了path,指向lib模塊,classname也不再指向commons里的Foo,而是lib里的MyFoo。
index.py:
from settings import ClassName from settings import Path def execute(): module = __import__(Path, fromlist=True) cls = getattr(module, ClassName) obj = cls() obj.f1() if __name__ == '__main__': execute()
利用__import__(Path, fromlist=True)的反射方法,我們動態的載入了lib模塊,然後獲得了lib里的MyFoo類,再用MyFoo類實例化了一個對象,再用對象調用MyFoo類的f1方法。最後結果是:
before do something!
Foo.f1
after do something!
我們成功的在不修改源代碼的情況下,擴展了f1的功能。梳理一下核心:MyFoo的繼承,使我們可以不修改源碼的同時擴展源碼,settings配置文件讓我們可以方便的切換模塊和類,index里的動態載入使我們不必局限在某一個類或模塊里。
類綜合運用實例二:自定義有序字典
我們都知道,在python里字典類型是無序的,那麼有沒有辦法實現一個有序字典呢?其實很簡單。(這裡介紹的是手工編寫,不是使用collection模塊里的orderedDict)
代碼如下:
#!/usr/bin/env python # -*- coding:utf-8 -*- class MyDict(dict): # MyDict類繼承python內置的dict類 def __init__(self): self.li = [] # 構造方法中實例對象的時候,添加一個列表,用於保存有序的key super(MyDict, self).__init__() # 調用父類的構造方法 def __setitem__(self, key, value): self.li.append(key) # 每執行一次obj[key] = value的時候就將key添加到列表裡 super(MyDict, self).__setitem__(key, value) # 調用父類的__setitem__方法 def __str__(self): # 當執行print(類的對象)語句時,會自動執行該方法。 temp_list = [] # 下麵是一個粗糙的格式化輸出的控制代碼 for key in self.li: value = self.get(key) temp_list.append("'%s':%s" % (key, value)) temp_str = "{" + ",".join(temp_list) + "}" return temp_str if __name__ == '__main__': # 以下是調用過程 obj = MyDict() obj["k1"] = 123 obj["k2"] = 456 obj["k3"] = "jack" obj["k4"] = "andy" obj["k5"] = [1, 2, 3] print(obj) 運行結果: {'k1':123,'k2':456,'k3':jack,'k4':andy,'k5':[1, 2, 3]}
實際測試中,你會發現,不管你指定多少次,它都是按這個順序輸出字典的內容。
這段代碼本身有很多問題,一是只實現了print輸出的有序,而沒有實現其他情況下的有序,這是偷懶行為。二是只實現了__setitem__,也就是obj[key] = value的執行方式,對於__getitem__
和__delitem__沒有實現。三是__str__方法中的代碼寫得很爛......
其實,以上都不重要。重點是要明白通過保存一個key的有序列表,實現了dict的有序;通過__setitem__的方法,既執行了列表的append操作,又執行了super的__setitem__方法,其中的編程思想閃耀著光輝。
鳴謝:武sir,您的水平之高,讓我敬仰萬分!
完!不對之處敬請指出,謝謝!