翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器
原書參考:http://www.jeffknupp.com/blog/2012/10/04/writing-idiomatic-python/
上一篇:翻譯《Writing Idiomatic Python》(四):字典、集合、元組
2.7 類
2.7.1 用isinstance函數檢查一個對象的類型
許多新手在接觸Python之后會產生一種“Python中沒有類型”的錯覺。當然Python的對象是有類型的,并且還會發生類型錯誤。比如,對一個int型對象和一個string型的對象使用+操作就會產生TypeError。如果你在寫一些需要基于某些變量類型來做出相應操作的代碼的話,那么isinstance函數就是你需要的。
isinstance(object, class-or-object-or-tuple)是Python的內建函數,如果第一個object參數和第二個參數,或者其子類型一致,那么返回值為真。如果第二個參數是一個元組的情況,那么當第一個參數的類型是元組中的某一個類型或者其子類型時返回真。需要注意的是盡管在大部分情況下你看到的第二個參數都是內建類型,但是這個函數可以用于任何類型,包括用戶創建的類。
// 原書里寫的就是class-or-object-or-tuple,其實比較容易混淆,寫成class-or-type-or-tuple也許更合適,另外和用type比較的用法比起來,其實兩種方法一般情況并無優劣之分,作者這里有些主觀了,主要的差別是isinstance會把子類也返回真。比如在Py2里一般的Python字符串和unicode字符串,如果用isinstance比較basestring則會返回真,但是如果用type則可以分辨出他們的區別,根據情況需要才能決定是用isinstance還是type
不良風格:
1 def get_size(some_object): 2 """Return the "size" of *some_object*, where size = len(some_object) for 3 sequences, size = some_object for integers and floats, and size = 1 for 4 True, False, or None.""" 5 try: 6 return len(some_object) 7 except TypeError: 8 if some_object in (True, False, type(None)): 9 return 1 10 else: 11 return int(some_object) 12 13 print(get_size('hello')) 14 print(get_size([1, 2, 3, 4, 5])) 15 print(get_size(10.0))地道Python:
1 def get_size(some_object): 2 if isinstance(some_object, (list, dict, str, tuple)): 3 return len(some_object) 4 elif isinstance(some_object, (bool, type(None))): 5 return 1 6 elif isinstance(some_object, (int, float)): 7 return int(some_object) 8 9 print(get_size('hello')) 10 print(get_size([1, 2, 3, 4, 5])) 11 print(get_size(10.0))2.7.2 使用下劃線作為開頭命名的變量和函數表明私有性
在Python的一個類中,無論是變量還是函數,都是共有的。用戶可以自由地在一個類已經定義之后添加新的屬性。除此以外,當繼承一個類的時候,因為這種自由性,用戶還可能無意中改變基類的屬性。最后,雖然所有的變量/屬性都可以被訪問,但是在邏輯上表明哪些變量是是公有的,哪些是私有或是受保護的還是非常有用的。
所以在Python中有一些被廣泛使用的命名上的傳統用來表明一個類作者(關于私有性公有性)的意圖,比如接下來要介紹的兩種用法。對于這兩種用法,雖然普遍認為是慣用法,但是事實上在使用中會使編譯器也產生不同的行為。
第一個,用單下劃線開始命名的表明是受保護的屬性,用戶不應該直接訪問。第二個,用兩個連續地下劃線開頭的屬性,表明是私有的,即使子類都不應該訪問。當然了,這并不能像其他一些語言中那樣真正阻止用戶訪問到這些屬性,但這都是在整個Python社區中被廣泛使用的傳統,從某種角度上來說這也是Python里用一種辦法完成一件事情哲學的體現。
前面曾提到用一個或兩個下劃線命名的方式不僅僅是傳統。一些開發者意識到這種寫法是有實際作用的。以單下劃線開頭的變量在import *時不會被導入。以雙下劃線開頭的變量則會觸發Python中的變量名扎壓(name mangling),比如如果Foo是一個類,那么在Foo中定義的一個名字會被展開成_classname__attributename.
不良風格:
1 class Foo(object): 2 def __init__(self): 3 self.id = 8 4 self.value = self.get_value() 5 6 def get_value(self): 7 pass 8 9 def should_destroy_earth(self): 10 return self.id == 42 11 12 class Baz(Foo): 13 def get_value(self, some_new_parameter): 14 """Since 'get_value' is called from the base class's 15 __init__ method and the base class definition doesn't 16 take a parameter, trying to create a Baz instance will 17 fail 18 """ 19 pass 20 21 class Qux(Foo): 22 """We aren't aware of Foo's internals, and we innocently 23 create an instance attribute named 'id' and set it to 42. 24 This overwrites Foo's id attribute and we inadvertently 25 blow up the earth. 26 """ 27 def __init__(self): 28 super(Qux, self).__init__() 29 self.id = 42 30 # No relation to Foo's id, purely coincidental 31 32 q = Qux() 33 b = Baz() # Raises 'TypeError' 34 q.should_destroy_earth() # returns True 35 q.id == 42 # returns True地道Python:
1 class Foo(object): 2 def __init__(self): 3 """Since 'id' is of vital importance to us, we don't 4 want a derived class accidentally overwriting it. We'll 5 prepend with double underscores to introduce name 6 mangling. 7 """ 8 self.__id = 8 9 self.value = self.__get_value() # Call our 'private copy' 10 11 def get_value(self): 12 pass 13 14 def should_destroy_earth(self): 15 return self.__id == 42 16 17 # Here, we're storing a 'private copy' of get_value, 18 # and assigning it to '__get_value'. Even if a derived 19 # class overrides get_value in a way incompatible with 20 # ours, we're fine 21 __get_value = get_value 22 23 class Baz(Foo): 24 def get_value(self, some_new_parameter): 25 pass 26 27 class Qux(Foo): 28 def __init__(self): 29 """Now when we set 'id' to 42, it's not the same 'id' 30 that 'should_destroy_earth' is concerned with. In fact, 31 if you inspect a Qux object, you'll find it doesn't 32 have an __id attribute. So we can't mistakenly change 33 Foo's __id attribute even if we wanted to. 34 """ 35 self.id = 42 36 # No relation to Foo's id, purely coincidental 37 super(Qux, self).__init__() 38 39 q = Qux() 40 b = Baz() # Works fine now 41 q.should_destroy_earth() # returns False 42 q.id == 42 # returns True2.7.3 使用properties來獲得更好的兼容性
許多時候提供直接訪問類數據的屬性會讓類更方便使用。比如一個Point類,直接使用x和y的屬性回避使用'getter'和'setter'這樣的函數更加好用。然而'getters'和'setters'的存在也并不是沒有原因的:你并不能確定有的時候某個屬性會不會需要(比如在子類中)被某個計算所替代。假設我們有一個Product類,這個類會被產品的名字和價格初始化。我們可以簡單地直接設置產品名稱和價格的成員變量,然而如果我們在稍后的需求中需要自動計算并將產品的稅也加到價格中的話,那么我們就會需要對所有的價格變量進行修改。而避免這樣做的辦法就是將價格設置為一個屬性(property)。
不良風格:
1 class Product(object): 2 def __init__(self, name, price): 3 self.name = name 4 # We could try to apply the tax rate here, but the object's price 5 # may be modified later, which erases the tax 6 self.price = price地道Python:
1 class Product(object): 2 def __init__(self, name, price): 3 self.name = name 4 self._price = price 5 6 @property 7 def price(self): 8 # now if we need to change how price is calculated, we can do it 9 # here (or in the "setter" and __init__) 10 return self._price * TAX_RATE 11 12 @price.setter 13 def price(self, value): 14 # The "setter" function must have the same name as the property 15 self._price = value2.7.4 使用__repr__生成機器可讀的類的表示
在一個類中__str__用來輸出對于人可讀性好的字符串,__repr__用來輸出機器可求值的字符串。Python默認的一個類的__repr__實現沒有任何作用,并且要實現一個對所有Python類都有效的默認的__repr__是很困難的。__repr__需要包含所有的用于重建該對象的信息,并且需要盡可能地能夠區分兩個不同的實例。一個簡單地原則是,如果可能的話,eval(repr(instance))==instance。在進行日志記錄的時候__repr__尤其重要,因為日志中打印的信息基本上來說都是來源于__repr__而不是__str__。
不良風格:
1 class Foo(object): 2 def __init__(self, bar=10, baz=12, cache=None): 3 self.bar = bar 4 self.baz = baz 5 self._cache = cache or {} 6 7 def __str__(self): 8 return 'Bar is {}, Baz is {}'.format(self.bar, self.baz) 9 10 def log_to_console(instance): 11 print(instance) 12 13 log_to_console([Foo(), Foo(cache={'x': 'y'})])地道Python:
1 class Foo(object): 2 def __init__(self, bar=10, baz=12, cache=None): 3 self.bar = bar 4 self.baz = baz 5 self._cache = cache or {} 6 7 def __str__(self): 8 return '{}, {}'.format(self.bar, self.baz) 9 10 def __repr__(self): 11 return 'Foo({}, {}, {})'.format(self.bar, self.baz, self._cache) 12 13 def log_to_console(instance): 14 print(instance) 15 16 log_to_console([Foo(), Foo(cache={'x': 'y'})])2.7.5 使用__str__生成人可讀的類的表示
當定義一個很有可能會被print()用到的類的時候,默認的Python表示就不是那么有用了。定義一個__str__方法可以讓print()函數輸出想要的信息。
不良風格:
1 class Point(object): 2 def __init__(self, x, y): 3 self.x = x 4 self.y = y 5 6 p = Point(1, 2) 7 print(p) 8 9 # Prints '<__main__.Point object at 0x91ebd0>'地道Python:
1 class Point(object): 2 def __init__(self, x, y): 3 self.x = x 4 self.y = y 5 6 def __str__(self): 7 return '{0}, {1}'.format(self.x, self.y) 8 9 p = Point(1, 2) 10 print(p) 11 12 # Prints '1, 2'2.8 上下文管理器
2.8.1 利用上下文管理器確保資源的合理管理
和C++中的RAII(Resource Acquisition Is Initialization,資源獲取就是初始化)原則相似,上下文管理器(和with語句一起使用)可以讓資源的管理更加安全和清楚。一個典型的例子是文件IO操作。
首先來看不良風格的代碼,如果發生了異常,會怎么樣?因為在這個例子中我們并沒有抓住異常,所以發生異常后會向上傳遞,則代碼會在無法關閉已打開文件的情況下退出。
標準庫中有許多的類支持或使用上下文管理器。除此以外,用戶自定義的類也可以通過定義__enter__和__exit__方法來支持上下文管理器。如果是函數,也可以通過contextlib來進行封裝。
不良風格:
1 file_handle = open(path_to_file, 'r') 2 for line in file_handle.readlines(): 3 if raise_exception(line): 4 print('No! An Exception!')地道Python:
1 with open(path_to_file, 'r') as file_handle: 2 for line in file_handle: 3 if raise_exception(line): 4 print('No! An Exception!')2.9 生成器
2.9.1 對于簡單的循環優先使用生成器表達式而不是列表解析
當處理一個序列時,一種很常見的情況是需要每次遍歷一個有微小改動的版本的序列。比如,需要打印出所有用戶的名字的大寫形式。
第一反應當然是用一個即時的表達式實現這種遍歷,自然而然地就容易想到列表解析,然而在Python中事實上有更好的內建實現方式:生成器表達式。
那么這兩種方式的主要區別在哪里呢?列表解析會產生一個列表對象并且立即產生列表里所有的元素。對于一些大的列表,這通常會帶來昂貴的甚至是不可接受的開銷。而生成器則返回一個生成器表達式,只有在調用的時候,才產生元素。對于上面提到的例子,也許列表解析還是可以接受的,但是如果我們要打印的不再是大寫的名字而是國會圖書館里所有圖書的名字的話,產生這個列表可能就已經導致內存溢出了,而生成器表達式則不會這樣。
不良風格:
1 for uppercase_name in [name.upper() for name in get_all_usernames()]: 2 process_normalized_username(uppercase_name)地道Python:
1 for uppercase_name in (name.upper() for name in get_all_usernames()): 2 process_normalized_username(uppercase_name)2.9.2 使用生成器延遲加載無限的序列
很多情況下,為一個無限長的序列提供一種方式來遍歷是非常有用的。否則你會需要提供一個異常昂貴開銷的接口來實現,而用戶還需要為此等待很長的時間用于生成進行遍歷的列表。
面臨這些情況,生成器就是理想的選擇當元組作為某,來看下面的例子:
不良風格:
1 def get_twitter_stream_for_keyword(keyword): 2 """Get's the 'live stream', but only at the moment 3 the function is initially called. To get more entries, 4 the client code needs to keep calling 5 'get_twitter_livestream_for_user'. Not ideal. 6 """ 7 8 imaginary_twitter_api = ImaginaryTwitterAPI() 9 if imaginary_twitter_api.can_get_stream_data(keyword): 10 return imaginary_twitter_api.get_stream(keyword) 11 12 current_stream = get_twitter_stream_for_keyword('#jeffknupp') 13 for tweet in current_stream: 14 process_tweet(tweet) 15 16 # Uh, I want to keep showing tweets until the program is quit. 17 # What do I do now? Just keep calling 18 # get_twitter_stream_for_keyword? That seems stupid. 19 20 def get_list_of_incredibly_complex_calculation_results(data): 21 return [first_incredibly_long_calculation(data), 22 second_incredibly_long_calculation(data), 23 third_incredibly_long_calculation(data), 24 ]地道Python:
?
1 def get_twitter_stream_for_keyword(keyword): 2 """Now, 'get_twitter_stream_for_keyword' is a generator 3 and will continue to generate Iterable pieces of data 4 one at a time until 'can_get_stream_data(user)' is 5 False (which may be never). 6 """ 7 8 imaginary_twitter_api = ImaginaryTwitterAPI() 9 while imaginary_twitter_api.can_get_stream_data(keyword): 10 yield imaginary_twitter_api.get_stream(keyword) 11 12 # Because it's a generator, I can sit in this loop until 13 # the client wants to break out 14 for tweet in get_twitter_stream_for_keyword('#jeffknupp'): 15 if got_stop_signal: 16 break 17 process_tweet(tweet) 18 19 def get_list_of_incredibly_complex_calculation_results(data): 20 """A simple example to be sure, but now when the client 21 code iterates over the call to 22 'get_list_of_incredibly_complex_calculation_results', 23 we only do as much work as necessary to generate the 24 current item. 25 """ 26 27 yield first_incredibly_long_calculation(data) 28 yield second_incredibly_long_calculation(data) 29 yield third_incredibly_long_calculation(data)?
轉載請注明出處:達聞西@博客園上一篇:翻譯《Writing Idiomatic Python》(四):字典、集合、元組
轉載于:https://www.cnblogs.com/frombeijingwithlove/p/4470920.html
總結
以上是生活随笔為你收集整理的翻译《Writing Idiomatic Python》(五):类、上下文管理器、生成器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CUDA ---- device管理
- 下一篇: jQuery学习笔记1--表格展开关系