python面向對象編程(下)

来源:http://www.cnblogs.com/feixuelove1009/archive/2016/06/28/5620660.html
-Advertisement-
Play Games

本篇將詳細介紹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,您的水平之高,讓我敬仰萬分!  

完!不對之處敬請指出,謝謝!

 

  

 

  

 

  

  

  

  

 

  

  

  

  

  

  

  

 

 

  

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • // 舉個例子:一個網站有用戶系統、商家系統、網站後臺3個系統 //可以分3個userType, user ,shop , system //網站後臺一般都有角色,如admin,employee //那麼網站的角色就有 user,shop,admin,employee,但是admin和employe ...
  • 這幾天在看Java的一些東西,除了覺的Java和.NET的相似度實在太高之外,就是Java太原始,急需被拯救。之後又回到.NET的思維來,想著怎麼在框架里實現讀寫分離控制請求切換。之前幾個月一直都有在思考,想及對框架現有的代碼改動可能較大,所以遲遲沒怎麼下手。最近終於在廁所蹲下的一瞬間,靈光一閃,感... ...
  • 我的個人博客站在使用百度富文本編輯器UEditor修改文章時,遇到了一些問題,(不知是bug,還是我沒有配置好)。但總算找到瞭解決方法,在此記錄下來。 小站首頁文章列表顯示為(顯示去除HTML標簽後的前600個字元): 具體在 www.zynblog.com 遇到的問題: 正常來講,進入文章修改頁, ...
  • 在這一個練習中,我們要使用帶返回值的方法。如果一個方法帶返回值,那麼它的形式是這樣的。 定義一個帶返回值的C#方法 static 返回類型 方法名字 (參數類型 參數1的名字,參數類型 參數2的名字) { 你的代碼 return 返回值 } static: 先不用管是什麼意思。在沒有講它的含義之前, ...
  • 最近寫了一個SOA服務,開始覺得別人拿到我的服務地址,然後直接添加引用就可以使用了,結果"大牛"告知不行。 讓我寫一個SOAP調用服務的樣例,我有點愣了,因為沒做過這方面的,於是搞到了一個Demo,然後學習了下。 學習如下: 在.Net中有一個對象:WebRequest它可以在後臺直接請求服務的方法 ...
  • 大家先來看看表單提交的幾種方式: 1、<!--通用提交按鈕--> <input type="submit" value="提交"> 2、<!--自定義提交按鈕--> <button type="Submit">提交</button> 3、<!--圖像按鈕--> <input type="image" ...
  • 函數最初的用處 大家剛學編程的時候,一定還記得為什麼要用函數。那就是把重覆的代碼歸納到一個函數中多次利用。這點毋庸置疑,大家也用的很熟了,但是除了這個還有什麼改進空間嗎?答案肯定是有的! PS.本文不討論面向對象、不討論設計模式,只是把視線聚焦在 Class 內部的函數上。 出現什麼問題了? 大家都 ...
  • ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...