函數基礎 & 裝飾器 & 遞歸函數 & 函數嵌套及作用域 & 匿名函數 & 內置函數
Python基礎-函數
認識函數
為什么要使用函數?
1.避免代碼重用,在一個完整的項目中,某些功能會反復使用。那么會將功能封裝成函數,當我們要使用功能的時候直接調用函數即可。
2.提高代碼的可讀性
本質:函數就是對功能的封裝
優點:
1.簡化代碼結構,增加了代碼的復用度(重復使用的程度)
2.如果想修改某些功能或者調試某個BUG,只需要修改對應的函數即可
函數的定義與調用
定義
def 函數名(參數1,參數2):
''' 函數注釋'''
語句
return 返回值
定義:def關鍵字開頭,空格之后接函數名和圓括號,最后還要加一個冒號,def是固定的,不能變
函數名:遵循標識符規則,函數名是包含字母,數字,下划線的任意組合,但是不能以數字開頭。雖然函數名可以隨便取名,但是一般盡量定義成可以表示函數功能的。
參數列表:(參數1,參數2,……,參數n)任何傳入函數的參數和變量必須放在圓括號之間,用逗號分隔。函數從函數的調用者那里獲取的信息
冒號:函數內容(封裝的功能)以冒號開始,並且縮進。
語句:函數封裝的功能
return:一般用於結束函數,並返回信息給函數的調用者
注意:最后的return表達式,可以不寫,相當於return None
調用
格式:
返回值 = 函數名(參數1,參數2)
函數名:是要使用的功能的函數名字
參數:函數的調用者給函數傳遞的信息,如果沒有參數,小括號也不能省略
函數調用的本質:實參給形參賦值的過程
下面我們先來定義一個求計算字符串長度的函數。假如我們的len()函數現在不能用了,那么我們就需要定義一個函數,求字符串的長度。如果讓我們一次一次的去寫相同的代碼,會顯得很麻煩。這時候我們的函數就上場了。
1.給定一個字符串,調用函數,當他沒有返回值的時候返回Null def length(): s='hello world' length=0 for i in s: length+=1 print(length) print(length()) 2.return 必須放在函數里,當函數有返回值的時候,必須用變量接收才會有效果 def length(): s='hello world' length=0 for i in s: length+=1 return length print(length()) 計算字符串的長度
函數的返回值
return的作用:結束一個函數的執行
1、首先返回值可以是任意的數據類型
2、函數可以有返回值:如果有返回值,必須要用變量接收才有效果
也可以沒有返回值:
沒有返回值的時候分三種情況:
1.當不寫return的時候,函數的返回值為None
2.當只寫一個return的時候,函數的返回值為None
3.return None的時候,函數的返回值為None(幾乎不用)
3、return返回一個值(一個變量)
4、return返回多個值(多個變量):多個值之間用逗號隔開,以元組的形式返回。
接收:可以用一個變量接收,也可以用多個變量接收,返回幾個就用幾個變量去接收

def func(): a=111 b=[1,2,3] c={'a':15,'b':6} #return a#返回一個值 #return a,b,c#返回多個值,變量之間按逗號隔開,以元組的形式返回 print(func()) 函數有一個或多個返回值

1.不寫return時返回None def func(): a=111 b=[1,2,3] ret=func() print(ret) 2.只寫一個return時返回None def func(): a=111 b=[1,2,3] return ret=func() print(ret) 3.return None的時候返回None def func(): a=111 b=[1,2,3] return None ret=func() print(ret) 函數沒有返回值的函數

def func4(): print (1111111) return#結束一個函數的執行 print (1242451) func4()

方法一 def func(): list=['hello','egon',11,22] return list[-1] print(func()) 方法二 def func(): list=['hello','egon',11,22] return list m,n,k,g=func()# print(g) 定義一個列表,返回列表的最后一個值
函數的參數
def fun(s):#參數接受:形式參數,簡稱形參 ''' 計算字符串長度的函數---------函數的功能 參數s:接受要計算的字符串--------參數的信息 return:要計算字符串長度 ---------返回值得信息 ''' length=0 for i in s: length+=1 return length ret=fun('helloword')#參數傳入:實際參數,簡稱實參 print(ret)
實參和形參
形參:是函數定義時候定義的參數
實參:函數調用時候傳進來的參數
傳遞多個參數
可以傳遞多個參數,多個參數之間用逗號隔開
站在參數的角度上,調用函數時傳參數有兩種方式:
1、按照位置傳參數
2、按照關鍵字傳參數
用法:1、位置參數必須在關鍵字參數的前面
2、對於一個參數只能賦值一次

def my_max(a,b):#位置參數:按順序定義參數 if a>b: return a else: return b # 站在傳參的角度上 print(my_max(20,30)) print(my_max(10,20))# 1.按照位置傳參 print(my_max(b=50,a=30))# 2.按照關鍵字傳參 print(my_max(10,b=30))#3.位置和關鍵字傳參混搭
默認參數
用法:為什么要用默認參數?將變化比較小的值設置成默認參數(比如一個班的男生多,女生就幾個,就可以設置個默認值參數)
定義:默認參數可以不傳,不傳的時候用的就是默認值,如果傳會覆蓋默認值。
默認的值是在定義函數的時候就已經確定了的

def stu_info(name,sex = "male"): """打印學生信息函數,由於班中大部分學生都是男生, 所以設置默認參數sex的默認值為'male' """ print(name,sex) stu_info('alex') stu_info('海燕','female')
默認參數缺陷:默認參數是一個可變數據類型

def default_param(a,l=[]): l.append(a) print(l) default_param('alex') default_param('rgon') 輸出:['alex'] ['alex', 'egon']
動態參數
上面說過了參數,如果要給一個函數傳遞參數,而參數又不是確定的,或者我給一個函數傳很多參數,我的形參就要寫很多,很麻煩。這時候就可以使用到動態參數了。
動態參數分為兩種:
1、動態參數接收位置參數(注意:動態參數必須在位置參數的后面,默認值參數必須在動態參數后面)
參數使用*args #args只是一個名字,可以自定義,比如*food
2、動態參數接收關鍵字參數
參數使用**kwargs
最終傳參順序為:
位置參數 > *args > 默認值參數 > **kwargs
按位置傳值多余的參數都由args統一接收,保存成一個元組的形式
按關鍵字傳值接受多個關鍵字參數,由kwargs接收,保存成一個字典的形式

def fun(a,b,*args): sum=a+b for i in args: sum+=i return sum print(fun(1,5,6,4))#輸出1+5+6+4的和

def fun(a,b,**kwargs): print(a,b,kwargs) # 按照關鍵字傳參數 fun(a = 10,b = 20,cccc= 30,dddd = 50)#輸出10 20 {'cccc': 30, 'dddd': 50} def f(a,b,*args,defult=6,**kwargs): #位置參數,*args, 默認參數,**kwargs # print(a,b,args,defult,kwargs) return a,b,args,defult,kwargs #傳參數的時候:必須先按照位置傳參數,再按照關鍵字傳參數 print(f(1,2,7,8,ccc=10,der=5))
# 形參:聚合 def func(*food): #聚合,位置參數 print(food) lst = ['雞蛋','煎餅果子','豆腐'] # 實參:打散 func(*lst) #打散,把list,tuple,set,str 進行迭代打散 # 聚合成關鍵字參數 def func(**food): print(food) dic = {'name':'xiaobai', 'age': '18'} func(**dic) #打散成關鍵字參數
函數注釋
def func(a,b): """ 這里是函數的注釋,先寫一下當前這個函數是干什么的,比如這個函數是處理a和b的和 :param a: 第一個數據 :param b: 第二個數據 :return: 返回的是兩個數的和 """ return a + b #通過print(函數名.__doc__)可以查看函數的注釋說明 print(func.__doc__) print(str.__doc__)
函數小結
1.定義:def 關鍵詞開頭,空格之后接函數名稱和圓括號()。
2.參數:圓括號用來接收參數。若傳入多個參數,參數之間用逗號分割。
參數可以定義多個,也可以不定義。
參數有很多種,如果涉及到多種參數的定義,應始終遵循位置參數、*args、默認參數、**kwargs順序定義。
如上述定義過程中某參數類型缺省,其他參數依舊遵循上述排序
3.注釋:函數的第一行語句應該添加注釋。
4.函數體:函數內容以冒號起始,並且縮進。
5.返回值:return [表達式] 結束函數。不帶表達式的return相當於返回 None
def 函數名(參數1,參數2,*args,默認參數,**kwargs):
"""注釋:函數功能和參數說明"""
函數體
……
return 返回值

函數—閉包
- 什么是閉包?閉包就是內層函數對外層函數的變量的引用,叫閉包
def func(): name = "小白" def inner(): print(name) #在內層函數中調用了外層函數的變量,叫閉包;可以讓一個局部變量常駐內存 inner() func() # 結果: 小白
可以使用__closure__來檢測函數是否是閉包,使用函數名.__closure__返回cell就是閉包,返回None就不是閉包
def func(): name = "小白" def inner(): print(name) #閉包 inner() print(inner.__closure__) # (<cell at 0x000001C307BD7498: str object at 0x000001C307BD69D0>,) func()
- 問題,如何在函數外邊調用內部函數?
def func(): name = "小白" # 內部函數 def inner(): print(name) #閉包 return inner ret = func() # 訪問外部函數,獲取到內部函數的內存地址 ret() # 訪問內部函數
如果多層嵌套呢?很簡單,只需要一層一層的往外層返回就行了
def func1(): def func2(): def func3(): print("哈哈") return func3 return func2 func1()()()
由它我們可以引出閉包的好處,由於我們在外界可以訪問內部函數,那這個時候內部函數訪問的時間和時機就不一定了,因為在外部,可以選擇在任意的時間去訪問內部函數,由於一個函數執行完畢,則這個函數中的變量以及局部命名空間中的內容都將會被銷毀;在閉包中,如果變量被銷毀了,那內部函數將不能正常執行,所以,python規定,如果在內部函數中訪問了外層函數中的變量,那么這個變量將不會消亡,將會常駐在內存中,也就是說,使用閉包,可以保證外層函數中的變量在內存中常駐,這樣有什么好處呢?看一個關於爬蟲的代碼:
from urllib.request import urlopen def but(): content = urlopen("http://news.baidu.com/guonei").read() def get_content(): return content return get_content fn = but() # 這個時候就開始加載校花100的內容 # 后⾯需要⽤到這⾥⾯的內容就不需要在執⾏⾮常耗時的⽹絡連接操作了 content = fn() # 獲取內容 print(content) content2 = fn() # 重新獲取內容 print(content2)
閉包的作用:就是讓一個變量能夠常駐內存,供后面的程序使用
函數—裝飾器
為什么要使用裝飾器呢?
裝飾器的功能:在不修改原函數及其調用方式的情況下對原函數功能進行擴展
裝飾器的本質:就是一個閉包函數,把一個函數當做參數,返回一個替代版的函數,本質上就是一個返回函數的函數。
那么我們先來看一個簡單的裝飾器:實現計算每個函數的執行時間的功能
def timeer(f): def inner(*args, **kwargs): start = time.time() f(*args, **kwargs) end = time.time() ret = end - start print("執行該程序共計用時%s秒"%ret) return inner @timeer def wowo(): time.sleep(0.5) print("to day is a good day") wowo()
以上的裝飾器都是不帶參數的函數,現在裝飾一個帶參數的該怎么辦呢?

def outer(func): def inner(age): if age > 20: age = 20 func(age) return inner # 使用@函數將裝飾器應用到函數 @outer def say(age): print("小明今年已經過了%s歲了" % (age)) say(21)

import time def outer(func): def inner(*args,**kwargs): #添加修改的功能 startTime = time.time() time.sleep(1) print("&&&&&&&&&&&&&&") func(*args,**kwargs) endTime = time.time() print("執行該函數總共花了 %f" %(endTime - startTime)) return inner @outer def say(name,age): #函數的參數理論上是無限制的,但實際上最好不要超過6~7個 print("my name is %s, I am %d years old" %( name,age)) say("lee", 18)

import time def timer(func): def inner(*args,**kwargs): start = time.time() re = func(*args,**kwargs) end=time.time() print(end - start) return re return inner @timer #==> func1 = timer(func1) def jjj(a): print('in jjj and get a:%s'%(a)) return 'fun2 over' jjj('aaaaaa') print(jjj('aaaaaa'))
開放封閉的原則
1.對擴展是開放的
2.對修改是封閉的
裝飾器的固定結構
import time def wrapper(func): # 裝飾器 def inner(*args, **kwargs): '''函數執行之前的內容擴展''' ret = func(*args, **kwargs) '''函數執行之前的內容擴展''' return ret return inner @wrapper # =====>aaa=timmer(aaa) def aaa(): time.sleep(1) print('fdfgdg') aaa()
''' # 固定格式 def outer(func): def inner(*args,**kwargs): #添加修改的功能 pass func(*args,**kwargs) return inner '''
import time def outer(func): def inner(*args,**kwargs): #添加修改的功能 startTime = time.time() time.sleep(1) print("&&&&&&&&&&&&&&") func(*args,**kwargs) endTime = time.time() print("執行該函數總共花了 %f" %(endTime - startTime)) return inner @outer def say(name,age): #函數的參數理論上是無限制的,但實際上最好不要超過6~7個 print("my name is %s, I am %d years old" %( name,age)) say("lee", 18)
帶參數的裝飾器
帶參數的裝飾器:就是給裝飾器傳參
用處:就是當加了很多裝飾器的時候,現在忽然又不想加裝飾器了,想把裝飾器給去掉了,但是那么多的代碼,一個一個的去閑的麻煩,那么,我們可以利用帶參數的裝飾器去裝飾它,這就他就像一個開關一樣,要的時候就調用了,不用的時候就去掉了。給裝飾器里面傳個參數,那么那個語法糖也要帶個括號。在語法糖的括號內傳參。在這里,我們可以用三層嵌套,弄一個標識為去標識。如下面的代碼示例

# 帶參數的裝飾器:(相當於開關)為了給裝飾器傳參 # F=True#為True時就把裝飾器給加上了 F=False#為False時就把裝飾器給去掉了 def outer(flag): def wrapper(func): def inner(*args,**kwargs): if flag: print('before') ret=func(*args,**kwargs) print('after') else: ret = func(*args, **kwargs) return ret return inner return wrapper @outer(F)#@wrapper def hahaha(): print('hahaha') @outer(F) def shuangwaiwai(): print('shuangwaiwai') hahaha() shuangwaiwai()
多個裝飾器裝飾一個函數

def qqqxing(fun): def inner(*args,**kwargs): print('in qqxing: before') ret = fun(*args,**kwargs) print('in qqxing: after') return ret return inner def pipixia(fun): def inner(*args,**kwargs): print('in qqxing: before') ret = fun(*args,**kwargs) print('in qqxing: after') return ret return inner @qqqxing @pipixia def dapangxie(): print('餓了嗎') dapangxie() ''' @qqqxing和@pipixia的執行順序:先執行qqqxing里面的 print('in qqxing: before'),然后跳到了pipixia里面的 print('in qqxing: before') ret = fun(*args,**kwargs) print('in qqxing: after'),完了又回到了qqqxing里面的 print('in qqxing: after')。所以就如下面的運行結果截圖一樣 '''
上例代碼的運行結果截圖
統計多少個函數被裝飾了的小應用

# 統計多少個函數被我裝飾了 l=[] def wrapper(fun): l.append(fun)#統計當前程序中有多少個函數被裝飾了 def inner(*args,**kwargs): # l.append(fun)#統計本次程序執行有多少個帶裝飾器的函數被調用了 ret = fun(*args,**kwargs) return ret return inner @wrapper def f1(): print('in f1') @wrapper def f2(): print('in f2') @wrapper def f3(): print('in f3') print(l)
函數—迭代器
str,list,tuple,dict,set為什么我們可以稱它們為可迭代對象?因為它們都遵循了可迭代協議,什么是可迭代協議?首先先看一段錯誤代碼:
# 對的 s = "Welcom" for i in s: print(i) # 錯的 for i in 123: print(i) # 結果: .... for i in 123: TypeError: 'int' object is not iterable
上面報錯信息中有這樣一句話. 'int' object is not iterable . 翻譯過來就是整數類型對象是不可迭代的,iterable表示可迭代的,表示可迭代協議;如果判斷數據類型是否符合可迭代協議?可以通過dir函數來查看類中定義好的所有方法。
s = "我的哈哈哈" print(dir(s)) # 可以打印對象中的⽅法和函數 print(dir(str)) # 也可以打印類中聲明的⽅法和函數
在打印結果中,尋找__iter__ 如果能夠找到,那么這個類的對象就是一個可迭代對象
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
'__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
'__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs',
'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric',
'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind',
'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
可以發現字符串中可以找到__iter__,繼續看一下list,tuple,dict,set等
print("__iter__" in dir(list)) print("__iter__" in dir(tuple)) print("__iter__" in dir(dict)) print("__iter__" in dir(set)) # 結果: True True True True
所有的以上數據類型中都有一個函數__iter__() ;所以,所有包含了__iter__()的數據類型都是可迭代的數據類型 Iterable
這是查看一個對象是否是可迭代對象的第一種方法,還可以通過 isinstence() 函數來查看一個對象是什么類型
lst = [1, 2, 3] from collections import Iterable # 可迭代的 from collections import Iterator # 迭代器 # isinstence(對象, 類型) 判斷xx對象是否是xxx類型的 print(isinstance(lst, Iterable)) print(isinstance(lst, Iterator)) # it = lst.__iter__() print(isinstance(it, Iterable)) # 判斷是否是可迭代的 迭代器一定是可迭代的 print(isinstance(it, Iterator)) # 迭代器里面一定有__next__(), __iter__() print("__iter__" in dir(lst)) # 確定是一個可迭代的 print("__next__" in dir(lst)) # 確定不是一個迭代器
綜上,可以看出如果對象中有__iter__函數,那么我們認為這個對象遵守了可迭代協議,就可以獲取到相應的迭代器,這里的__iter__是幫助我們獲取到對象的迭代器,我們使用迭代器中的__next__()來獲取到一個迭代器中的元素。
for循環的工作原理到底是什么?繼續看代碼
s = "來了就是深圳人" sl = s.__iter__() # 獲取迭代器 # 使用迭代器獲取每一個元素 print(sl.__next__()) # 來 print(sl.__next__()) # 了 print(sl.__next__()) # 就 print(sl.__next__()) # 是 print(sl.__next__()) # 深 print(sl.__next__()) # 圳 print(sl.__next__()) # 人 print(sl.__next__()) # StopIteration # for循環機制 for i in s: print(i) # 使⽤while循環+迭代器來模擬for循環 while True: try: sle = s.__iter__() print(sle.__next__()) except StopIteration: break
總結:
- Iterable:可迭代對象,內部包含__iter__()函數
- Iterator:迭代器,內部包含__iter__()函數,同時包含__next__()函數
- 迭代器的特點:1、節省內存;2、惰性機制;3、不能反復,只能向下執行
我們可以把要迭代的內容當成子彈,然后呢,獲取到迭代器__iter__(),就把子彈都裝在彈夾中,然后發射就是__next__()把每一個子彈(元素)打出來,也就是說,for循環的時候,一開始的時候是__iter__()來獲取迭代器,后面每次獲取元素都是通過__next__()來完成的。當程序遇到StopIteration將結束循環
函數—遞歸函數
遞歸的定義
需求,輸入一個數(大於等於1),求1+2+3+……+n的和 # 普通函數寫法: def sun1(n): sum = 0 for x in range(1,n + 1): sum += x return sum # 遞歸來寫 ''' 1+2+3+4+5 sum2(1) + 0 = sum2(1) sum2(1) + 2 = sum2(2) sum2(2) + 3 = sum2(3) sum2(3) + 4 = sum2(4) sum2(4) + 5 = sum2(5) 5 + sum2(4) 5 + 4 + sum2(3) 5 + 4 + 3 + sum2(2) 5 + 4 + 3 + 2 + sum2(1) 5 + 4 + 3 + 2 + 1 ''' def sum2(n): if n == 1: return 1 else: return n * sum2(n - 1) res = sum2(5) print(res)
1.什么是遞歸:在一個函數里在調用這個函數本身
2.最大遞歸層數做了一個限制:997,但是也可以自己限制
def foo(n): print(n) n+=1 foo(n) foo(1)
3.最大層數限制是python默認的,可以做修改,但是不建議你修改。(因為如果用997層遞歸都沒有解決的問題要么是不適合使用遞歸來解決問題,要么就是你的代碼太爛了)

import sys sys.setrecursionlimit(10000000)#修改遞歸層數 n=0 def f(): global n n+=1 print(n) f() f()
我們可以通過以上代碼,導入sys模塊的方式來修改遞歸的最大深度。
sys模塊:所有和python相關的設置和方法
4.結束遞歸的標志:return
5.遞歸解決的問題就是通過參數,來控制每一次調用縮小計算的規模
6.使用場景:數據的規模在減少,但是解決問題的思路沒有改變
7.很多排序算法會用到遞歸
遞歸小應用
1.下面我們來猜一下小明的年齡
小明是新來的同學,麗麗問他多少歲了。
他說:我不告訴你,但是我比滔滔大兩歲。
滔滔說:我也不告訴你,我比曉曉大兩歲
曉曉說:我也不告訴你,我比小星大兩歲
小星也沒有告訴他說:我比小華大兩歲
最后小華說,我告訴你,我今年18歲了
這個怎么辦呢?當然,有人會說,這個很簡單啊,知道小華的,就會知道小星的,知道小星的就會知道曉曉的,以此類推,就會知道小明的年齡啦。這個過程已經非常接近遞歸的思想了。
小華 | 18+2 |
小星 | 20+2 |
曉曉 | 22+2 |
滔滔 | 24+2 |
小明 | 26+2 |
上面的圖我們可以用個序號來表示吧
age(5) = age(4)+2 age(4) = age(3) + 2 age(3) = age(2) + 2 age(2) = age(1) + 2 age(1) = 18
那么代碼該怎么寫呢?
def age(n): if n == 1: return 18 else: return age(n - 1) + 2 ret=age(6) print(ret)
2.一個數,除2直到不能整除2

def cal(num): if num%2==0:#先判斷能不能整除 num=num//2 return cal(num) else: return num print(cal(8))
3.如果一個數可以整除2,就整除,不能整除就*3+1

def func(num): print(num) if num==1: return if num%2==0: num=num//2 else: num=num*3+1 func(num) func(5)
三級菜單
menu = {
'北京': {
'海淀': {
'五道口': {
'soho': {},
'網易': {},
'google': {}
},
'中關村': {
'愛奇藝': {},
'汽車之家': {},
'youku': {},
},
'上地': {
'百度': {},
},
},
'昌平': {
'沙河': {
'老男孩': {},
'北航': {},
},
'天通苑': {},
'回龍觀': {},
},
'朝陽': {},
'東城': {},
},
'上海': {
'閔行': {
"人民廣場": {
'炸雞店': {}
}
},
'閘北': {
'火車戰': {
'攜程': {}
}
},
'浦東': {},
},
'山東': {},
}

def threeLM(menu): while True: for key in menu:#循環字典的key,打印出北京,上海,山東 print(key) name=input('>>>:').strip() if name=='back' or name=='quit':#如果輸入back,就返回上一層。如果輸入quit就退出 return name #返回的name的給了ret if name in menu: ret=threeLM(menu[name]) if ret=='quit':return 'quit'#如果返回的是quit,就直接return quit 了,就退出了 threeLM() # print(threeLM(menu))#print打印了就返回出quit了,threeLM()沒有打印就直接退出了
二分查找算法
從這個列表中找到55的位置lst = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88]
這就是二分查找,從上面的列表中可以觀察到,這個列表是從小到大依次遞增的有序列表。
按照上面的圖就可以實現查找了。
- 普通方法:
lst = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88] left = 0 # 列表的第一個元素 right = len(lst) - 1 # 列表的最后一個元素 n = 1 # 要查找的數 while left < right: middle = (left + right) // 2 # 列表中間那個數 if n < lst[middle]: right = middle elif n > lst[middle]: left = middle else: print("找到了,在第%s位"%(middle)) break else: print("沒找到")
- 普通遞歸版本二分法:
lst = [2,3,5,10,15,16,18,22,26,30,32,35,41,42,43,55,56,66,67,69,72,76,82,83,88] def binary_search(num, left, right): if left <= right: middle = (right+left) // 2 if num > lst[middle]: left = middle + 1 elif num < lst[middle]: right = middle - 1 else: return "找到了,在第%s位"%(middle) return binary_search(num,left,right) # 這個return必須加,否則接收到的永遠是None. else: return -1 print(binary_search(6, 0, len(lst)-1))

l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] def find(l,aim): mid=len(l)//2#取中間值,//長度取整(取出來的是索引) if l[mid]>aim:#判斷中間值和要找的那個值的大小關系 new_l=l[:mid]#顧頭不顧尾 return find(new_l,aim)#遞歸算法中在每次函數調用的時候在前面加return elif l[mid]<aim: new_l=l[mid+1:] return find(new_l,aim) else: return l[mid] print(find(l,66))

l = [2, 3, 5, 10, 15, 16, 18, 22, 26, 30, 32, 35, 41, 42, 43, 55, 56, 66, 67, 69, 72, 76, 82, 83, 88] def func(l, aim,start = 0,end = len(l)-1): mid = (start+end)//2#求中間的數 if not l[start:end+1]:#如果你要找的數不在里面,就return'你查找的數字不在這個列表里面' return '你查找的數字不在這個列表里面' elif aim > l[mid]: return func(l,aim,mid+1,end) elif aim < l[mid]: return func(l,aim,start,mid-1) elif aim == l[mid]: print("bingo") return mid index = func(l,55) print(index) # print(func(l,41))
函數—函數嵌套及作用域
三元運算符
if條件成立的結果 if 條件 else 條件不成立的結果
列如:
a = 20 b = 10 c = a if a>b else b print(c)
命名空間
- 全局命名空間:創建的存儲“變量名與值的關系”的空間叫做全局命名空間
- 局部命名空間:在函數的運行中開辟的臨時的空間叫做局部命名空間
- 內置命名空間:內置命名空間中存放了python解釋器為我們提供的名字:input,print,str,list,tuple...它們都是我們熟悉的,拿過來就可以用的方法。
三種命名空間之間的加載順序和取值順序:
加載順序:內置(程序運行前加載)-->全局(從上到下順序加載進來的)-->局部(調用的時候加載)--->內置
取值:在局部調用:局部命名空間--->全局命名空間--->內置命名空間
站在全局范圍找:全局----內置----局部
使用:
全局不能使用局部的
局部的可以使用全局的
作用域
1.命令空間和作用域是分不開的
2.作用域分為兩種:
全局作用域:全局命名空間與內置命名空間的名字都屬於全局范圍
在整個文件的任意位置都能被引用,全局有效
局部作用域:局部命名空間,只能在局部范圍內生效
3.站在全局看:
使用名字的時候:如果全局有,用全局的
如果全局沒有,用內置的
4.為什么要有作用域?
為了函數內的變量不會影響到全局
5.globals方法:查看全局作用域的名字【print(globals())】
locals方法:查看局部作用域的名字【print(locals())】

def func(): a = 12 b = 20 print(locals()) print(globals()) func()
站在全局看,globals is locals
global關鍵字:強制轉換為全局變量

# x=1 # def foo(): # global x #強制轉換x為全局變量 # x=10000000000 # print(x) # foo() # print(x) # 這個方法盡量能少用就少用
nonlocal讓內部函數中的變量在上一層函數中生效,外部必須有

# x=1 # def f1(): # x=2 # def f2(): # # x=3 # def f3(): # # global x#修改全局的 # nonlocal x#修改局部的(當用nonlocal時,修改x=3為x=100000000,當x=3不存在時,修改x=2為100000000 ) # # 必須在函數內部 # x=10000000000 # f3() # print('f2內的打印',x) # f2() # print('f1內的打印', x) # f1() # # print(x)
函數的嵌套定義

def animal(): def tiger(): print('nark') print('eat') tiger() animal()
作用域鏈

x=1 def heihei(): x='h' def inner(): x='il' def inner2(): print(x) inner2() inner() heihei()
函數名的本質:就是函數的內存地址

def func(): print('func') print(func)#指向了函數的內存地址
函數名可以用作函數的參數

def func(): print('func') def func2(f): f() print('func2') func2(func)
函數名可以作為函數的返回值

return說明1 def func(): def func2(): print('func2') return func2 f2=func() f2() #func2=func() #func2() 2. def f1(x): print(x) return '123' def f2(): ret = f1('s') #f2調用f1函數 print(ret) f2() 3. def func(): def func2(): return 'a' return func2 #函數名作為返回值 func2=func() print(func2())
函數—匿名函數
匿名函數介紹
不使用def這樣的語句來定義函數,使用lambda來創建匿名函數;也叫lambda表達式 1.匿名函數的核心:一些簡單的需要用函數去解決的問題,匿名函數的函數體只有一行 2.參數可以有多個,用逗號隔開 3.返回值和正常的函數一樣可以是任意的數據類型 4.lambda函數有自己的命名空間,且不能訪問自由參數列表之外的或全局命名空間的參數
匿名函數練習
請把下面的函數轉換成匿名函數 def add(x,y) return x+y add() 結果: sum1=lambda x,y:x+y print(sum1(5,8))

