1. 程式人生 > >Python有參函式和無參函式例項

Python有參函式和無參函式例項

5.2.4 函式、生成器和類

還是從幾個例子看起:

def say_hello():
    print('Hello!')

def greetings(x='Good morning!'):
    print(x)

say_hello()                 		# Hello!
greetings()                 		# Good morning!
greetings("What's up!")     		# What's up!
a = greetings()             		# 返回值是None

def create_a_list(x, y=2, z=3):	# 預設引數項必須放後面
    return [x, y, z]

b = create_a_list(1)        		# [1, 2, 3]
c = create_a_list(3, 3)     		# [3, 2, 3]
d = create_a_list(6, 7, 8)  	# [6, 7, 8]

def traverse_args(*args):
    for arg in args:
        print(arg)

traverse_args(1, 2, 3)              # 依次列印1, 2, 3
traverse_args('A', 'B', 'C', 'D')   # 依次列印A, B, C, D

def traverse_kargs(**kwargs):
    for k, v in kwargs.items():
        print(k, v)

traverse_kargs(x=3, y=4, z=5)       # 依次列印('x', 3), ('y', 4), ('z', 5)
traverse_kargs(fighter1='Fedor', fighter2='Randleman')

def foo(x, y, *args, **kwargs):
    print(x, y)
    print(args)
    print(kwargs)

# 第一個pring輸出(1, 2)
# 第二個print輸出(3, 4, 5)
# 第三個print輸出{'a': 3, 'b': 'bar'}
foo(1, 2, 3, 4, 5, a=6, b='bar')

其實和很多語言差不多,括號裡面定義引數,引數可以有預設值,且預設值不能在無預設值引數之前。Python中的返回值用return定義,如果沒有定義返回值,預設返回值是None。引數的定義可以非常靈活,可以有定義好的固定引數,也可以有可變長的引數(args: arguments)和關鍵字引數(kargs: keyword arguments)。如果要把這些引數都混用,則固定引數在最前,關鍵字引數在最後。

Python中萬物皆物件,所以一些情況下函式也可以當成一個變數似的使用。比如前面小節中提到的用字典代替switch-case的用法,有的時候我們要執行的不是通過條件判斷得到對應的變數,而是執行某個動作,比如有個小機器人在座標(0, 0)處,我們用不同的動作控制小機器人移動:

moves = ['up', 'left', 'down', 'right']

coord = [0, 0]

for move in moves:
    if move == 'up':        # 向上,縱座標+1
        coord[1] += 1
    elif move == 'down':    # 向下,縱座標-1
        coord[1] -= 1
    elif move == 'left':    # 向左,橫座標-1
        coord[0] -= 1
    elif move == 'right':   # 向右,橫座標+1
        coord[0] += 1
    else:
        pass
    print(coord)            # 列印當前位置座標

不同條件下對應的是對座標這個列表中的值的操作,單純的從字典取值就辦不到了,所以就把函式作為字典的值,然後用這個得到的值執行相應動作:

moves = ['up', 'left', 'down', 'right']

def move_up(x):         # 定義向上的操作
    x[1] += 1

def move_down(x):       # 定義向下的操作
    x[1] -= 1

def move_left(x):       # 定義向左的操作
    x[0] -= 1

def move_right(x):      # 定義向右的操作
    x[0] += 1

# 動作和執行的函式關聯起來,函式作為鍵對應的值
actions = {
    'up': move_up,
    'down': move_down,
    'left': move_left,
    'right': move_right
}

coord = [0, 0]

for move in moves:
    actions[move](coord)
    print(coord)

把函式作為值取到後,直接加一括號就能使了,這樣做之後起碼在迴圈部分看上去很簡潔。有點C裡邊函式指標的意思,只不過更簡單。其實這種用法在之前講排序的時候我們已經見過了,就是operator中的itemgetter。itemgetter(1)得到的是一個可呼叫物件(callable object),和返回下標為1的元素的函式用起來是一樣的:

def get_val_at_pos_1(x):
    return x[1]

heros = [
    ('Superman', 99),
    ('Batman', 100),
    ('Joker', 85)
]

sorted_pairs0 = sorted(heros, key=get_val_at_pos_1)
sorted_pairs1 = sorted(heros, key=lambda x: x[1])

print(sorted_pairs0)
print(sorted_pairs1)

在這個例子中我們用到了一種特殊的函式:lambda表示式。Lambda表示式在Python中是一種匿名函式,lambda關鍵字後面跟輸入引數,然後冒號後面是返回值(的表示式),比如上邊例子中就是一個取下標1元素的函式。當然,還是那句話,萬物皆物件,給lambda表示式取名字也是一點問題沒有的:

some_ops = lambda x, y: x + y + x*y + x**y
some_ops(2, 3)  # 2 + 3 + 2*3 + 2^3 = 19

生成器(Generator

生成器是迭代器的一種,形式上看和函式很像,只是把return換成了yield,在每次呼叫的時候,都會執行到yield並返回值,同時將當前狀態儲存,等待下次執行到yield再繼續:

# 從10倒數到0
def countdown(x):
    while x >= 0:
        yield x
        x -= 1

for i in countdown(10):
    print(i)

# 列印小於100的斐波那契數
def fibonacci(n):
    a = 0
    b = 1
    while b < n:
        yield b
        a, b = b, a + b

for x in fibonacci(100):
    print(x)

生成器和所有可迭代結構一樣,可以通過next()函式返回下一個值,如果迭代結束了則丟擲StopIteration異常:

a = fibonacci(3)
print(next(a))  # 1
print(next(a))  # 1
print(next(a))  # 2
print(next(a))  # 丟擲StopIteration異常

Python3.3以上可以允許yield和return同時使用,return的是異常的說明資訊:

# Python3.3以上可以return返回異常的說明
def another_fibonacci(n):
    a = 0
    b = 1
    while b < n:
        yield b
        a, b = b, a + b
    return "No more ..."

a = another_fibonacci(3)
print(next(a))  # 1
print(next(a))  # 1
print(next(a))  # 2
print(next(a))  # 丟擲StopIteration異常並列印No more訊息

類(Class

Python中的類的概念和其他語言相比沒什麼不同,比較特殊的是protected和private在Python中是沒有明確限制的,一個慣例是用單下劃線開頭的表示protected,用雙下劃線開頭的表示private:

class A:
    """Class A"""
    def __init__(self, x, y, name):
        self.x = x
        self.y = y
        self._name = name

    def introduce(self):
        print(self._name)

    def greeting(self):
        print("What's up!")

    def __l2norm(self):
        return self.x**2 + self.y**2

    def cal_l2norm(self):
        return self.__l2norm()

a = A(11, 11, 'Leonardo')
print(A.__doc__)        	# "Class A"
a.introduce()           	# "Leonardo"
a.greeting()            	# "What's up!"
print(a._name)          	# 可以正常訪問
print(a.cal_l2norm())   # 輸出11*11+11*11=242
print(a._A__l2norm())   # 仍然可以訪問,只是名字不一樣
print(a.__l2norm())     	# 報錯: 'A' object has no attribute '__l2norm'

類的初始化使用的是__init__(self,),所有成員變數都是self的,所以以self.開頭。可以看到,單下劃線開頭的變數是可以直接訪問的,而雙下劃線開頭的變數則觸發了Python中一種叫做name mangling的機制,其實就是名字變了下,仍然可以通過前邊加上“_類名”的方式訪問。也就是說Python中變數的訪問許可權都是靠自覺的。類定義中緊跟著類名字下一行的字串叫做docstring,可以寫一些用於描述類的介紹,如果有定義則通過“類名.__doc__”訪問。這種前後都加雙下劃線訪問的是特殊的變數/方法,除了__doc__和__init__還有很多,這裡就不展開講了。

Python中的繼承也非常簡單,最基本的繼承方式就是定義類的時候把父類往括號裡一放就行了:

class B(A):
    """Class B inheritenced from A"""
    def greeting(self):
        print("How's going!")

b = B(12, 12, 'Flaubert')
b.introduce()   # Flaubert
b.greeting()    # How's going!
print(b._name())        # Flaubert
print(b._A__l2norm())   # “私有”方法,必須通過_A__l2norm訪問

5.2.5 map, reduce和filter

map可以用於對可遍歷結構的每個元素執行同樣的操作,批量操作:

map(lambda x: x**2, [1, 2, 3, 4])                 # [1, 4, 9, 16]

map(lambda x, y: x + y, [1, 2, 3], [5, 6, 7])   # [6, 8, 10]

reduce則是對可遍歷結構的元素按順序進行兩個輸入引數的操作,並且每次的結果儲存作為下次操作的第一個輸入引數,還沒有遍歷的元素作為第二個輸入引數。這樣的結果就是把一串可遍歷的值,減少(reduce)成一個物件:

reduce(lambda x, y: x + y, [1, 2, 3, 4])    # ((1+2)+3)+4=10

filter顧名思義,根據條件對可遍歷結構進行篩選:

filter(lambda x: x % 2, [1, 2, 3, 4, 5])    # 篩選奇數,[1, 3, 5]

需要注意的是,對於filter和map,在Python2中返回結果是列表,Python3中是生成器。

 

5.2.6 列表生成(list comprehension)

列表生成是Python2.0中加入的一種語法,可以非常方便地用來生成列表和迭代器,比如上節中map的兩個例子和filter的一個例子可以用列表生成重寫為:

[x**2 for x in [1, 2, 3, 4]]                      # [1, 4, 9 16]

[sum(x) for x in zip([1, 2, 3], [5, 6, 7])] # [6, 8, 10]

[x for x in [1, 2, 3, 4, 5] if x % 2]       # [1, 3, 5]

zip()函式可以把多個列表關聯起來,這個例子中,通過zip()可以按順序同時輸出兩個列表對應位置的元素對。如果要生成迭代器只需要把方括號換成括號,生成字典也非常容易:

iter_odd = (x for x in [1, 2, 3, 4, 5] if x % 2)

print(type(iter_odd))                       # <type 'generator'>

square_dict = {x: x**2 for x in range(5)}   # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

至於列表生成和map/filter應該優先用哪種,這個問題很難回答,不過Python創始人Guido似乎不喜歡map/filter/reduce,他曾在表示過一些從函數語言程式設計裡拿來的特性是個錯誤。