類(3)

2018-02-24 15:48 更新

在上一節(jié)中,對類有了基本的或者說是模糊的認(rèn)識,為了能夠?qū)︻愑懈羁痰恼J(rèn)識,本節(jié)要深入到一些細(xì)節(jié)。

類屬性和實例屬性

正如上節(jié)的案例中,一個類實例化后,實例是一個對象,有屬性。同樣,類也是一個對象,它也有屬性。

>>> class A(object):
...     x = 7
... 

在交互模式下,定義一個很簡單的類(注意觀察,有(object),是新式類),類中有一個變量x = 7,當(dāng)然,如果愿意還可以寫別的。因為一下操作中,只用到這個,我就不寫別的了。

>>> A.x
7

在類A中,變量x所引用的數(shù)據(jù),能夠直接通過類來調(diào)用?;蛘哒fx是類A的屬性,這種屬性有一個名稱,曰“類屬性”。類屬性僅限于此——類中的變量。它也有其他的名字,如靜態(tài)數(shù)據(jù)。

>>> foo = A()
>>> foo.x
7

實例化,通過實例也可以得到這個屬性,這個屬性叫做“實例屬性”。對于同一屬性,可以用類來訪問(類屬性),在一般情況下,也可以通過實例來訪問同樣的屬性。但是:

>>> foo.x += 1
>>> foo.x
8
>>> A.x
7

實例屬性更新了,類屬性沒有改變。這至少說明,類屬性不會被實例屬性左右,也可以進(jìn)一步說“類屬性與實例屬性無關(guān)”。那么,foo.x += 1的本質(zhì)是什么呢?其本質(zhì)是該實例foo又建立了一個新的屬性,但是這個屬性(新的foo.x)居然與原來的屬性(舊的foo.x)重名,所以,原來的foo.x就被“遮蓋了”,只能訪問到新的foo.x,它的值是8.

>>> foo.x
8
>>> del foo.x
>>> foo.x
7

既然新的foo.x“遮蓋”了舊的foo.x,如果刪除它,舊的不久顯現(xiàn)出來了?的確是。刪除之后,foo.x就還是原來的值。此外,還可以通過建立一個不與它重名的實例屬性:

>>> foo.y = foo.x + 1
>>> foo.y
8
>>> foo.x
7

foo.y就是新建的一個實例屬性,它沒有影響原來的實例屬性foo.x。

但是,類屬性能夠影響實例屬性,這點應(yīng)該好理解,因為實例就是通過實例化調(diào)用類的。

>>> A.x += 1
>>> A.x
8
>>> foo.x
8

這時候?qū)嵗龑傩愿悓傩远淖儭?/p>

以上所言,是指當(dāng)類中變量引用的是不可變數(shù)據(jù)。如果類中變量引用可變數(shù)據(jù),情形會有所不同。因為可變數(shù)據(jù)能夠進(jìn)行原地修改。

>>> class B(object):
...     y = [1,2,3]
...

這次定義的類中,變量引用的是一個可變對象。

>>> B.y         #類屬性
[1, 2, 3]
>>> bar = B()
>>> bar.y       #實例屬性
[1, 2, 3]

>>> bar.y.append(4)
>>> bar.y
[1, 2, 3, 4]
>>> B.y
[1, 2, 3, 4]

>>> B.y.append("aa")
>>> B.y
[1, 2, 3, 4, 'aa']
>>> bar.y
[1, 2, 3, 4, 'aa']

從上面的比較操作中可以看出,當(dāng)類中變量引用的是可變對象是,類屬性和實例屬性都能直接修改這個對象,從而影響另一方的值。

對于類屬性和實例屬性,除了上述不同之外,在下面的操作中,也會有差異。

>>> foo = A()
>>> dir(foo)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'x']

實例化類A,可以查看其所具有的屬性(看最后一項,x),當(dāng)然,執(zhí)行dir(A)也是一樣的。

>>> A.y = "hello"
>>> foo.y
'hello'

增加一個類屬性,同時在實例屬性中也增加了一樣的名稱和數(shù)據(jù)的屬性。如果增加通過實例增加屬性呢?看下面:

>>> foo.z = "python"
>>> foo.z
'python'
>>> A.z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute 'z'

類并沒有收納這個屬性。這進(jìn)一步說明,類屬性不受實例屬性左右。另外,在類確定或者實例化之后,也可以增加和修改屬性,其方法就是通過類或者實例的點號操作來實現(xiàn),即object.attribute,可以實現(xiàn)對屬性的修改和增加。

數(shù)據(jù)流轉(zhuǎn)

在類的應(yīng)用中,最廣泛的是將類實例化,通過實例來執(zhí)行各種方法。所以,對此過程中的數(shù)據(jù)流轉(zhuǎn)一定要弄明白。

回顧上節(jié)已經(jīng)建立的那個類,做適當(dāng)修改,并請出"canglaoshi"。但是,我將注釋刪除,讀者是否能夠?qū)懮媳匾淖⑨屇??如果你把注釋寫上,就已?jīng)理解了類的基本結(jié)構(gòu)。

#!/usr/bin/env python
# coding=utf-8

__metaclass__ = type

class Person:
    def __init__(self, name):
        self.name = name

    def getName(self):
        return self.name

    def breast(self, n):
        self.breast = n

    def color(self, color):
        print "%s is %s" % (self.name, color)

    def how(self):
        print "%s breast is %s" % (self.name, self.breast)

girl = Person('canglaoshi')
girl.breast(90)

girl.color("white")
girl.how()

運行后結(jié)果:

$ python 20701.py 
canglaoshi is white
canglaoshi breast is 90

一圖勝千言,有圖有真相。通過圖示,我們看一看數(shù)據(jù)的流轉(zhuǎn)過程。

創(chuàng)建實例girl = Person('canglaoshi'),注意觀察圖上的箭頭方向。girl這個實例和Person類中的self對應(yīng),這正是應(yīng)了上節(jié)所概括的“實例變量與self對應(yīng),實例變量主外,self主內(nèi)”的概括。"canglaoshi"是一個具體的數(shù)據(jù),通過初始化函數(shù)中的name參數(shù),傳給self.name,前面已經(jīng)講過,self也是一個實例,可以為它設(shè)置屬性,self.name就是一個屬性,經(jīng)過初始化函數(shù),這個屬性的值由參數(shù)name傳入,現(xiàn)在就是"canglaoshi"。

在類Person的其它方法中,都是以self為第一個或者唯一一個參數(shù)。注意,在python中,這個參數(shù)要顯明寫上,在類內(nèi)部是不能省略的。這就表示所有方法都承接self實例對象,它的屬性也被帶到每個方法之中。例如在方法里面使用self.name即是調(diào)用前面已經(jīng)確定的實例屬性數(shù)據(jù)。當(dāng)然,在方法中,還可以繼續(xù)為實例self增加屬性,比如self.breast。這樣,通過self實例,就實現(xiàn)了數(shù)據(jù)在類內(nèi)部的流轉(zhuǎn)。

如果要把數(shù)據(jù)從類里面?zhèn)鞯酵饷?,可以通過return語句實現(xiàn)。如上例子中所示的getName方法。

因為實例名稱(girl)和self是對應(yīng)關(guān)系,實際上,在類里面也可以用girl代替self。例如,做如下修改:

#!/usr/bin/env python
# coding=utf-8

__metaclass__ = type

class Person:
    def __init__(self, name):
        self.name = name

    def getName(self):
        #return self.name
        return girl.name    #修改成這個樣子,但是在編程實踐中不要這么做。

girl = Person('canglaoshi')
name = girl.getName()
print name

運行之后,打?。?/p>

canglaoshi

這個例子說明,在實例化之后,實例變量girl和函數(shù)里面的那個self實例是完全對應(yīng)的。但是,提醒讀者,千萬不要用上面的修改了的那個方式。因為那樣寫使類沒有獨立性,這是大忌。

命名空間

命名空間,英文名字:namespaces。在研究類或者面向?qū)ο缶幊讨?,它常常被提到。雖然在《函數(shù)(2)中已經(jīng)對命名空間進(jìn)行了解釋,那時是在函數(shù)的知識范疇中對命名空間的理解。現(xiàn)在,我們在類的知識范疇中理解“類命名空間”——定義類時,所有位于class語句中的代碼都在某個命名空間中執(zhí)行,即類命名空間。

在研習(xí)命名空間以前,請打開在python的交互模式下,輸入:import this,可以看到:

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

這里列位看到的就是所謂《python之禪》,請看最后一句: Namespaces are one honking great idea -- let's do more of those!

這是為了向看官說明Namespaces、命名空間值重要性。

把在《函數(shù)(2)》中已經(jīng)闡述的命名空間用一句比較學(xué)術(shù)化的語言概括:

命名空間是從所定義的命名到對象的映射集合。

不同的命名空間,可以同時存在,當(dāng)彼此相互獨立互不干擾。

命名空間因為對象的不同,也有所區(qū)別,可以分為如下幾種:

  • 內(nèi)置命名空間(Built-in Namespaces):Python運行起來,它們就存在了。內(nèi)置函數(shù)的命名空間都屬于內(nèi)置命名空間,所以,我們可以在任何程序中直接運行它們,比如前面的id(),不需要做什么操作,拿過來就直接使用了。
  • 全局命名空間(Module:Global Namespaces):每個模塊創(chuàng)建它自己所擁有的全局命名空間,不同模塊的全局命名空間彼此獨立,不同模塊中相同名稱的命名空間,也會因為模塊的不同而不相互干擾。
  • 本地命名空間(Function&Class: Local Namespaces):模塊中有函數(shù)或者類,每個函數(shù)或者類所定義的命名空間就是本地命名空間。如果函數(shù)返回了結(jié)果或者拋出異常,則本地命名空間也結(jié)束了。

從網(wǎng)上盜取了一張圖,展示一下上述三種命名空間的關(guān)系

那么程序在查詢上述三種命名空間的時候,就按照從里到外的順序,即:Local Namespaces --> Global Namesspaces --> Built-in Namesspaces

>>> def foo(num,str):
...     name = "qiwsir"
...     print locals()
... 
>>> foo(221,"qiwsir.github.io")
{'num': 221, 'name': 'qiwsir', 'str': 'qiwsir.github.io'}
>>> 

這是一個訪問本地命名空間的方法,用print locals()?完成,從這個結(jié)果中不難看出,所謂的命名空間中的數(shù)據(jù)存儲結(jié)構(gòu)和dictionary是一樣的。

根據(jù)習(xí)慣,看官估計已經(jīng)猜測到了,如果訪問全局命名空間,可以使用?print globals()。

作用域

作用域是指 Python 程序可以直接訪問到的命名空間。“直接訪問”在這里意味著訪問命名空間中的命名時無需加入附加的修飾符。(這句話是從網(wǎng)上抄來的)

程序也是按照搜索命名空間的順序,搜索相應(yīng)空間的能夠訪問到的作用域。

def outer_foo():
    b = 20
    def inner_foo():
        c = 30
a = 10

假如我現(xiàn)在位于inner_foo()函數(shù)內(nèi),那么c對我來講就在本地作用域,而b和a就不是。如果我在inner_foo()內(nèi)再做:b=50,這其實是在本地命名空間內(nèi)新創(chuàng)建了對象,和上一層中的b=20毫不相干??梢钥聪旅娴睦樱?/p>

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

def outer_foo():
    a = 10
    def inner_foo():
        a = 20
        print "inner_foo,a=",a      #a=20

    inner_foo()
    print "outer_foo,a=",a          #a=10

a = 30
outer_foo()
print "a=",a                #a=30

#運行結(jié)果

inner_foo,a= 20
outer_foo,a= 10
a= 30

如果要將某個變量在任何地方都使用,且能夠關(guān)聯(lián),那么在函數(shù)內(nèi)就使用global 聲明,其實就是曾經(jīng)講過的全局變量。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號