python中定義函數和參數的傳遞問題


作者:達聞西
鏈接:https://zhuanlan.zhihu.com/p/24162430
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

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,他曾在表示過一些從函數式編程里拿來的特性是個錯誤。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM