Python 作用域和命名空間
類的定義非常巧妙的運用了命名空間,要完全了解接下來的知識,需要先了解作用域和命名空間的工作原理。另外,這一切的知識對于任何進階 Python 程式員都非常有用。
命名空間 是從命名到對象的映射。
目前命名空間主要是通過 Python 字典實作的,不過通常不關心具體的實作方式(除非出于性能考慮),以後也有可能會改變其實作方式。
以下有一些命名空間的例子:内置命名(像 abs() 這樣的函數,以及内置異常名)集,子產品中的全局命名,函數調用中的局部命名。某種意義上講對象的屬性集也是一個命名空間。關于命名空間需要了解的一件很重要的事就是不同命名空間中的命名沒有任何聯系,例如兩個不同的子產品可能都會定義一個名為 maximize 的函數而不會發生混淆-使用者必須以子產品名為字首來引用它們。
稱 Python 中任何一個“
.
”之後的命名為 屬性
例如,表達式
z.real
中的
real
是對象
z
的一個屬性。
嚴格來講,從子產品中引用命名是引用屬性:
表達式
modname.funcname
中,
modname
是一個子產品對象,
funcname
是它的一個屬性。
是以,子產品的屬性和子產品中的全局命名有直接的映射關系:它們共享同一命名空間!
屬性可以是隻讀過或寫的。後一種情況下,可以對屬性指派。你可以這樣:
modname.the_answer = 42
。可寫的屬性也可以用
del 語句
删除。
例如:
del modname.the_answer
會從
modname 對象
中删除
the_answer 屬性
。
不同的命名空間在不同的時刻建立,有不同的生存期。包含内置命名的命名空間在 Python 解釋器啟動時建立,會一直保留,不被删除。子產品的全局命名空間在子產品定義被讀入時建立,通常,子產品命名空間也會一直儲存到解釋器退出。由解釋器在最高層調用執行的語句,不管它是從腳本檔案中讀入還是來自互動式輸入,都是
__main__
子產品的一部分,是以它們也擁有自己的命名空間(内置命名也同樣被包含在一個子產品中,它被稱作
builtins
)。
當調用函數時,就會為它建立一個
局部命名空間
,并且在函數傳回或抛出一個并沒有在函數内部處理的異常時被删除。(實際上,用遺忘來形容到底發生了什麼更為貼切。)當然,每個遞歸調用都有自己的局部命名空間。
作用域
就是一個 Python 程式可以直接通路命名空間的正文區域。這裡的直接通路意思是一個對名稱的錯誤引用會嘗試在命名空間内查找。盡管作用域是靜态定義,在使用時他們都是動态的。每次執行時,至少有三個命名空間可以直接通路的作用域嵌套在一起:
- 包含局部命名的使用域在最裡面,首先被搜尋;其次搜尋的是中層的作用域,這裡包含了同級的函數;
- 最後搜尋最外面的作用域,它包含内置命名。
- 首先搜尋最内層的作用域,它包含局部命名任意函數包含的作用域,是内層嵌套作用域搜尋起點,包含非局部,但是也非全局的命名
- 接下來的作用域包含目前子產品的全局命名
- 最外層的作用域(最後搜尋)是包含内置命名的命名空間
以下是一個示例,示範了如何引用不同作用域和命名空間,以及 global 和 nonlocal 如何影響變量綁定:
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
spam = "test spam"
do_local()
print("After local assignment:", spam)
do_nonlocal()
print("After nonlocal assignment:", spam)
do_global()
print("After global assignment:", spam)
scope_test()
print("In global scope:", spam)
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
local 指派語句是無法改變 scope_test 的 spam 綁定。nonlocal 指派語句改變了 scope_test 的 spam 綁定,并且 global 指派語句從子產品級改變了 spam 綁定。
類
名詞 | 描述 |
---|---|
類(Class) | 用來描述具有相同的屬性和方法的對象的集合。它定義了該集合中每個對象所共有的屬性和方法。對象是類的執行個體。 |
類變量 | 類變量在整個執行個體化的對象中是公用的。類變量定義在類中且在函數體之外。類變量通常不作為執行個體變量使用。 |
資料成員 | 類變量或者執行個體變量用于處理類及其執行個體對象的相關的資料。 |
方法重寫 | 如果從父類繼承的方法不能滿足子類的需求,可以對其進行改寫,這個過程叫方法的覆寫(override),也稱為方法的重寫。 |
執行個體變量 | 定義在方法中的變量,隻作用于目前執行個體的類。 |
繼承 | 即一個派生類(derived class)繼承基類(base class)的字段和方法。繼承也允許把一個派生類的對象作為一個基類對象對待。例如,有這樣一個設計:一個Dog類型的對象派生自Animal類,這是模拟"是一個(is-a)"關系(例圖,Dog是一個Animal)。 |
執行個體化 | 建立一個類的執行個體,類的具體對象。 |
方法 | 類中定義的函數。 |
對象 | 通過類定義的資料結構執行個體。對象包括兩個資料成員(類變量和執行個體變量)和方法(執行個體方法,類方法,靜态方法) |
空行
函數之間或類的方法之間用空行分隔,表示一段新的代碼的開始。類和函數入口之間也用一行空行分隔,以突出函數入口的開始。
空行與代碼縮進不同,空行并不是Python文法的一部分。書寫時不插入空行,Python解釋器運作也不會出錯。但是空行的作用在于分隔兩段不同功能或含義的代碼,便于日後代碼的維護或重構。
記住:空行也是程式代碼的一部分。
和其它程式設計語言相比,Python 在盡可能不增加新的文法和語義的情況下加入了類機制。
Python中的類提供了面向對象程式設計的所有基本功能:類的繼承機制允許多個基類,派生類可以覆寫基類中的任何方法,方法中可以調用基類中的同名方法。
對象可以包含任意數量和類型的資料。
類的格式:
class ClassName(SuperClass1,SuperClass2,...):
'''
optional documentation string
'''
Class_suite
一些說明
- 資料屬性會覆寫同名的方法屬性。為了避免意外的名稱沖突,這在大型程式中是極難發現的 Bug,使用一些約定來減少沖突的機會是明智的。
- 可能的約定包括:大寫方法名稱的首字母,使用一個唯一的小字元串(也許隻是一個下劃線)作為資料屬性名稱的字首,或者方法使用動詞而資料屬性使用名詞。
-
資料屬性可以被方法引用,也可以由一個對象的普通使用者(客戶)使用。換句話說,類不能用來實作純淨的資料類型。
事實上,Python 中不可能強制隐藏資料——一切基于約定(如果需要,使用 C 編寫的 Python 實作可以完全隐藏實作細節并控制對象的通路。這可以用來通過 C 語言擴充 Python)。
-
客戶應該謹慎的使用資料屬性——客戶可能通過踐踏他們的資料屬性而使那些由方法維護的常量變得混亂。
注意:隻要能避免沖突,客戶可以向一個執行個體對象添加他們自己的資料屬性,而不會影響方法的正确性——再次強調,命名約定可以避免很多麻煩。
- 從方法内部引用資料屬性(或其他方法)并沒有快捷方式。這實際上增加了方法的可讀性:當浏覽一個方法時,在局部變量和執行個體變量之間不會出現令人費解的情況。
-
一般,方法的第一個參數被命名為 self。
這僅僅是一個約定:對 Python 而言,名稱
絕對沒有任何特殊含義。(但是請注意:如果不遵循這個約定,對其他的 Python 程式員而言你的代碼可讀性就會變差,而且有些 類檢視器 程式也可能是遵循此約定編寫的。)self
-
類屬性的任何函數對象都為那個類的執行個體定義了一個方法。函數定義代碼不一定非得定義在類中:也可以将一個函數對象指派給類中的一個局部變量。
例如:
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C(object):
f = f1
def g(self):
return 'hello world'
h = g
現在 f,g和h都是類C的屬性,引用的都是函數對象,是以它們都是 C 執行個體的方法-- h 嚴格等于g。
通過 self 參數的方法屬性,方法可以調用其它的方法:
類可以看作是一種把對象分組歸類的方法。
舊式類的定義(即将被淘汰)
新式類與舊式類的差別主要表現在對超類命名空間檢索的使用算法不同。
舊式類可以直接省略括号和超類清單,而新式類以object作為Python中所有類的預設超類。
# 定義Ball1球類
class Ball1:
def bounce(self): # self可以看作是類本身的一個執行個體的引用。
if self.direction=='down':
self.direction='up'
# 建立一個Ball1球類的執行個體myBall1
myBall1=Ball1()
# 定義myBall1的屬性方法之一
myBall1.direction='down'
myBall1.color='red'
myBall1.size='small'
# 調用bounce()方法
myBall1.bounce()
# 定義屬性的方法之二:初始化對象
class Ball2:
def __init__(self,color,size,direction):
self.color=color
self.size=size
self.direction=direction
def bounce(self):
if self.direction=='down':
self.direction='up'
myBall2=Ball2('red','small','down')
myBall2.bounce()
class Ball3:
def __init__(self,color,size,direction):
self.color=color
self.size=size
self.direction=direction
def __str__(self):
msg='Hi,I\'m a '+self.size+' '+self.color+' '+'ball!'
return msg
myBall=Ball3('red','small','down')
print(myBall)
Hi,I'm a small red ball!
# 修改屬性
myBall.color='black'
myBall.color
'black'
myBall=Ball3('red','big','down')
myBall.size
'big'
print(myBall)
Hi,I'm a big red ball!
類對象
類對象支援兩種操作:屬性引用和執行個體化。
- 屬性引用使用和 Python 中所有的屬性引用一樣的标準文法:
obj.name
- 類對象建立後,類命名空間中所有的命名都是有效屬性名。
示例:
類執行個體化後,可以使用其屬性,實際上,建立一個類之後,可以通過類名通路其屬性。
class MyClass:
"""一個簡單的類執行個體"""
i = 12345
def f(self):
return 'hello world'
# 執行個體化類
x = MyClass()
# 通路類的屬性和方法
print("MyClass 類的屬性 i 為:", x.i)
print("MyClass 類的方法 f 輸出為:", x.f())
MyClass 類的屬性 i 為: 12345
MyClass 類的方法 f 輸出為: hello world
很多類都傾向于将對象建立為有初始狀态的。是以類可能會定義一個名為
__init__()
的特殊方法(構造方法),像下面這樣:
def __init__(self):
self.data = []
類定義了
__init__()
方法的話,類的執行個體化操作會自動調用
__init__()
方法。是以在下例中,可以這樣建立一個新的執行個體:
x = MyClass()
當然,
__init__()
方法可以有參數,參數通過
__init__()
傳遞到類的執行個體化操作上。例如:
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i)
3.0 -4.5
類的方法
在類地内部,使用
def
關鍵字來定義一個方法,與一般函數定義不同,類方法必須包含參數 self, 且為第一個參數,self 代表的是類的執行個體。
self代表類的執行個體,而非類
類的方法與普通的函數隻有一個特别的差別——它們必須有一個額外的第一個參數名稱, 按照慣例它的名稱是self。
class Test:
def prt(self):
print(self)
print(self.__class__)
t = Test()
t.prt()
<__main__.Test object at 0x0000015BDD989630>
<class '__main__.Test'>
從執行結果可以很明顯的看出,self 代表的是類的執行個體,代表目前對象的位址,而 self.class 則指向類。
self 不是 python 關鍵字,我們把他換成 runoob 也是可以正常執行的:
class Test:
def prt(runoob):
print(runoob)
print(runoob.__class__)
t = Test()
t.prt()
<__main__.Test object at 0x000001BD61AB2668>
<class '__main__.Test'>
#類定義
class people:
#定義基本屬性
name = ''
age = 0
#定義私有屬性,私有屬性在類外部無法直接進行通路
__weight = 0
#定義構造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 說: 我 %d 歲。" %(self.name,self.age))
# 執行個體化類
p = people('runoob',10,30)
p.speak()
runoob 說: 我 10 歲。
type(p)
__main__.people
# 新式類
類的成員:
- 成員變量:
- 成員方法:
- 執行個體方法
- 類方法
- 靜态方法
- property(修飾器)
建立執行個體
class Bird(object): # object類是Python中所有類的預設超類。
pass # pass不做任何事情,一般用做占位語句,以保持程式結構的完整性。
# b1,b2,b3均是綁定了Bird類執行個體對象的引用。
b1=Bird()
b2=Bird()
b3=Bird()
a=[b1,b2,b3]
type(a[0])
__main__.Bird
執行個體成員方法 & 執行個體變量
# 執行個體方法:setCoordinate,getCoordinate,move,pr
# 執行個體成員變量(屬性變量):self(對類的執行個體自身的引用),x,y
class Bird(object):
def setCoordinate(self,x,y):
self.x,self.y=x,y
def getCoordinate(self):
print(self.x,self.y)
def move(self,dx,dy):
self.x += dx
self.y += dy
def pr(self):
print(self)
print(self.__class__)
print(self.x.__class__)
print(self.getCoordinate().__class__)
# 建立執行個體
bird=Bird()
# 調用執行個體方法
bird.setCoordinate(1,1)
bird.getCoordinate()
1 1
bird.pr()
<__main__.Bird object at 0x000001BD6197C5F8>
<class '__main__.Bird'>
<class 'int'>
1 1
<class 'NoneType'>
bird.move(1,2)
bird.getCoordinate()
2 3
直接通路執行個體變量
bird.x
2
bird.x,bird.y=3,3
print(bird.x,bird.y)
3 3
bird.x+=2
bird.y+=1
bird.x,bird.y
(5, 4)
在對象的屬性或方法前加上雙下劃線(__)可使之變為私有
以下均以例子說明:
私有成員變量
class Bird(object):
def setCoordinate(self,x,y):
self.__x,self.__y=x,y
def getCoordinate(self):
print(self.__x,self.__y)
bird=Bird()
bird.setCoordinate(1,1)
bird.getCoordinate()
1 1
bird.__x # 在類外直接通路私有變量,python解釋器會抛出一個異常。
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-102-373560fcf103> in <module>()
----> 1 bird.__x # 在類外直接通路私有變量,python解釋器會抛出一個異常。
AttributeError: 'Bird' object has no attribute '__x'
Python的私有并不是絕對的。
Python解釋器對于類中所有加了雙下劃線的成員名會做一些名字上的替換,若存在一個成員的名字為
__fooName
,則解釋器會在檢測到它存在後将其替換為
_ClassName__fooName
,即在成員名前加上單下劃線開頭的類名。
bird._Bird__x
1
類屬性與方法的所有
- 類的私有屬性
:兩個下劃線開頭,聲明該屬性為私有,不能在類地外部被使用或直接通路。在類内部的方法中使用時__private_attrs
self.__private_attrs
-
在類地内部,使用 def 關鍵字來定義一個方法,與一般函數定義不同,類方法必須包含參數 self,且為第一個參數,self 代表的是類的執行個體。
self 的名字并不是規定死的,也可以使用 this,但是最好還是按照約定是用 self。
- 類的私有方法
:兩個下劃線開頭,聲明該方法為私有方法,隻能在類的内部調用 ,不能在類地外部調用。__private_method
self.__private_methods
class JustCounter(object):
__secretCount = 0 # 私有變量
publicCount = 0 # 公開變量
def count(self):
self.__secretCount += 1
self.publicCount += 1
print (self.__secretCount)
counter = JustCounter()
counter.count()
counter.count()
print (counter.publicCount)
print (counter.__secretCount) # 報錯,執行個體不能通路私有變量
1
2
2
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-104-9a9ab8650dbe> in <module>()
12 counter.count()
13 print (counter.publicCount)
---> 14 print (counter.__secretCount) # 報錯,執行個體不能通路私有變量
AttributeError: 'JustCounter' object has no attribute '__secretCount'
class Site(object):
def __init__(self, name, url):
self.name = name # public
self.__url = url # private
def who(self):
print('name : ', self.name)
print('url : ', self.__url)
def __foo(self): # 私有方法
print('這是私有方法')
def foo(self): # 公共方法
print('這是公共方法')
self.__foo()
x = Site('菜鳥教程', 'www.runoob.com')
x.who() # 正常輸出
x.foo() # 正常輸出
x.__foo() # 報錯
name : 菜鳥教程
url : www.runoob.com
這是公共方法
這是私有方法
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-105-211d0443cb75> in <module>()
18 x.who() # 正常輸出
19 x.foo() # 正常輸出
---> 20 x.__foo() # 報錯
AttributeError: 'Site' object has no attribute '__foo'
類的專有方法:
-
: 構造函數,在生成對象時調用__init__
-
: 析構函數,釋放對象時使用__del__
-
: 列印,轉換__repr__
-
: 按照索引指派__setitem__
-
: 按照索引擷取值__getitem__
-
: 獲得長度__len__
-
: 比較運算__cmp__
-
: 函數調用__call__
-
: 加運算__add__
-
: 減運算__sub__
-
: 乘運算__mul__
-
: 除運算__div__
-
: 求餘運算__mod__
-
: 乘方__pow__
類變量 & 類方法
# 實作一個Bird類,包括兩個類成員變量(name_zh,name_en)和兩個類成員方法(getName,setName):
class Bird(object):
name_zh=u'鳥類'
name_en=u'bird'
@classmethod
def getName(cls,type):
if type=='zh':
return cls.name_zh
else:
return cls.name_en
@classmethod
def setName(cls,type,new_name):
if type=='zh':
cls.name_zh=new_name
else:
cls.name_en=new_name
# 調用和通路類方法和類變量
Bird.getName('zh')
'鳥類'
Bird.name_en
'bird'
Bird.setName('en','birdbird')
Bird.getName('en')
'birdbird'
Bird.name_zh='新鳥類'
Bird.name_zh
'新鳥類'
類變量和類方法可以被執行個體成員方法以隻讀的方式通路。
class Bird(object):
name=u'bird'
@classmethod
def getName(cls):
return cls.name
def getName2(self):
print('directly:',self.name)
print('by call classmethod:',self.getName())
bird=Bird()
bird.getName2()
directly: bird
by call classmethod: bird
bird.name
'bird'
bird.getName()
'bird'
一個小'bug'
class Bird(object):
name=u'bird'
def setName(self,new_name):
self.name=new_name
bird=Bird()
bird.name
'bird'
Bird.name
'bird'
bird.setName('birdbird') # 非讀操作,且bird命名空間中沒有name變量,故需要建立一個叫name的執行個體變量。
bird.name
'birdbird'
Bird.name #Bird通路的依然是類變量的值。
'bird'
靜态方法位于類命名空間中的一種特殊函數,無法對任何執行個體成員進行操作。
實作封裝
class Math(object):
@staticmethod
def pow(x,y):
pass
@staticmethod
def sqrt(x):
pass
@staticmethod
def log(x):
pass
調用
可以通路類變量和類方法,但是,類的執行個體對象無法調用靜态方法。
class Bird(object):
name=u'bird'
@staticmethod
def setName(new_name):
Bird.name=new_name
Bird.name
'bird'
Bird.setName('birdbird')
Bird.name
'birdbird'
property 修飾器
目的:
修飾執行個體方法,以改變對外的通路方式,使得其在外部被通路時更像是在通路一個變量,而非函數。
class Bird(object):
def __init__(self,name):
self.__name=name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name=name
@name.deleter
def name(self):
del self.__name
bird=Bird('bird')
bird.name
'bird'
bird.name='birdbird'
bird.name
'birdbird'
del bird.name
bird.name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-136-3d2d6764ec0c> in <module>()
----> 1 bird.name
<ipython-input-131-7072a73d10cd> in name(self)
4 @property
5 def name(self):
----> 6 return self.__name
7 @name.setter
8 def name(self,name):
AttributeError: 'Bird' object has no attribute '_Bird__name'
從上述結果可以看出,Bird類的名叫
name
的對象完全成為了
__name
的替身。有了
name
作為通路的中間層,一些驗證的邏輯就可以在
name
的方法中實作了。
類的初始化
__init__
關鍵字是類的構造方法。
在Python中,為一個類建立一個 執行個體對象 時,會立即調用類的構造方法(即當對象被建立後,Python解釋器會去類中查找
__init__
函數)。
__init__
函數為類添加一些初始化的屬性或功能。
class Bird(object):
def __init__(self,name=None):
if name:
self.name=name
class Bird(object):
def __init__(self,*args,**kwargs):
if kwargs.get('name',None):
self.name=kwargs['name']
if kwargs.get('weight',None):
self.weight=kwargs['weight']
bird_1=Bird(name='bird_1',weight=1)
bird_1.name
'bird_1'
bird_1.weight
1
類的繼承:
- 函數重寫
- 多重繼承
Python 同樣支援類的繼承,如果一種語言不支援繼承,類就沒有什麼意義。派生類的定義如下所示:
class DerivedClassName(BaseClassName1):
<statement-1>
.
.
.
<statement-N>
需要注意圓括号中基類的順序,若是基類中有相同的方法名,而在子類使用時未指定,python從左至右搜尋 即方法在子類中未找到時,從左到右查找基類中是否包含方法。
BaseClassName(示例中的基類名)必須與派生類定義在一個作用域内。除了類,還可以用表達式,基類定義在另一個子產品中時這一點非常有用:
class DerivedClassName(modname.BaseClassName):
#類定義
class people(object):
#定義基本屬性
name = ''
age = 0
#定義私有屬性,私有屬性在類外部無法直接進行通路
__weight = 0
#定義構造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 說: 我 %d 歲。" %(self.name,self.age))
#單繼承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#調用父類的構函
people.__init__(self,n,a,w)
self.grade = g
#覆寫父類的方法
def speak(self):
print("%s 說: 我 %d 歲了,我在讀 %d 年級"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak()
ken 說: 我 10 歲了,我在讀 3 年級
如果你的父類方法的功能不能滿足你的需求,你可以在子類重寫你父類的方法,執行個體如下:
class Parent(object): # 定義父類
def myMethod(self):
print ('調用父類方法')
class Child(Parent): # 定義子類
def myMethod(self):
print ('調用子類方法')
c = Child() # 子類執行個體
c.myMethod() # 子類調用重寫方法
調用子類方法
例子
class Animal(object):
def __init__(self,*args,**kwargs):
pass
def eat(self):
print('Animal is eating')
def reproduce(self):
print('Animal is reproducing')
class Bird(Animal):
def __init__(self,*args,**kwargs):
Animal.__init__(self,*args,**kwargs)
def fly(self):
print('Bird is flying')
def reproduce(self): # 方法的重寫
print('Bird is reproducing')
class Dog(Animal):
def __init__(self,*args,**kwargs):
Animal.__init__(self,*args,**kwargs)
def run(self):
print('Dog is running')
def eat(self):
print('Bird is eating')
# 多重繼承
class Sound(object):
def sound(self,voice):
print(voice)
class Movement(object):
def move(self,target_x,target_y):
self.x,self.y=target_x,target_y
class Bird(Sound,Movement):
pass
class Car(Sound,Movement):
pass
多态
動态類型:一個引用可以綁定任意類型的對象。
運算符重載
Python同樣支援運算符重載,我麼可以對類的專有方法進行重載,執行個體如下:
class Vector(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)
Vector (7, 8)
探尋有趣之事!