分享書籍[writing idiomatic python ebook] 二

海鳥發表於2014-03-28

 對多個變數設定相同的值時,用連等號一起賦值

x = 10
y = 10
z = 10

改成:

x = y = z = 10

 

交換變數值時,可以避免定義新的臨時變數

x = 10
y = 5

temp = x
x = y
y = temp

改成:

x = 10
y = 5

x, y = y, x

 

多次呼叫字元物件方法時,可以用鏈式呼叫方法,避免中間產生過多變數  

str1 = 'i am a bug!'
str2 = str1.strip()
str3 = str2.upper()
str4 = str3.replace('!', '?')

改成:

str4 = str1.strip().upper().replace('!', '?')

當然,如果連續呼叫鏈過長,也會使程式碼不清楚,作者給的建議是最好不要超過三次呼叫

 

拼接字元列表時,用join方法去實現

mylist = ['i', 'am', 'a', 'bug']
resultStr = ''
for e in mylist:
    resultStr += e

改成:

mylist = ['i', 'am', 'a', 'bug']
resultStr = ''.join(mylist)

  

格式化字元時多使用format函式

我們格式化字元時一般會用下面兩種方式:

name = "tony"
age = 100
str = "myname : " + name + " my age : " + str(age)

str1 = "myname : %s my age : %d" % (name, age)

改成:

str2 = "myname : {} my age {}".format(name, age)

 

對list物件進行相關操作並生成新的物件時多使用comprehension(這個怎麼翻譯?)

mylist = range(20)

odd_list = []
for e in mylist:
    if e % 2 == 1:
        odd_list.append(e)

改成:

mylist = range(20)
odd_list = [e for e in mylist if e % 2 == 1]

這種寫法效能更好

 

有時候使用負列表索引更方便

mylist = range(20)

len_mylist = len(mylist)
last_five_list = mylist[len_mylist - 5:]

改成:

last_five_list = mylist[-5:]

 

判斷一個列表裡的元素是否都為True時,可以對列表進行all操作

def contains_zero(itor):
    for e in itor:
        if e:
            return False
    return True

改成:

def contains_zero(itor):
    return all(itor)

all只有在列表中的每個元素都返回True時才會返回True

 

區別xrange和range

range會在記憶體中生成完整的列表例項, 而xrange則只是生成一個迭代器;

如下我只想列印一個集合中第一個偶數

for index in range(3, 1000000000):
    if index % 2 == 0:
        print index
        break

在我機器上會出現如下錯誤,就是因為range會在記憶體中生成完整物件例項:

Traceback (most recent call last):
  File "C:\Users\tony\Desktop\huawei.py", line 3, in <module>
    for index in range(3, 1000000000):
MemoryError
[Finished in 0.2s with exit code 1]

改成如下就正常了:

for index in xrange(3, 1000000000):
    if index % 2 == 0:
        print index
        break

 

用dict物件完成switch...case...的功能

在python裡沒有switch...case...功能。但是dict可以編寫出更直觀簡潔的程式碼出來。如下,模擬一個計算器的功能,根據傳入的操作符和運算元來算出結果:

def apply_operation(left_operand, right_operand, operator):
    if operator == '+':
        return left_operand + right_operand
    elif operator == '-':
        return left_operand - right_operand
    elif operator == '*':
        return left_operand * right_operand
    elif operator == '/':
        return left_operand / right_operand

改成:

def apply_operation(left_operand, right_operand, operator):
    import operator as op
    operator_mapper = {'+': op.add, '-': op.sub, '*': op.mul, '/': op.truediv}
    return operator_mapper[operator](left_operand, right_operand)

 

使用dict.get方法可以提供一個預設值

我們在獲取dict的某個元素時,因為不確定該鍵是否存在,所以一般是先檢查,再獲取。如下:

mydict = {'a': 1}

default_b = 2
if 'b' in mydict:
    default_b = mydict['b']

print default_b

改成:

print mydict.get('b', 2)

 

大家或許只知道list的comprehension,其實dict也有comprehension

user_list = [{'name': 'lucy', 'email': 'lucy@g.com'}, {'name': 'lily', 'email': 'lily@g.com'}]

user_email = {}

for user in user_list:
    if 'email' in user:
        user_email[user['name']] = user['email']

改成:

{user['name']: user['email'] for user in user_list if 'email' in user}

    

利用set的集合運算功能

在很多場景中,我們經常要從兩個集合(列表)中找出相同的,相異的,或者相互排除的元素列表,這個時候我們可以利用set本身支援的各種集合運算功能。就像數學中講的集合的:相交,相併,異合等等。

如下我們要計算出滿足兩個特殊的相交集:

def get_both_popular_and_active_users():
    most_popular_users = get_list_of_most_popular_users()
    most_active_users = get_list_of_most_active_users()
    popular_and_active_users = []
    for user in most_active_users:
        if user in most_popular_users:
            popular_and_active_users.append(user)

改成:

def get_both_popular_and_active_users():
    return(set(get_list_of_most_active_users()) & set(get_list_of_most_popular_users()))

set支援的運算有: A & B, A | B, A ^ B 。還有一點要注意的,set在計算的時候如何判定兩個元素相等的呢,除了要在類中定義的__eq__方法返回值相同外,還要定義 __hash__ 值相同。這點和java中的HashMap的判定行為(equals, hashCode)差不多

 

set的comprehension

到這裡我們可以比較下list, dict, set 三種資料結構的comprehension的不同表述。list用[...], dict用{key:value...}, 而set用{...}

users_first_names = set()
for user in users:
    users_first_names.add(user.first_name)

改成:

users_first_names = {user.first_name for user in users}

 

訪問tuple的資料項時,可以用namedtuple代替index的方式訪問

rows = [('lily', 20, 2000), ('lucy', 19, 2500)]

for row in rows:
    print '{}`age is {}, salary is {} '.format(row[0], row[1], row[2])

改成:

Employee = namedtuple('Employee', 'name, age, salary')

for row in rows:
    employee = Employee._make(row)
    print '{}`age is {}, salary is {} '.format(employee.name, employee.age, employee.salary)

namedtuple方法會成一個tuple的子類,並通過類方法_make可以把一個tuple例項轉換成一個可以通過name來索引的物件。這比通過index的方式更直觀

 

作用isinstance來判斷物件的型別

因為在python中定義變數時,不用像其它靜態語言,如java, 要指定其變數資料型別,如int = 4. 但是這並不意味在python中沒有資料型別,只是一個變數的資料型別是在執行的時候根據具體的賦值才最終確定。比如下面的程式碼是計算一個物件的長度值,如果是序列型別(str,list,set,dict)的, 直接呼叫len方法,如果是True, False, None則返回1,如果是數值的,則返回其int值.

def get_size(some_object):
    try:
        return len(some_object)
    except TypeError:
        if some_object in (True, False, None):
        return 1
    else:
        return int(some_object)

print(get_size('hello'))
print(get_size([1, 2, 3, 4, 5]))
print(get_size(10.0))

改成:

def get_size(some_object):
    if isinstance(some_object, (list, dict, str, tuple)):
        return len(some_object)
    elif isinstance(some_object, (bool, type(None))):
        return 1
    elif isinstance(some_object, (int, float)):
        return int(some_object)

這裡有一點要注意,在判斷物件是否是None時,用了type(None)。因為沒有直接書面的形式表達None的型別。

 

在類中定義私有屬性(private attribute)時,儘量用下劃線開頭的名稱規範,如__name, _name

在python在,預設情況下所有的類屬性,方法函式都預設是公共的(public),即外部物件可以訪問。然後在有些情況下,我們需要約定一些成員屬性或者方法只是內部使用,不能直接被外部修改。在python中達到這些的目的一般是通過“約定規範”來完成。比如某一個類屬性以下劃線(_)開頭,則是顯式的的約定該成員是具有私有屬性的,最好不要在外部直接呼叫。當然這樣的約定並不能阻止你這樣做,但是良好素質的程式設計師應該都會遵守這個規定。而雙下劃線(__)開頭的成員更具有“私有”的屬性,因為在外部不能直接呼叫,因為python解譯器把這些的成員名稱進行變形處理,使外部不能輕易直接訪問。來看下面一段程式碼:

class A(object):
    __i = 1

    def __init__(self):
        self._j = 2

    def printJ(self):
        return self._j
      
class B(A):
    def __init__(self):
        super(B, self).__init__()
        self._j = 3

b = B()
print b.printJ() 

# 列印如下
3
[Finished in 0.3s]

我們發現類B的例項b呼叫printJ方法時輸出3, 這是因為B的構造器中重寫了self._j的賦值。假始我們現在就是希望printJ方法列印父類A的構造器中的self_j值,即列印2,修改程式碼如下:

class A(object):
    __i = 1

    def __init__(self):
        self.__j = 2

    def printJ(self):
        return self.__j
      
class B(A):
    def __init__(self):
        super(B, self).__init__()
        self.__j = 3

b = B()
print b.printJ()

用__j 代替 _j, 這樣B中就不能訪問父類A中的私有變數__j,所以現在不是重寫,而是重新定義了一個成員屬性。可以通過下面的語句可以看出來確實是兩個成員屬性:

print b._A__j, b._B__j
#列印
2 3
[Finished in 0.3s]

 

 在class中定義__str__方法,這些在列印物件字元時更直觀,更好閱讀些

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(10, 11)
print p

列印結果:

<__main__.Point object at 0x7f2f0f6b5c50>

如果在程式中記錄這些的日誌往往是無有多少可用資訊可用,因為我們更多的關注物件中的各成員實際值,所以可以重新定義__str__方法來覆蓋預設的行為:

class Point(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return '{self.x},{self.y}'.format(self = self)

p = Point(10, 11)
print p

列印:

10,11

就像java裡重寫toString方法一樣

 

用with管理操作資源的上下文環境

在一個比較典型的場景裡,如資料庫操作,我們操作connection時一般要正常關閉連線,而不管是正常退出還是異常退出。如下:

class Connection(object):
    def execute(self, sql):
        raise Exception('ohoh, exception!')

    def close(self):
        print 'closed the Connection'

try:
    conn = Connection()
    conn.execute('select * from t_users')
finally:
    conn.close()

上面我們用finally來保證conn的關閉。但是我們可以用with更優雅的完成這個工作。

class Connection(object):
    def execute(self, sql):
        raise Exception('ohoh, exception!')

    def close(self):
        print 'closed the Connection'

    def __enter__(self):
        return self

    def __exit__(self, errorType, errorValue, error):
        self.close()

with Connection() as conn:
    conn.execute('select * from t_users')

注意: 用with管理資源時,要定義__enter__和__exit__方法,它們分別在進入with和退出上下文時執行

 

運用generator

我們知道,運用list comprehensive可以對集合進行一定的運算變成另外一個集合物件,但是它會一下子生成所有元素並插入到新生成的列表集合中。如:

for e in [i * 2 for i in oneList]:
    pass

但是這樣會比較消耗資源,而我們的目的的只是遍歷新生成的列表集合進行相應的運算,所以這個時候用generator更適合:

for e in (i * 2 for i in oneList):
    pass

用(...) 代替 [....],就像用xrange代替range一樣

 

相關文章