Python學習入門教程(29)—類(之四)

(本號正在連續推出以Python官網文檔為主線的系統學習Python的系列文章和視頻,感興趣的朋友們歡迎搜索關注。在這裡學習Python事半功倍!本文及後續文章如無特別聲明均以Windows平臺作為演示平臺,Python版本為:3.8.1)


【本篇繼上篇繼續講解關於"類"的內容】

私有變量

在Python中不存在只能從對象內部訪問的“私有”實例變量。但是,有一個約定是大多數Python代碼都遵循的:以下劃線(例如_spam)為前綴的名稱應該被視為非公共部分(無論是函數、方法還是數據成員)。這類變量應該被視為實現細節,是可以在不通知的情況下進行更改的。

為了避免當前類中定義的名稱與其子類中定義的名稱之間的衝突,Python提供稱為命名重整(name mangling)的機制對此進行了有限的支持。任何形如__spam的標識符(至少有兩個前導下劃線,最多一個尾劃線)都被替換成_classname_ spam的形式,其中classname是去掉前導下劃線的當前類名。標識符只要出現在類定義中不管其所在的位置在哪裡都會被重整。命名重整有助於讓子類重寫方法而不破壞類內方法的調用。例如:

<code>class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)

def update(self, iterable):
for item in iterable:
self.items_list.append(item)

__update = update # private copy of original update() method

class MappingSubclass(Mapping):

def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)/<code>

即使MappingSubclass引入了一個__update標識符,上面的示例也可以工作,因為它分別替換為Mapping中的_mapping__update和MappingSubclass類中的_mappingsubclass__update了。

重整規則的設計主要是為了避免衝突。儘管如此仍然可以訪問或修改被認為是私有的變量。這在特殊情況下很有用,例如在調試器中。

空類的用處

有時需要用到類似於Pascal“record”或C“struct”的數據類型,這樣的數據類型可以將一些命名的數據項捆綁在一起。Python中的空類就能實現這一需求:

<code>class Employee:
pass

john = Employee() # Create an empty employee record

# Fill the fields of the record
john.name = 'John Doe'
john.dept = 'computer lab'
john.salary = 1000/<code>

一段期望特定抽象數據類型的Python代碼通常可以使用一個可以模擬該數據類型的方法的類來代替。例如,如果有一個函數用來格式化來自文件對象的一些數據,那麼可以定義一個具有從字符串緩衝區獲取數據的read()和readline()方法的類,並將其作為參數傳遞給該函數。

實例的方法對象(假設為m)也有屬性:m.self是具有方法m的實例,m.__func__是與此方法對應的函數對象。

具有迭代功能的類

到目前為止,我們見到大多數容器對象是可以使用for循環語句的,如:

<code>for element in [1, 2, 3]:
\t\tprint(element)
for element in (1, 2, 3):
print(element)
for key in {'one':1, 'two':2}:
print(key)
for char in "123":
print(char)
for line in open("myfile.txt"):
print(line, end='')/<code>

這種訪問方式清晰、簡潔、方便,本質上是使用迭代器工作的。在Python中迭代器的使用是遍及和統一的。實際上,for語句調用了容器對象上的iter(),該函數返回一個迭代器對象,該對象定義了一次訪問容器中一個元素的方法_next__()。當沒有更多的元素時,_next__()引發一個StopIteration異常,該異常告訴for終止循環。可以使用next()內置函數調用_next__()方法,下面這個例子展示了它是如何工作的:

<code>>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator>
>>> next(it)
'a'

>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration/<module>/<stdin>/<iterator>/<code>

瞭解了迭代器背後的運行機制之後,很容易將迭代器行為添加到自定義的類中:在類中定義一個_iter__()方法,讓該方法返回一個具有__next__()方法的對象即可。如果類定義了_next__(),那麼_iter__()直接返回self即可:

<code>class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)

def __iter__(self):
return self

def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]/<code>
<code>>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.reverse object="" at="">
>>> for char in rev:
... print(char)
...
m
a
p
s/<code>

生成器

生成器是創建迭代器的簡單而強大的工具。編寫生成器像編寫常規函數一樣,但是在需要返回數據時使用yield語句。每次在其上調用next()時,生成器將從它上次停止的地方恢復執行(生成器記得所有數據值和最後執行的語句)。生成器可以很容易地創建,如下:

<code>def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]/<code>
<code>>>> for char in reverse('golf'):
... print(char)
...
f
l
o
g/<code>

用生成器完成的任何工作也可以用基於類的迭代器完成。生成器之所以如此緊湊,是因為其_iter__()和_next__()方法是自動創建的。

另一個關鍵特性是生成器在相鄰兩次調用之間會自動保存本地變量和當前的執行狀態。這使得該函數比使用self.index和self.data這樣的實例變量更清晰且更容易編寫。

除了自動創建方法和保存程序狀態外,當生成器終止時,它們還會自動引發StopIteration。總之,這些特性使得創建迭代器不費吹灰之力,比編寫一個常規函數容易的多。

發生器表達式

一些簡單的生成器可以簡單地編碼為表達式,使用類似於列表推導式的語法,但使用圓括號而不是方括號。這些表達式是為包含生成器的函數立即使用該生成器的情況而設計的。與完整的生成器定義相比,生成器表達式更緊湊,但通用性差些,與等價的列表理解相比生成器表達式更節省內存。

例子:

<code>>>> sum(i*i for i in range(10))                 # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x*y for x,y in zip(xvec, yvec)) # dot product
260

>>> unique_words = set(word for line in page for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']/<code>


【結束】


分享到:


相關文章: