1. 程式人生 > 其它 >Python Tips, Tricks, and Hacks

Python Tips, Tricks, and Hacks

一、快速技巧

1.1、4 種引號

' 
''' 
" 
""" 

print """I wish that I'd never heard him say, '''She said, "He said, 'Give me five dollars'"'''"""

1.2、物件/變數的真與假

my_object = 'Test' # True example
# my_object = '' # False example


if len(my_object) > 0:
    print 'my_object is not empty'


if len(my_object):  # 0 等價於 false
    print 'my_object is not empty'


if my_object != '':
    print 'my_object is not empty'


if my_object: # 空字串等價於 false
    print 'my_object is not empty'

結論:如果只是想判斷物件是否為空,真的沒必要去檢查它的長度或者是否相等。

1.3、子串檢測

string = 'Hi there' # True example
# string = 'Good bye' # False example
if string.find('Hi') != -1:
    print 'Success!'

上面的程式碼有點醜陋,下面的功能與之完全等價:

string = 'Hi there' # True example
# string = 'Good bye' # False example
if 'Hi' in string:
    print 'Success!'

1.4、優雅的列印列表

join 會將每個元素轉換成字串並用指定字元連線,其聰明之處在於不會在末尾元素新增連線符。 更重要的是它的優雅和高效,它的時間是線性的。

recent_presidents = ['George Bush', 'Bill Clinton', 'George W. Bush']
print 'The three most recent presidents were: %s.' % ', '.join(recent_presidents)
# prints 'The three most recent presidents were: George Bush, Bill Clinton, George W. Bush.

1.5、整除與浮點除法

預設是整除,想要得到小數請看如下的幾種方法

5/2        # Returns 2
5.0/2      # Returns 2.5
float(5)/2 # Returns 2.5
5//2       # Returns 2


from __future__ import division
5/2        # Returns 2.5
5.0/2      # Returns 2.5
float(5)/2 # Returns 2.5
5//2       # Returns 2

1.6、Lambda 表示式

Lambda 就是一個單行的匿名函式,你可以把它轉換成一個正常的函式,但活用 Lambda 可以讓我們的程式碼更加優雅。

def add(a,b): return a+b
add2 = lambda a,b: a+b
print add2(2, 3)

squares = map(lambda a: a*a, [1,2,3,4,5])
# squares is now [1,4,9,16,25]

這裡如果沒有 lambda,你必須定義一個函式。 Lambda Functions 的語法: lambda variable(s) : expression

二、列表

2.1、列表解析

2.1.1、對映列表

numbers = [1,2,3,4,5]
squares = []
for number in numbers:
    squares.append(number*number)
# Now, squares should have [1,4,9,16,25]

使用 map 對映

numbers = [1,2,3,4,5]
squares = map(lambda x: x*x, numbers)
# Now, squares should have [1,4,9,16,25]

雖然 3 行程式碼變成了 1 行程式碼,但仍然比較醜陋,下面看看列表解析:

numbers = [1,2,3,4,5]
squares = [number*number for number in numbers]
# Now, squares should have [1,4,9,16,25]

2.1.2、過濾列表

numbers = [1,2,3,4,5]
numbers_under_4 = []
for number in numbers:
    if number < 4:
        numbers_under_4.append(number)
# Now, numbers_under_4 contains [1,4,9]


numbers = [1,2,3,4,5]
numbers_under_4 = filter(lambda x: x < 4, numbers)
# Now, numbers_under_4 contains [1,2,3]


numbers = [1,2,3,4,5]
numbers_under_4 = [number for number in numbers if number < 4]
# Now, numbers_under_4 contains [1,2,3]

恩,列表解析再一次給了我們更短、更簡潔、更容易理解的程式碼。

2.1.3、Map and Filter at Once

numbers = [1,2,3,4,5]
squares = []
for number in numbers:
    if number < 4:
        squares.append(number*number)
# squares is now [1,4,9]


numbers = [1,2,3,4,5]
squares = map(lambda x: x*x, filter(lambda x: x < 4, numbers))
# squares is now [1,4,9]


numbers = [1,2,3,4,5]
squares = [number*number for number in numbers if number < 4]
# square is now [1,4,9]

2.1.4、生成器表示式

Generator expressions are newish in Python 2.4, and possibly the least publicized Cool Thing About Python ever. As in, I just found out about them. Generator expressions do not load the whole list into memory at once, but instead create a 'generator object' so only one list element has to be loaded at any time. Of course, if you actually need to use the entire list for something, this doesn't really help much. But if you're just passing it off to something that takes any iterable object -- like a for loop -- you might as well use a generator function.

numbers = (1,2,3,4,5) # Since we're going for efficiency, I'm using a tuple instead of a list ;)
squares_under_10 = (number*number for number in numbers if number*number < 10)
print type(squares_under_10) # squares_under_10 現在是一個生成器物件
# squares_under_10 is now a generator object, from which each successive value can be gotten by calling .next()

for square in squares_under_10:
    print square,
# prints '1 4 9'

這會比列表解析更加高效

So, you want to use generator expressions for large numbers of items. You want to always use list comprehensions if you need the entire list at once for some reason. If neither of these is true, just do whatever you want. It's probably good practice to use generator expressions unless there's some reason not to, but you're not going to see any real difference in efficiency unless the list is very large.

As a final note, generator expressions only need to be surrounded by one set of parentheses. So, if you're calling a function with only a generator expression, you only need one set of parentheses. This is valid Python: some_function(item for item in list).

2.1.5、巢狀 for 語句

List comprehensions and generator expressions can be used for more than just mapping and filtering; you can create rather complex lists of lists with them [1]. Not only can you map and filter, you can nest the for expressions. A python neophyte might write something like:

for x in (0,1,2,3):
    for y in (0,1,2,3):
        if x < y:
            print (x, y, x*y),

# prints (0, 1, 0) (0, 2, 0) (0, 3, 0) (1, 2, 2) (1, 3, 3) (2, 3, 6)

You can see that this code is pretty crazy. With a list comprehension, though, you can do this more quickly:

print [(x, y, x * y) for x in (0,1,2,3) for y in (0,1,2,3) if x < y]
# prints [(0, 1, 0), (0, 2, 0), (0, 3, 0), (1, 2, 2), (1, 3, 3), (2, 3, 6)]

As you can see, this code iterates over four values of y, and for each of those values, iterates over four values of x and then filters and maps. Each list item then, is itself a list of x, y, x * y. Note that xrange(4) is a bit cleaner than (0,1,2,3), especially for longer lists, but we haven't gotten there yet.

2.2 reduce化簡一個列表

numbers = [1,2,3,4,5]
result = 1
for number in numbers:
    result *= number
# result is now 120
Or you could use the built-in function reduce, which accepts a function that takes two arguments, and a list:

numbers = [1,2,3,4,5]
result = reduce(lambda a,b: a*b, numbers)
# result is now 120

Now it's not as pretty as a list comprehension, but it is shorter than a for loop. Definitely worth keeping in mind.

2.3 迭代一個列表: range, xrange and enumerate

strings = ['a', 'b', 'c', 'd', 'e']
for index in xrange(len(strings)):
    print index,
# prints '0 1 2 3 4'

The problem here is that usually you end up needing the list elements anyways. What's the use of just having the index values? Python has a really awesome built-in function called enumerate that will give you both. enumerate-ing a list will return an iterator of index, value pairs:

strings = ['a', 'b', 'c', 'd', 'e']
for index, string in enumerate(strings):
    print index, string,
# prints '0 a 1 b 2 c 3 d 4 e'

As an added advantage, enumerate is quite a bit cleaner and more readable than xrange(len()). Because of this, range and xrange are probably only useful if you need to create a list of values from scratch for some reason, instead of from an existing list.

2.4 檢查列表中的每個元素

Say you want to check to see if any element in a list satisfies a condition (say, it's below 10). Before Python 2.5, you could do something like this:

numbers = [1,10,100,1000,10000]
if [number for number in numbers if number < 10]:
    print 'At least one element is over 10'
# Output: 'At least one element is over 10'

With the new built-in any function introduced in Python 2.5, you can do the same thing cleanly and efficiently. 

numbers = [1,10,100,1000,10000]
if any(number < 10 for number in numbers):
    print 'Success'
# Output: 'Success!'

Similarly, you can check if every element satisfies a condition. Without Python 2.5, you'll have to do something like this:

numbers = [1,2,3,4,5,6,7,8,9]
if len(numbers) == len([number for number in numbers if number < 10]):
    print 'Success!'
# Output: 'Success!'

With Python 2.5, there's of course an easier way: the built-in all function. As you might expect, it's smart enough to bail after the first element that doesn't match, returning False. This method works just like the any method described above.

numbers = [1,2,3,4,5,6,7,8,9]
if all(number < 10 for number in numbers):
    print 'Success!'
# Output: 'Success!'

2.5 按元素位置合併列表: zip

letters = ['a', 'b', 'c']
numbers = [1, 2, 3]
squares = [1, 4, 9]
zipped_list = zip(letters, numbers, squares)
# zipped_list contains [('a', 1, 1), ('b', 2, 4), ('c', 3, 9)]

2.6 更多的列表操作

The following are all built-in functions that can be called on any list or iterable. max Returns the largest element in the list min Returns the smallest element in the list sum Returns the sum of all elements in the list. Accepts an optional second argument, the value to start with when summing (defaults to 0).

2.7 利用 set 唯一性判斷 list 是否元素重複

numbers = [1,2,3,3,4,1]
set(numbers)
# returns set([1,2,3,4])

if len(numbers) == len(set(numbers)):
    print 'List is unique!'
# In this case, doesn't print anything

三、字典

3.1  字典結構

dict(a=1, b=2, c=3)
# returns {'a': 1, 'b': 2, 'c': 3}

相比 a = {'a': 1, 'b': 2, 'c': 3} 的方式更加簡潔明瞭。

This might be a bit cleaner than a 'regular' dictionary creation depending on your code; there are less quotes floating around. I use it often.

3.2 字典轉換成列表

Turning a dictionary into a list or an iterator is easy. To get a list of keys, you can just cast the dict into a list. It's cleaner, though to call .keys() on the dictionary to get a list of the keys, or .iterkeys() to get an iterator. Similarly, you can call .values() or .itervalues() to get a list or iterator of dictionary values. Remember though, that dicts are inherently unordered and so these values won't be in any meaningful order. To preserve both keys and values, you can turn a dict into a list or iterator of 2-item tuples by using .items() or .iteritems(). This is something that you'll probably do a lot, and isn't very exciting:

dictionary = {'a': 1, 'b': 2, 'c': 3}
dict_as_list = dictionary.items()
#dict_as_list now contains [('a', 1), ('b', 2), ('c', 3)]

3.3 列表轉換成字典

You can reverse the process, turning a list of 2-element lists or tuples into a dict:

dict_as_list = [['a', 1], ['b', 2], ['c', 3]]
dictionary = dict(dict_as_list)
# dictionary now contains {'a': 1, 'b': 2, 'c': 3}

You can also combine this with the 'keyword arguments' method of creating a dictionary discussed above:

dict_as_list = [['a', 1], ['b', 2], ['c', 3]]
dictionary = dict(dict_as_list, d=4, e=5)
# dictionary now contains {'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5}

Being able to convert a dict to a list is kind of handy, I guess. But what really makes it awesome is the next trick.

3.4 字典解析

Although Python doesn't have built-in dictionary comprehensions, you can do something pretty close with little mess or code. Just use .iteritems() to turn your dict into a list, throw it in a generator expression (or list comprehension), and then cast that list back into a dict. For example, say I have a dictionary of name:email pairs, and I want to create a dictionary of name:is_email_at_a_dot_com pairs:

emails = {'Dick': '[email protected]', 'Jane': '[email protected]', 'Stou': '[email protected]'}
email_at_dotcom = dict( [name, '.com' in email] for name, email in emails.iteritems() )
# email_at_dotcom now is {'Dick': True, 'Jane': True, 'Stou': False}

Damn straight. Of course, you don't have to start and end with a dict, you can throw some lists in there too. While this is a little less readable than a straight list comprehension, I'd argue it's still better than a massive for loop.

四、條件表示式

4.1  正確的方式

While writing this article, I stumbled upon the right way to select values inline, new in Python 2.5 (you'd think there would have been more fanfare!). Python now supports the syntax 'value_if_true if test else value_if_false'. So, you can do simple selection of values in one line, with no weird syntax or major caveats:

test = True
# test = False
result = 'Test is True' if test else 'Test is False'
# result is now 'Test is True'

Okay, it's a bit ugly still. Alas. You can also chain multiple tests in one line:

test1 = False
test2 = True
result = 'Test1 is True' if test1 else 'Test1 is False, test2 is True' if test2 else 'Test1 and Test2 are both False'

The first if/else is evaluated first, and if test1 is false the second if/else is evaluated. You can do more complicated things too, especially if you throw in some parentheses. Personal Note: This is pretty new on the field, and my reaction is mixed. It really is the Right Way, it's cleaner, and I like it... but it's still ugly especially if you have multiple nested if/else's. Of course, the syntax for all of the value selection tricks is ugly. I have soft spot for the and/or trick below, I actually find it very intuitive, now that I understand how it works. Also, it's not any less efficient than doing things the Right Way. What do you think? Feel free to comment below. Although the inline if/else is the new, more correct way, you'd better still check out the tricks below. Even if you only plan on programming in Python 2.5, you're still going to run into these in older code. Of course, if you need backwards compatibility or don't have Python 2.5, you'd really better check out the tricks below.

4.2   The and/or Trick:慎用!

In Python, 'and' and 'or' are complex creatures. and-ing two expressions together doesn't just return True if both are true and False if both are false. Instead, 'and' returns the first false value, or the last value if all are true. In other words, if the first value is false it is returned, otherwise the last value is returned. The result of this is something you would expect: if both are true, the last value is returned, which is true and will evaluate to True in a boolean test (eg, an 'if' statement). If one is false, that one is returned and will evaluate to False in a boolean test. or-ing two expressions together is similar. 'or' returns the first true value, or the last value if all are false. In other words, if the first value is true it is returned, otherwise the last value is returned. So, if both are false, the last value is returned, which is false and will evaluate to False in a boolean test. If one is true, that one is returned and will evaluate to True in a boolean test. This doesn't help you much when we're just testing for truthfulness. But you can use 'and' and 'or' for other purposes in Python; my favorite is to select between values in a manner akin to C's ternary conditional assignment operator 'test ? value_if_true : value_if_false':

test = True
# test = False
result = test and 'Test is True' or 'Test is False'
# result is now 'Test is True'

How does this work? If test is true, the and statement skips over it and returns its right half, here 'Test is True' or 'Test is False'. As processing continues left to right, the or statement returns the first true value, 'Test is True'. If test is false, the and statement returns test. As processing continues left to right, the remaining statement is test or 'Test is False'. Since test is false, the or statement skips over it and returns its right half, 'Test is False'. Warning Be careful that the middle (if_true) value is never false. If it is, the 'or' statement will always skip over it and always return the rightmost (if_false) value, no matter what the test value is. Having gotten used to this method, 'The Right Way' (above) actually seems less intuitive to me. If you're not worried about backwards compatibility, I suggest you try both and see which one you like better. It's pretty easy to nest and/or tricks or to throw on extra and's or or's once you understand the logic behind it. If you can't decide or don't feel like learning both, then don't use and/or. Do things the Right Way, and be done with it. Of course, if you need to support Python versions under 2.5, 'The Right Way' won't work. (I was tempted to say that it 'is The Wrong Way'). In that case the and/or trick is definitely your best bet for most situations. Hopefully this all makes sense; it's hard to explain. It might seem complicated now, but if you use it a few times and play with 'and' and 'or' it will shortly make sense and you'll be able to come up with more complicated 'and' and 'or' tricks on your own.

4.3  使用 true 和 false 作為索引

Another way to select values is to use True and False as list indexes, taking advantage of the fact that False == 0 and True == 1:

test = True
# test = False
result = ['Test is False','Test is True'][test]
# result is now 'Test is True'

This is more straightforward than the and/or trick, and free of the problem where the value_if_true must itself be true. However, it also suffers from a significant flaw: both list items are evaluated before truthfulness is checked. For strings or other simple items, this is not a big deal. But if each item involves significant computation or I/O, you really don't want to do twice the work that you have to. For this reason I usually prefer the 'Right Way' or the and/or trick. Also note that the index method only works when you know that test is False or True (or 0 or 1, but not any other integer or an arbitrary object). Otherwise you should write bool(test) instead of test to get the same behavior as the and/or trick expression above.

五、 函式

5.1 預設值僅僅被計算一次

def function(item, stuff = []):
    stuff.append(item)
    print stuff

function(1)
# prints '[1]'
function(2)
# prints '[1,2]' !!!

在Python裡,函式的預設值實在函式定義的時候例項化的,而不是在呼叫的時候。

The default value for a function argument is only evaluated once, when the function is defined. Python simply assigns this value to the correct variable name when the function is called.

def foo(numbers=[]):
    numbers.append(9)
    print numbers
    
>>> foo()
[9]
>>> foo(numbers=[1,2])
[1, 2, 9]
>>> foo(numbers=[1,2,3])
[1, 2, 3, 9]

>>> foo() # first time, like before
[9]
>>> foo() # second time
[9, 9]
>>> foo() # third time...
[9, 9, 9]
>>> foo() # WHAT IS THIS BLACK MAGIC?!
[9, 9, 9, 9]

我們仍然會問,為什麼在呼叫函式的時候這個預設值卻被賦予了不同的值?因為在你每次給函式指定一個預設值的時候,Python都會儲存這個值。如果在呼叫函式的時候重寫了預設值,那麼這個儲存的值就不會被使用。當你不重寫預設值的時候,那麼Python就會讓預設值引用儲存的值(這個例子裡的numbers)。它並不是將儲存的值拷貝來為這個變數賦值。這個概念可能對初學者來說,理解起來會比較吃力,所以可以這樣來理解:有兩個變數,一個是內部的,一個是當前執行時的變數。現實就是我們有兩個變數來用相同的值進行互動,所以一旦 numbers 的值發生變化,也會改變Python裡面儲存的初始值的記錄。

Python doesn't check if that value (that location in memory) was changed. It just continues to assign that value to any caller that needs it. So, if the value is changed, the change will persist across function calls. Above, when we appended a value to the list represented by stuff, we actually changed the default value for all eternity. When we called function again looking for a default value, the modified default was given to us. The solution: don't use mutable objects as function defaults. You might be able to get away with it if you don't modify them, but it's still not a good idea.

解決方案:使用不可變物件,例如 None。

A better way to write the above code would be:

def function(item, stuff = None):
    if stuff is None:
        stuff = []
    stuff.append(item)
    print stuff

function(1)
# prints '[1]'
function(2)
# prints '[2]', as expected

None is immutable (and we're not trying to change it anyways), so we're safe from accidently changing value of the default. On the plus side, a clever programmer could probably turn this into a trick, in effect creating C-style 'static variables'.

通常,當人們聽到這裡,大家會問另一個關於預設值的問題。思考下面的程式:

def foo(count=0):
    count += 1
    print count

>>> foo()
1
>>> foo()
1
>>> foo(2)
3
>>> foo(3)
4
>>> foo()
1

當我們執行它的時候,其結果完全是我們期望的:

這又是為啥呢?其祕密不在與預設值被賦值的時候,而是這個預設值本身。整型是一種不可變的變數。跟 list 型別不同,在函式執行的過程中,整型變數是不能被改變的。當我們執行 count+=1 這句話時,我們並沒有改變 count 這個變數原有的值。而是讓 count 指向了不同的值。可是,當我們執行 numbers.append(9) 的時候,我們改變了原有的 list 。因而導致了這種結果。

下面是在函式裡使用預設值時會碰到的另一種相同問題:

def print_now(now=time.time()):
    print now
    
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91
>>> print_now()
1373121487.91

跟前面一樣,time.time() 的值是可變的,那麼它只會在函式定義的時候計算,所以無論呼叫多少次,都會返回相同的事件 — 這裡輸出的事件是程式被Python解釋執行的時間。

* 這個問題和它的解決方案在 Python 2.x 和 3.x 裡都是類似的,在Python 3.x 裡面唯一的不同,是裡面的print 表示式應該是函式呼叫的方式(print(numbers))。

其實這裡的知識點在於你要認清 python 中賦值、引用和複製、拷貝的關係,請參考:

http://my.oschina.net/leejun2005/blog/145911

5.1.1 強制使預設引數每次被拷貝複製

If you prefer less cluttered functions at the cost of some clarity, you can forcefully re-evaluate the default arguments before each function call. The following decorator stores the original values of the default arguments. It can be used to wrap a function and reset the default arguments before each call. [3]

from copy import deepcopy
def resetDefaults(f):
    defaults = f.func_defaults
    def resetter(*args, **kwds):
        f.func_defaults = deepcopy(defaults)
        return f(*args, **kwds)
    resetter.__name__ = f.__name__
    return resetter

Simply apply this decorator to your function to get the expected results.

@resetDefaults # This is how you apply a decorator
def function(item, stuff = []):
    stuff.append(item)
    print stuff

function(1)
# prints '[1]'
function(2)
# prints '[2]', as expected

5.2 接受任意的數字或引數

Python lets you have arbitrary numbers of arguments in your functions. First define any required arguments (if any), then use a variable with a '*' prepended to it. Python will take the rest of the non-keyword arguments, put them in a list or tuple, and assign them to this variable:

def do_something(a, b, c, *args):
    print a, b, c, args
do_something(1,2,3,4,5,6,7,8,9)
# prints '1, 2, 3, (4, 5, 6, 7, 8, 9)'

Why would you want to do this? A common reason is that your function accepts a number of items and does the same thing with all of them (say, sums them up). You could force the user to pass a list: sum_all([1,2,3]) or you could allow them to use an arbitrary number of arguments, which makes for cleaner code: sum_all(1,2,3). You can also have arbitrary numbers of keyword arguments. After you've defined all other arguments, use a variable with '**' prepended to it. Python will take the rest of the keyword arguments, put them in a dictionary, and assign them to this variable:

def do_something_else(a, b, c, *args, **kwargs):
    print a, b, c, args, kwargs
do_something_else(1,2,3,4,5,6,7,8,9, timeout=1.5)
# prints '1, 2, 3, (4, 5, 6, 7, 8, 9), {"timeout": 1.5}'

Why would you want to do this? I think the most common reason is if your function is a wrapper for some other function or functions, any keyword arguments that you use can be popped off the dictionary and the remainder of the keyword arguments can be passed to the other function(s) (see Passing a List or Dictionary as Arguments, below)

5.2.1  警告

Passing both arbitrary non-keyword arguments and named (non-arbitrary) keyword arguments in one function is seemingly impossible. This is because named keyword arguments must be defined before the '*' parameter in the function definition, and are filled before that parameter is filled. For example, imagine a function:

def do_something(a, b, c, actually_print = True, *args):
    if actually_print:
        print a, b, c, args

We now have a problem: there is no way to specify 'actually_print' as a named keyword argument while simultaneously providing arbitrary non-keyword arguments. Both of the following will error:

do_something(1, 2, 3, 4, 5, actually_print = True)
# actually_print is initially set to 4 (see why?) and then re-set,
# causing a TypeError ('got multiple values for keyword argument')

do_something(1, 2, 3, actually_print = True, 4, 5, 6)
# This is not allowed as keyword arguments may not precede non-keyword arguments.  A SyntaxError is raised.

The only way to pass 'actually_print' in this situation is to pass it as a non-keyword argument:

do_something(1, 2, 3, True, 4, 5, 6)
# Result is '1, 2, 3, (4, 5, 6)'

5.3  傳遞一個列表或者字典作引數

Since you can receive arguments as a list or dictionary, it's not terribly surprising, I suppose, that you can send arguments to a function from a list or dictionary. The syntax is exactly the same as above.

To send a list as non-keyword arguments, just prepend it with a '*':

args = [5,2]
pow(*args)
# returns pow(5,2), meaning 5^2 which is 25

And, of course, to send a dictionary as keyword arguments (this is probably more common), prepend it with '**':

def do_something(actually_do_something=True, print_a_bunch_of_numbers=False):
    if actually_do_something:
        print 'Something has been done'
        #
        if print_a_bunch_of_numbers:
            print range(10)
kwargs = {'actually_do_something': True, 'print_a_bunch_of_numbers': True}
do_something(**kwargs)

# prints 'Something has been done', then '[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'

Historical footnote: In older versions of Python (pre-2.3) you called functions with arbitrary arguments using the built-in apply (function, arg_list, keyword_arg_dict)'.

5.4 裝飾器

Function decorators are fairly simple, but if you've never seen them before you'll have no idea what's going on, as unlike most of Python the syntax isn't very clear. A decorator is a function that wraps another function: the main function is called and its return value is passed to the decorator. The decorator then returns a function that replaces the wrapped function as far as the rest of the program is concerned. Without further delay, here is the syntax:

def decorator1(func):
    return lambda: func() + 1

def decorator2(func):
    def print_func():
        print func()
    return print_func

@decorator2
@decorator1
def function():
    return 41

function()
# prints '42'

關於裝飾器的理解,請參考:

http://my.oschina.net/leejun2005/blog/146025

In this example, 'function' is passed to 'decorator1'. 'decorator1' returns a function that calls 'function' and adds 1. This function is then passed to 'decorator2', which returns a function that calls the function returned by 'decorator1' and prints the result. This last function is the function you are actually calling when you call 'function'. Whew.

This example does the exact same thing, but more verbosely and without decorators:

def decorator1(func):
    return lambda: func() + 1

def decorator2(func):
    def print_func():
        print func()
    return print_func

def function():
    return 41

function = decorator2(decorator1(function))

function()
# prints '42'

Typically decorators are used to add abilities to your functions (see Creating Class Methods, below). But even more typically, they're not used at all. But it's good to know what you're looking at. For more information, check out my article Python Decorators Don't Have to be (that) Scary, Python Decorators on Dr. Dobbs or "Function Definitions" in the Python Docs

5.5  使用字典函式模擬 switch 語句

Ever miss the switch statement? As you probably know, Python doesn't really have a syntactical equivalent, unless you count repeated elif's. What you might not know, though, is that you can replicate the behavior (if not the cleanliness) of the switch statement by creating a dictionary of functions keyed by the value you want to switch on. For example, say you're handling keystrokes and you need to call a different function for each keystroke. Also say you've already defined these three functions:

def key_1_pressed():
    print 'Key 1 Pressed'

def key_2_pressed():
    print 'Key 2 Pressed'

def key_3_pressed():
    print 'Key 3 Pressed'

def unknown_key_pressed():
    print 'Unknown Key Pressed'

In Python, you would typically use elif's to choose a function:

keycode = 2
if keycode == 1:
   key_1_pressed()
elif keycode == 2:
   key_2_pressed()
elif number == 3:
   key_3_pressed()
else:
   unknown_key_pressed()
# prints 'Key 2 Pressed'

But you could also throw all the functions in a dictionary, and key them to the value you're switching on. You could even check see if the key exists and run some code if it doesn't:

keycode = 2
functions = {1: key_1_pressed, 2: key_2_pressed, 3: key_3_pressed}
functions.get(keycode, unknown_key_pressed)()

You can see that this could be a lot cleaner than the elif example for large numbers of functions.

六、類

6.1 手動傳遞 self

Methods are just regular functions that when called from an instance are passed that instance as the first argument (usually called 'self'). If for some reason you're not calling the function from an instance, you can always pass the instance manually as the first argument. For example:

class Class:
    def a_method(self):
        print 'Hey a method'

instance = Class()

instance.a_method()
# prints 'Hey a method', somewhat unsuprisingly.  You can also do:

Class.a_method(instance)
# prints 'Hey a method'

Internally, these statements are exactly the same.

6.2  檢查存在的方法或屬性

Need to know if a particular class or instance has a particular property or method? You can use the built-in 'hasattr' function to check; it accepts the object and the attribute (as a string) to check for. You use similarly to the dict 'has_key' method (although it works completely differently):

class Class:
    answer = 42
hasattr(Class, 'answer')
# returns True
hasattr(Class, 'question')
# returns False

You can also check for existence of and access the property in one step using the built-in function 'getattr'. getattr also accepts the object and the attribute, as a string, to check for. It has an optional third argument, giving the default if the attribute is not found. Unlike the dict's 'get' method that you might be more familiar with, if the default is not given and the attribute is not found, an AttributeError is raised:

class Class:
    answer = 42
getattr(Class, 'answer')
# returns 42
getattr(Class, 'question', 'What is six times nine?')
# returns 'What is six times nine?'
getattr(Class, 'question')
# raises AttributeError

Don't overuse hasattr and getattr. If you've written your class in manner where you need to keep checking to see if a property exists, you've written it wrong. Just always have the value exist and set it to None (or whatever) if it's not being used. These functions are best used for handling polymorphism, that is, allowing your function/class/whatever to support different kinds of objects.

6.3  建立後再修改類

You can add, modify, or delete a class property or method long after the class has been created, and even after it has been instantiated. Just access the property or method as Class.attribute. No matter when they were created, instances of the class will respect these changes:

class Class:
   def method(self):
        print 'Hey a method'

instance = Class()
instance.method()
# prints 'Hey a method'

def new_method(self):
    print 'New method wins!'

Class.method = new_method
instance.method()
# prints 'New method wins!'

Pretty awesome. But don't get carried away with modifying preexisting methods, it's bad form and can confuse the crap out of any objects using that class. On the other hand, adding methods is a lot less (but still somewhat) dangerous.

6.4  建立類方法

Occasionally when writing a class you want to include a function that is called from the class, not the instance. Perhaps this method creates new instances, or perhaps it is independent of any properties of any individual instance. Python actually gives you two ways to do this, depending if your method needs to (or should) know about which class called it. Both involve applying decorators to your methods. A 'class method' receives the class as the first argument, just as a regular instance method receives the instance as the first argument. So, the method is aware if it is being called from its own class or from a subclass. A 'static method' receives no information about where it is called; it is essentially a regular function, just in a different scope. Class and static methods can be called straight from the class, as Class.method(), or from an instance as Class().method(). The instance is ignored except for its class. Here's an example of each, along with a regular instance method:

class Class:
    @classmethod
    def a_class_method(cls):
        print 'I was called from class %s' % cls
    #
    @staticmethod
    def a_static_method():
        print 'I have no idea where I was called from'
    #
    def an_instance_method(self):
        print 'I was called from the instance %s' % self

instance = Class()

Class.a_class_method()
instance.a_class_method()
# both print 'I was called from class __main__.Class'

Class.a_static_method()
instance.a_static_method()
# both print 'I have no idea where I was called from'

Class.an_instance_method()

# raises TypeError
instance.an_instance_method()
# prints something like 'I was called from the instance <__main__.Class instance at 0x2e80d0>'

七、 總結

Need more inspiration? One good place to look is the Python Built-in Functions page. There's a lot of cool functions that you've probably never heard of.

If you come up with any good tricks or need-to-knows, feel free to add them to this article. (You can get a Siafoo account at http://www.siafoo.net/new/user)

Happy coding.

八、我寫的一些其他 python 博文

Python Decorators Don't Have to be (that) Scary

Type Checking in Python

Python __Underscore__ Methods

Python Debugging Tips

Use setup.py to Deploy Your Python App with Style

九、引用和參考

1、Python Tips, Tricks, and Hacks

http://www.siafoo.net/article/52

2、Python 入門指南(中文版)

http://www.pythondoc.com/pythontutorial27/index.html

3、Python 新手常犯錯誤(第一部分):用一個可變值作為函式引數預設值

http://blog.jobbole.com/42706/

4、Python 新手常犯錯誤(第二部分):作用域(還可以參考《learning python》P332)

http://blog.jobbole.com/43826/

5、衝刺豆瓣(13):python 面試必讀

http://www.cnblogs.com/BeginMan/p/3218164.html

6、30 Python Language Features and Tricks You May Not Know About

http://sahandsaba.com/thirty-python-language-features-and-tricks-you-may-not-know.html

7、Python程式設計中需要注意的一些事

http://blog.jobbole.com/19835/

8、Python技巧和陷阱

http://python.jobbole.com/81486/

9、分享一個準備給公司講python高階程式設計的slide

http://www.dongwm.com/archives/fen-xiang-%5B%3F%5D-ge-zhun-bei-gei-gong-si-jiang-pythongao-ji-bian-cheng-de-slide/

http://dongweiming.github.io/Expert-Python/#1