dic = {'k1':50,'k2':80,'k3':90} # func= lambda k:dic[k] # print(max(dic,key=func)) print(max(dic,key = lambda k:dic[k]))#上面兩句就相當於下面一句

3.map方法 l=[1,2,3,4] # def func(x): # return x*x # print(list(map(func,l))) print(list(map(lambda x:x*x,l)))

l=[15,24,31,14] # def func(x): # return x>20 # print(list(filter(func,l))) print(list(filter(lambda x:x>20,l)))
# 現有兩個元組(('a'),('b')),(('c'),('d')), 請使用python中匿名函數生成列表[{'a':'c'},{'b':'d'}] # 方法一 t1=(('a'),('b')) t2=(('c'),('d')) # print(list(zip(t1,t2))) print(list(map(lambda t:{t[0],t[1]},zip(t1,t2)))) # 方法二 print(list([{i,j} for i,j in zip(t1,t2)])) #方法三 func = lambda t1,t2:[{i,j} for i,j in zip(t1,t2)] ret = func(t1,t2) print(ret)
函數—生成器與推導式
生成器
什么是生成器?
生成器實質就是迭代器。
在Python中有三種方式來獲取生成器:
- 1、通過生成器函數
- 2、通過各種推導式來實現生成器
- 3、通過數據的轉換也可以獲取生成器
首先看一個很簡單的函數:
def func(): print("123") return 456 ret = func() print(ret) # 結果: 123 456
將函數中的return換成yield就是生成器
def func(): print("123") yield 456 ret = func() print(ret) # 結果: <generator object func at 0x0000023853646A40>
運行的結果和上面不一樣,為什么呢?由於函數中存在了yield,那么這個函數就是一個生成器函數,這個時候,再執行這個函數的時候,就不再是函數的執行了,而是獲取這個生成器;如何使用呢?想想迭代器.生成器的本質就是迭代器,所以,可以執行__next__()來執行以下生成器
def func(): print("123") yield 456 ret = func() # 這個時候函數不會執⾏. ⽽是獲取到⽣成器 gen = ret.__next__() # 這個時候函數才會執⾏. yield的作⽤和return⼀樣. 也是返回數據 print(gen) # 結果: 123 456
那么可以看到,yield和return的效果是不一樣的,有什么區別呢?yield是分段來執行一個函數,return呢?直接停止函數
def func(): print("123") yield 456 print("111") yield 222 gener = func() ret1 = gener.__next__() print(ret1) ret2 = gener.__next__() print(ret2) ret3 = gener.__next__() # 最后⼀個yield執⾏完畢. 再次__next__()程序報錯, 也就是說. 和return⽆關了. print(ret3) # 結果: 123 Traceback (most recent call last): 456 File "E:/練習/函數/生成器.py", line 47, in <module> 111 222 ret3 = gener.__next__() StopIteration
當程序運行完最后一個yield,那么后面繼續進行__next__()程序會報錯
生成器的作用示例:
# 我們來看這樣⼀個需求. 麻花藤向馬榮訂購10000套衣服. 馬榮就比較實在. 直接造出來10000套衣服 def cloth(): lis = [] for i in range(0, 10001): lis.append("衣服"+str(i)) return lis cl = cloth() # 但是呢, 問題來了. 麻花藤現在沒有這么多人啊. ⼀次性給這么多. 往哪⾥放啊. 很尷尬啊. 最好的效果是什么樣呢? 麻花藤要1套. 馬榮給麻花藤1套. ⼀共10000套. 是不是最完美的. def cloth(): for i in range(0,10001): yield "衣服"+str(i) cl = cloth() print(cl.__next__()) print(cl.__next__()) print(cl.__next__()) print(cl.__next__())
第一種是直接一次性全部拿出來,會很占內存,第二種使用生成器,一次就一個,用多少生成多少,生成器是一個一個的指向下一個,不會回去,__next__()到哪兒,指針就到哪兒。下一次繼續獲取指針指向的值
生成器send方法:
send和__next__()一樣可以讓生成器執行到下一個yield。
def eat(): print("我吃什么啊") a = yield "饅頭" print("a=",a) b = yield "⼤餅" print("b=",b) c = yield "⾲菜盒⼦" print("c=",c) yield "GAME OVER" gen = eat() # 獲取⽣成器 ret1 = gen.__next__() print(ret1) ret2 = gen.send("胡辣湯") print(ret2) ret3 = gen.send("狗糧") print(ret3) ret4 = gen.send("貓糧") print(ret4)
send和__next__()區別:
- send和next()都是讓生成器向下走一次
- send可以給上一個yield的位置傳遞值,不能給最后一個yield傳遞值(會報錯);在第一次執行生成器代碼的時候不能使用send()
生成器可以使用for循環來循環獲取內部的元素:
def func(): print(111) yield 222 print(333) yield 444 print(555) yield 666 gen = func() for i in gen: print(i) 結果: 111 222 333 444 555 666
列表推導式
- 語法:[ 最終結果(變量) for 變量 in 可迭代對象 if 條件篩選 ]
- 示例:從1~100的數,寫入到列表
# 普通寫法 lis = [] for i in range(1,101): lis.append(i) print(lis) # 列表推導式寫法 list = [ i for i in range(1, 101) ] print(list)
# 1. 打印100以內的偶數 list = [ i for i in range(1,101) if i%2==0 ] print(list) # 2. 獲取1-100內能被3整除的數 num = [ i for i in range(1,101) if i%3 == 0 ] print(num) # 3. 100以內能被3整除的數的平方 num = [ i*i for i in range(1,101) if i%3 == 0 ] print(num) # 4. 尋找名字中帶有兩個e的⼈的名字 names = [['Tom', 'Billy', 'Jefferson' , 'Andrew' , 'Wesley' , 'Steven' , 'Joe'],['Alice', 'Jill' , 'Ana', 'Wendy', 'Jennifer', 'Sherry' , 'Eva']] name = [name for nameLs in names for name in nameLs if name.count("e") == 2 ] print(name)
生成器推導式
- 語法:( 結果 for 變量 in 可迭代對象 if 條件篩選 )
生成器推導式和列表推導式的語法基本上時一樣的,只是把[]替換成()
gen = (i for i in range(10)) print(gen) # 結果: <generator object <genexpr> at 0x0000020C25196BA0>
打印的結果就是一個生成器,可以使用for循環來循環這個生成器
gen = (i for i in range(10)) for num in gen: print(num)
生成器也可以進行篩選
# 1. 獲取1-100內能被3整除的數 gen = (i for i in range(1,100) if i % 3 == 0) for num in gen: print(num) # 2. 100以內能被3整除的數的平⽅ gen = (i * i for i in range(100) if i % 3 == 0) for num in gen: print(num) # 3. 尋找名字中帶有兩個e的⼈的名字 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] # 推導式 gen = (name for first in names for name in first if name.count("e") >= 2) for name in gen: print(name)
生成器與列表推導式的區別:
- 列表推導式比較耗內存,一次性加載,生成器推導式幾乎不占內存,使用的時候才分配和使用內存
- 得到的值不一樣,列表推導式得到的是一個列表,生成器推導式獲取的是一個生成器
- 比如:
# 同樣⼀籃⼦雞蛋. 列表推導式: 直接拿到⼀籃⼦雞蛋. ⽣成器表達式: 拿到⼀個老⺟雞. 需要雞蛋就給你下雞蛋. # ⽣成器的惰性機制: ⽣成器只有在訪問的時候才取值. 說⽩了. 你找他要他才給你值. 不找他要. 他是不會執⾏的.
def func(): print(111) yield 222 g = func() # ⽣成器g g1 = (i for i in g) # ⽣成器g1. 但是g1的數據來源於g g2 = (i for i in g1) # ⽣成器g2. 來源g1 print(list(g)) # 獲取g中的數據. 這時func()才會被執⾏. 打印111.獲取到222. g完畢. print(list(g1)) # 獲取g1中的數據. g1的數據來源是g. 但是g已經取完了. g1 也就沒有數據了 print(list(g2)) # 和g1同理
字典推導式
- 語法:{ 結果(k:v) for 變量 in 可迭代對象 if 條件篩選 }
# 1. 把字典中的key和value互換 dic = {'a': 1, 'b': '2'} new_dict = { dic[key]:key for key in dic } print(new_dict) # 2. 在以下list中. 從lst1中獲取的數據和lst2中相對應的位置的數據組成⼀個新字典 lst1 = ['jay', 'jj', 'sylar'] lst2 = ['周傑倫', '林俊傑', '邱彥濤'] new_dict = { lst1[i]:lst2[i] for i in range(len(lst1)) } print(new_dict) # 3. 合並大小寫對應的value值,將k統一成小寫 mcase = {'a':10,'b':34,'A':7} res = {i.lower():mcase.get(i.lower(),0)+mcase.get(i.upper(),0) for i in mcase} print(res)
集合推導式
- 語法:{ 結果(k) for 變量 in 可迭代對象 if 條件篩選 }
集合推導式可以幫我們直接生成一個集合,集合的特點:無序,不重復,所以集合推導式自帶去重功能
# 1. 絕對值去重 lis = [1, 2, -4, -8, -4, 3, 2] s = [abs(i) for i in lis] print(s) # 2. 求每個值的平凡 l=[5,-5,1,2,5] print({i**2 for i in l})
友情提示:惰性機制,不到最后不會拿值
# 一道有意思的題
def add(a, b): return a + b def test(): for i in range(4): yield i g = test() for n in [2,10]: g = (add(n, i) for i in g) print(list(g))
函數—內置函數

# 內置函數操作 #!usr/bin/env python # -*- coding:utf-8 -*- # 1.locals()和globals() def func(): x=1 y=2 print(locals()) print(globals()) func() # 2.eval,exec,和compile print(123) "print(456)"#字符串 eval("print(456)")#吧字符串轉換成python代碼去執行(有返回值) exec("print(7889)")#吧字符串轉換成python代碼去執行(無返回值) num = eval('4+5+6')#執行了,有返回值 print(num) num = exec('4+5+6')#執行了,沒有返回值 print(num) # compile#做編譯 com=compile('1+2+3','',mode = 'eval')#節省時間 print(eval(com)) print(eval('1+2+3'))#這句效果和上面的compile()效果一樣 # 3.print print('123',end='')#不換行 print('456',end='') print(1,2,3) print(1,2,3,4,5,6,sep=',') # print()函數的小例子 import time import sys for i in range(0,101,2): time.sleep(0.1) char_num = i//2 #打印多少個# per_str = '%s%% : %s\n' % (i, '*' * char_num) if i == 100 else '\r%s%% : %s'%(i,'*'*char_num) print(per_str,end='', file=sys.stdout, flush=True) import sys for i in range(0, 101, 2): time.sleep(0.1) char_num = i // 2 per_str = '\r%s%% : %s' % (i, '*' * char_num) print(per_str, file=sys.stdout, flush=True) # 4.input() # 5.type() # 6.hash print(hash('asdsffd'))#一開始幾個都是不變的,,然后重新運行一次就變了 print(hash('asdsffd')) print(hash('asdsffd')) print(hash('asdsffd')) print(hash('asdsffd')) print(hash((1,2,3,4))) # 7.open # r,w,a,r+,w+,a+(都可以加b) f=open('tmp','r+')#r+打開文件 print(f.read(3))#如果讀了在寫,追加 f.seek(5)#如果seek指定了光標的位置,就從該位置開始覆蓋這寫 f.write('aaaaaa')#如果直接寫,從頭覆蓋 f.close() # 8.__import__() import os import sys import time # 9.callable:查看能不能調用 print(callable(123))#數字不能調用結果就是False print(callable(open))#函數可以調用就返回True # 10.dir 查看數據類型的方法 print(dir(__builtins__))#看着報錯,,但其實不報錯 print(dir(int)) print(dir(list)) print(dir(0))#和int一樣 print(set(dir(list))-set(dir(tuple))) # 11.int num1=int(123) num2=int(12.3)#強制轉換成int類型 print(num1,num2) # 12.取商/余 print(divmod(7,3)) # 13.計算最小值 print(min(1,2,3,4)) print(min([5,6])) # 13.計算最大值 print(max(1,2,3,4)) print(max([5,6])) # 14.sum求和 print(sum(1,3,4,5))#出錯了,參數是序列,散列不行 print(sum([5,6])) print(sum((1,2,3,4))) # 以下的兩個方式是一樣的 print(1+2) print(int(1).__add__(2)) # 15.round精確度 print(round(3.1415926,2))#保留兩位 # 16.pow()冪運算 print(pow(2,3)) print(2**3) # 17.和數據結構相關的 # 1.reversed()順序的反轉 l=[1,2,3,4] print(list(reversed(l)))#是生成了一個新的列表,沒有改變原來的列表(以后能不用reversed就不用reversed,用reverse) # l.reverse()#在現在的列表的基礎上修改了,修改的是原來的列表 print(l) # 2.slice切片 # 3.format()#除了格式化以外的作業 print(format('test','<20')) print(format('test','>40')) print(format('test','^40')) # 4.bytes s='你好' sb=bytes(s,encoding='utf-8') print(sb) print(sb.decode('utf-8')) sb2=bytearray(s,encoding='utf-8') sb2[0]=229 #修改 了解就好 print(sb2.decode('utf-8')) print(sb2) print(sb2[0]) # 5.repr print(repr('1234')) print(repr(1234)) print('name:%r'%('egon'))#你怎么傳進去的就按什么格式打印出來了 # 6.set和frozenset(不可變的集合)就像list和tuple # 7.enumerate l=['a','b'] for i in enumerate(l): print(i) for i ,j in enumerate(l): print(i,j) # 8.all和any print(all([1,2,3])) print(all([0,2,3]))#因為0是False print(any([1,2,3])) print(any([0,2,3])) # 9.zip() l=[1,2,3] l2=[4,5,6,7,8] print(zip(l,l2)) print(list(zip(l,l2))) l3={'k':'v'} print(list(zip(l,l3))) print(ret) 常見內置函數的操作
sorted() 排序
排序函數:sorted
- 語法:sorted(lterable, key=None, reverse=False)
Iterable: 可迭代對象 key: 排序規則(排序函數), 在sorted內部會將可迭代對象中的每⼀個元素傳遞給這個函 數的參數. 根據函數運算的結果進⾏排序 reverse: 是否是倒敘. True: 倒敘, False: 正序
lst = [1,5,3,4,6] lst2 = sorted(lst) print(lst) # 原列表不會改變 print(lst2) # 返回的新列表是經過排序的 dic = {1:'A', 3:'C', 2:'B'} print(sorted(dic)) # 如果是字典. 則返回排序過后的key
- 和函數組合使用:
# 按照字符串的長度進行排序 lst = ['小白', '小白1', '小白21','小白123', '小黑1','小黑3232'] # 計算字符串長度 def func(lst): return len(lst) ll = sorted(lst, key=func) print(ll)
- 和lambda組合使用:
# 按照字符串的長度進行排序 lst = ['小白', '小白1', '小白21','小白123', '小黑1','小黑3232'] # 計算字符串長度 def func(lst): return len(lst) print(sorted(lst, key=lambda s:len(s))
# 按照年齡排序 lst = [ {"name":"小白", "age":14}, {"name":"小黑", "age":25}, {"name":"小黃", "age":11}, {"name":"小藍", "age":30}, {"name":"小粉", "age":8}, ] def func(dic): return dic["age"] ll = sorted(lst, key=func) print(ll) # 使用匿名函數: ll = sorted(lst, key=lambda dic:dic["age"]) print(ll)
filter() 篩選
- 語法:filter(function, lterable)
function: ⽤來篩選的函數. 在filter中會⾃動的把iterable中的元素傳遞給function. 然后根據function返回的True或者False來判斷是否保留此項數據 terable: 可迭代對象
lst = [1,2,3,4,5,6,7] ll = filter(lambda x: x%2==0, lst) # 篩選所有的偶數 print(ll) # 得到的是一個迭代器 print(list(ll))
# 篩選年齡大於20歲的 lst = [ {"name":"小白", "age":14}, {"name":"小黑", "age":25}, {"name":"小黃", "age":11}, {"name":"小藍", "age":30}, {"name":"小粉", "age":8}, ] fl = filter(lambda e:e["age"]>20, lst) print(list(fl))
map() 映射
- 語法:map(function, iterable)
可以對可迭代對象中的每⼀個元素進⾏映射. 分別取執⾏function
- 計算列表中每個元素的平方,返回新列表
def func(e): return e * e mp = map(func,[1,3,5,7,9]) print(list(mp))
改寫成lambda
mp = map(lambda e:e*e, [1,3,5,7,9]) print(list(mp))
- 計算兩個列表中兩個相同位置的數據的和
lst1 = [1, 2, 3, 4, 5] lst2 = [2, 4, 6, 8, 10] print(list(map(lambda x,y:x+y, lst1,lst2)))
內置參數詳解 https://docs.python.org/3/library/functions.html?highlight=built#ascii
內置函數流程圖 https://www.processon.com/mindmap/5be92ca8e4b0128076976f13