第五篇 匿名函式、內建函式、import的使用、包的使用

weixin_34185560發表於2018-06-14

一、函式的遞迴呼叫

#函式遞迴呼叫:在呼叫一個函式的過程中直接或間接地呼叫該函式本身,稱之為函式的遞迴呼叫,也就是函式的巢狀呼叫時呼叫自己本身
import sys
print(sys.getrecursionlimit()) #列印發現預設遞迴的層級是1000
sys.setrecursionlimit(2000) #可以修改遞迴的層級到2000
n=1
def func1():
    global n
    print('from func1',n)
    n+=1
    func1()
func1()

#間接地呼叫自身也叫遞迴呼叫
def func():
    print('from func')
    bar()

def bar():
    func()
func()

#遞迴分為兩個重要的階段:遞推+回溯
# age(5)=age(4)+2
# age(4)=age(3)+2
# age(3)=age(2)+2
# age(2)=age(1)+2
# age(1)=18
# n!=1 #age(n)=age(n-1)+2
# n=1 #age(n)=18

上面的例子可以用函式的遞迴呼叫表示
def age(n):
    if n == 1:
        res=18
        return res
    res=age(n-1)+2 #res=age(4)+2
    return res
print(age(5))

#總結遞迴呼叫:
#1:進入下一次遞迴時,問題的規模必須降低
#2:遞迴呼叫必須要有一個明確的結束條件
#3:在python中沒有尾遞迴優化,遞迴呼叫的效率就是不高

#練習,取出列表中的每一元素
l=[1,2,[3,[4,[5,[6,7,[8,9,[10,[11,[12,]]]]]]]]]
def get(l):
    for item in l:
        if isinstance(item,list): #等於if type(item) is list,判斷item是否是列表型別
            get(item) #如果是列表就遞迴呼叫
        else:
            print(item)
get(l)

二、二分法

判斷一個數字是否在這個列表內,如果在就列印find it
l=[1,2,10,30,33,99,101,200,301,402] #從小到大排列的數字列表,如果不是按照順序排列的不能用二分法
def get(num,l):
    print(l)
    if len(l) > 0: #列表不為空,則證明還有值是可以執行二分法邏輯的
        mid=len(l)//2 #地板除只取整數部分
        if num > l[mid]:
            #in the right
            l=l[mid+1:] #對列表進行切分,取列表的右半部分
        elif num < l[mid]:
            #in the left
            l=l[:mid]
        else:  #如果正好是中間的值
            print('find it')
            return
        get(num,l) #使用函式的遞迴呼叫
    else: #列表為空,則證明根本不存在要查詢的值
        print('not exists')
        return
get(403,l)

三、匿名函式

#匿名函式即沒有繫結名字的函式,沒有繫結名字,意味著只能用一次就會被回收
#所以說匿名函式的應用場景就是:某個功能只用一次就結束了
def f1(n):
    return n**2
print(f1(3)) #列印函式的返回值
上面的程式碼可以寫成匿名函式
f2=lambda n:n**2 #n:n**2第一個n是上面函式中的引數,匿名函式只能寫到一行,並且把冒號後面的當做匿名函式的返回值,不用加return
print(f2(3))

取出最大的工資
salaries={
    'egon':3000,
    'alex':100000000,
    'wupeiqi':10000,
    'yuanhao':2000
}
print(max(salaries)) #預設比較的是key
print(max(salaries.values())) #要取value需這樣寫
執行結果:
Yuanhao  
100000000

#拉鍊函式
l1=[1,2,3]
s1='hello'
res=zip(l1,s1)
print(list(res)) #把res的值放到列表中
執行結果:
[(1, 'h'), (2, 'e'), (3, 'l')]
發現拉鍊函式是把l1和s1中的元素按照順序合起來放在元組中
#補充:序列型別的比較,先比較第一個,如果第一個一樣大才比較第二個,有索引的叫做序列型別
兩個元組比較大小
t1=(3333,'a')
t2=(2,'a','b','c')
print(t1 < t2)
執行結果為False

把工資最大的人名取出來
salaries={
    'egon':3000,
    'alex':100000000,
    'wupeiqi':10000,
    'yuanhao':2000
}
res=zip(salaries.values(),salaries.keys())
#print(max(res)) #發現把工資最大的和人名放到一個元組中
print(max(res)[1]) #要取出人名就取元組中索引為1的元素

#max與lambda的結合
salaries={
    'egon':3000,
    'alex':100000000,
    'wupeiqi':10000,
    'yuanhao':2000
}
def f1(k):
    return salaries[k]
print(max(salaries,key=f1))  #表示取最大值,不是鍵而是值,key=什麼表示要取的什麼,因為key=f1函式的返回值,表示要取value的最大值
執行結果為:alex

上面程式碼可以用匿名函式簡寫為
print(max(salaries,key=lambda k:salaries[k])) #表示取的是lambda匿名函式的最大值,此匿名函式的返回值為salaries[k],如果k=alex,就是比較alex的value是不是最大的,不是就繼續比較
print(min(salaries,key=lambda k:salaries[k]))
print(sorted(salaries,key=lambda k:salaries[k])) #表示排序,按照lambda函式的返回值排序,也就是按照工資排序
print(sorted(salaries,key=lambda k:salaries[k],reverse=True))

執行結果為:
Alex    
Yuanhao
['yuanhao', 'egon', 'wupeiqi', 'alex']
['alex', 'wupeiqi', 'egon', 'yuanhao']

匿名函式其他常用的其他用途
#map,reduce,filter
map表示對映,在列表中所有的人名後面加_SB結尾
l=['alex','wupeiqi','yuanhao','huanghongwei']
print(list(map(lambda x:x+'_SB',l)))
執行結果:
['alex_SB', 'wupeiqi_SB', 'yuanhao_SB', 'huanghongwei_SB']
reduce表示合併:計算1到100的和
from functools import reduce
print(reduce(lambda x,y:x+y,range(1,101))) #,匿名函式有x,y兩個引數,預設初始值是0,首先取初始值0,然後在range中取第一個元素1,進行運算,運算的結果再取一個元素進行加運算,
print(reduce(lambda x,y:x+y,range(1,101),100)) #如果初始值是100,就會100+1,然後101+2,103+3這樣運算
執行結果:
5050
5150
filter:過濾,把SB結尾的過濾出來
l=['alex_SB','wupeiqi_SB','yuuanhao_SB','hhw','egon']
res=filter(lambda name:name.endswith('SB'),l)
print(list(res))

四、內建函式

#其他內建函式
print(abs(-1)) #求-1絕對值為+1

print(all([1,'a',[]])) #all的引數為可迭代物件,並且可迭代物件中的每一個元素都為真,結果才為真,否則為假
print(all([])) #如果引數為空,則結果為真,空不是沒有引數,而是空列表或者空元組、字典等
執行結果:
False
True

print(any([0,None,'',1])) #any表示引數為可迭代物件中的元素有一個為真,結果為真
print(any([])) #False

print(bin(10)) #十進位制轉化為二進位制
print(oct(10)) #十進位制轉化為八進位制
print(hex(10)) #十進位制轉化為十六進位制

# bool()
#布林值為假:0,None,空

把字串轉化為bytes型別
print('hello'.encode('utf-8'))
print(bytes('hello',encoding='utf-8'))

print(callable(max)) #判斷物件是否是可呼叫的,max是個內建函式,可以被呼叫,結果為True

print(chr(65)) #把數字根據ASIC錶轉化為對應的字母
print(chr(90))
print(ord('A')) #反過來,轉化為數字

#complex複數
x=1-2j #x=complex(1-2j)
print(type(x)) #檢視型別
print(x.real) #檢視複數的實部為1
print(x.imag) #檢視虛部為-2

#dict,int,list,tuple,str,float,set,frozenset
s=set({1,2,3}) #可變集合,造的集合是可變型別的,set是內建函式,用來造可變集合
s=frozenset({1,2,3}) #不可變集合,造的集合是不可變型別的
dict是內建函式,用來造字典

import time
print(dir(time))#檢視一個模組都有哪些屬性,比如time.time屬性等

print(divmod(1001,25)) #40是商,1是餘數
執行結果:(40, 1)

l=['a','b','c','d']
for x in enumerate(l):
    print(x)
執行結果:取出列表裡的元素的同時,還取出索引
(0, 'a')
(1, 'b')
(2, 'c')
(3, 'd')

print(hash('asdfasdfasdfasdfasdf')) #取雜湊值

檢視函式的註釋資訊
def func():
    '''
    xxxxxx
    :return:
    '''
    pass
print(help(func)) 

print(isinstance(1,int)) #判斷一個資料是不是什麼型別
print(type(1) is int) #也可以用這種方式,但推薦用上面那種

print(pow(10,2,3)) #10**2%3

print(str({'a':1})) #把一個資料型別轉化為字串

l=[1,4,2,9]
print(list(reversed(l)))#對列表倒著排

print(round(10.55545,3)) #表示保留三位小數,四捨五入

l1=['a','b','c','d','e']
l2=['a','b','c','d','e']
print(l1[1:5:2]) #'b','d'
print(l2[1:5:2]) #'b','d'
下面這種方式和上面是一樣的
l1=['a','b','c','d','e']
l2=['a','b','c','d','e']
obj=slice(1,5,2)#可以取切片物件
print(l1[obj])
print(l2[obj])

print(sum([1,2,3,4])) #傳進來的值可以是列表、元組、集合等數字
print(sum(range(10)))#sum函式可以把傳進來的引數變成迭代器,next一次取一次值

print(vars() is locals())#不加引數vars函式是看區域性作用域的名字,等於locals函式
vars(obj) 等同於obj.__dict__

# import "time" #import 不能匯入字串,但可以通過如下方式匯入字串
m=input('>>: ')
print(type(m))
obj=__import__(m) #實現與使用者互動的方式匯入模組,比如輸入的是time字串,就相當於匯入time模組
print(obj.time())

#瞭解:compile,exec,eval
#eval:提取字串內的表示式執行,然後返回執行結果
s1="1+2+3"
print(eval(s1))
s2="['a','b','c']"
l=eval(s2)
print(type(l))
print(l)
執行結果:
6
<class 'list'>
['a', 'b', 'c']
s2="for i in range(10):print(i)"
eval(s2) #執行會報錯,因為沒有執行結果

#exec:僅僅只是執行字串內的表示式或語句,沒有返回值
s1="1+2+3"
print(exec(s1))
s2="for i in range(10):print(i)"
exec(s2)#可以執行

#優先掌握
max min sorted map   from functools import reduce   filter sum bool chr
divmod enumerate id input print isinstance iter len open pow type
zip

五、import的使用

#常見的場景:一個模組就是一個包含了一組功能的python檔案,比如spam.py,模組名為spam,可以通過import spam使用。
#在python中,模組的使用方式都是一樣的,但其實細說的話,模組可以分為四個通用類別: 

  1 使用python編寫的.py檔案

  2 已被編譯為共享庫或DLL的C或C++擴充套件

  3 把一系列模組組織到一起的資料夾(注:資料夾下有一個__init__.py檔案,該資料夾稱之為包)

  4 使用C編寫並連結到python直譯器的內建模組
#匯入模組,只會在第一次匯入時執行原始檔的程式碼
#如果模組已經載入到記憶體了,下一次匯入直接引用記憶體中匯入的結果
import spam #m1=111111
import spam #m2=m1
import sys
print(sys.modules) #檢視記憶體中已經載入的模組
print('spam' in sys.modules) #檢視spam模組是否在記憶體中
#import 匯入檔案都做了哪些事?
#1 以原始檔為準產生一個名稱空間,在spam.py檔案裡
#2 以剛剛產生的名稱空間為準,執行原始檔的程式碼
#3 會在當前檔案中定義一個名字,這個名字就是模組名,用來引用模組中的名字
#spam.py
print('from the spam.py')
money=1000
def read1():
    print('spam模組:',money)
def read2():
    print('spam模組')
    read1()
def change():
    global money
    money=0

import spam #匯入模組就會執行原始檔的程式碼
money=0
def read1():
    print('from ------>')
read1()#呼叫的是當前檔案的read1
spam.read1() #呼叫的是spam.py檔案中的read1
spam.read2()
money=1000000000
spam.change()
print(money) #打列印的是當前檔案定義的money
spam.read1()
總結:import匯入spam模組後,可以使用spam.呼叫spam.py檔案的內容

六、from...import的使用

#spam.py
print('from the spam.py')
money=1000
def read1():
    print('spam模組:',money)
def read2():
    print('spam模組')
    read1()
def change():
    global money
    money=0

#為模組起別名
import spam as sm
print(sm.money)
舉例
#mysql.py
def parse():
    print('mysql sql parse')
#oracle.py
def parse():
    print('oracle sql parse')

engine_type='mysql'
if engine_type == 'mysql':
    import mysql as engine
elif engine_type == 'oracle':
    import oracle as engine
engine.parse()
#在一行匯入多個模組
import spam,time
#匯入模組的另外一種方式:from...import...
from spam import money,read1,read2,change #從哪個模組匯入什麼屬性
money=1
print(money) #列印的是當前檔案的
read1() #好處是呼叫的時候不用加字首spam.read1(),壞處會和當前檔案的衝突
def read1():
    print('===>?')
read1()#呼叫當前的
read2()
change()
print(money) #列印當前檔案的

from spam import * #可以匯入模組的所有屬性,但不知道都匯入了什麼,儘量不要用,不然衝突的可能性更大
print(money)
print(read1) #列印函式的記憶體地址
print(read2)
print(change)

#spam.py
print('from the spam.py')
__all__=['money','read1'] #from ... import *
money=1000
def read1():
    print('spam模組:',money)
def read2():
    print('spam模組')
    read1()
def change():
    global money
    money=0

from spam import * #* 對應模組spam內的__all__屬性
print(money)
print(read1)
print(read2) #此時會報錯,因為沒有匯入此屬性

七、python檔案的兩種用途

#模組的過載 (瞭解)
考慮到效能的原因,每個模組只被匯入一次,放入字典sys.module中,如果你改變了模組的內容,你必須重啟程式,python不支援重新載入或解除安裝之前匯入的模組,
有的同學可能會想到直接從sys.module中刪除一個模組不就可以解除安裝了嗎,注意了,你刪了sys.module中的模組物件仍然可能被其他程式的元件所引用,因而不會被清楚。
特別的對於我們引用了這個模組中的一個類,用這個類產生了很多物件,因而這些物件都有關於這個模組的引用。
如果只是你想互動測試的一個模組,使用 importlib.reload()可以重新載入模組, 比如
import importlib 
importlib.reload(modulename)#這隻能用於測試環境。

#py檔案區分兩種用途:模組與指令碼
#編寫好的一個python檔案可以有兩種用途:    
一:指令碼,一個檔案就是整個程式,用來被執行
二:模組,檔案中存放著一堆功能,用來被匯入使用
#python為我們內建了全域性變數__name__,
 當檔案被當做指令碼執行時:__name__ 等於'__main__'
 當檔案被當做模組匯入時:__name__等於模組名,也就是說如果被匯入的模組中含有__name__程式碼,那麼__name__等於模組名
#作用:用來控制.py檔案在不同的應用場景下執行不同的邏輯
if __name__ == '__main__':
總結一下:
如果我們是直接執行某個.py檔案的時候,該檔案中”__name__ == '__main__'“是True,但是我們如果從另外一個.py檔案通過import匯入該檔案的時候,這時__name__的值就是我們這個py檔案的名字而不是__main__。
這個功能還有一個用處:除錯程式碼的時候,在”if __name__ == '__main__'“下面加入一些我們的除錯程式碼,我們可以讓外部模組呼叫的時候不執行我們的除錯程式碼

八、模組的搜尋路徑

#模組的查詢順序是:記憶體中已經載入的模組->內建模組->sys.path路徑中包含的模組
import sys
sys.path.append(r'D:\張大志邁遠\pythontest\day5\day5\a') #如果模組的py檔案與執行檔案不在同一目錄下,要將模組的py檔案加到sys.path環境變數中才能匯入模組sys.path.append或者sys.path.insert
import m
或者
from a import m

九、包的使用

1、什麼是包?
#官網解釋Packages are a way of structuring Python’s module namespace by using “dotted module names”
包是一種通過使用‘.模組名’來組織python模組名稱空間的方式。
#具體的:包就是一個包含有__init__.py檔案的資料夾,所以其實我們建立包的目的就是為了用資料夾將檔案/模組組織起來
#需要強調的是:
  1. 在python3中,即使包下沒有__init__.py檔案,import 包仍然不會報錯,而在python2中,包下一定要有該檔案,否則import 包報錯
2. 建立包的目的不是為了執行,而是被匯入使用,記住,包只是模組的一種形式而已,包的本質就是一種模組

#匯入包實際上就是在匯入包下面的__init__.py檔案,如果想呼叫包下的其他模組檔案,需要先在__init__.py檔案中匯入此模組檔案才能使用包下的其他模組,__init__.py檔案的path環境變數路徑和執行檔案環境變數相同,也就是執行檔案在哪個目錄下,_init__.py檔案的環境變數也在哪個目錄下

在__init__.py中匯入模組的方法
from aaa import m1 #匯入aaa目錄下的m1模組,這樣在執行檔案中就可以使用這個模組
在執行檔案中的操作
import aaa #在執行檔案中先匯入aaa這個包,就是匯入__init__.py檔案,因為此檔案中已經匯入m1模組
aaa.m1.f1()#就可以在執行檔案中呼叫m1模組下的f1屬性

如果想在執行檔案中直接調m1模組的f1屬性
#點的左邊必須是包,from...import後必須是一個明確的名字,不能帶點
from aaa.m1 import f1 #在__init__.py中匯入的方式
在執行檔案中呼叫的方式
import aaa #先匯入包,因為__init__.py中已經匯入f1屬性
aaa.f1() #就可以在執行檔案中直接呼叫f1屬性

以上匯入包的方式都是絕對路徑的匯入,都是從包的頂級目錄開始找
也可以用相對路徑的方式匯入包
from .. ccc import f4 #..表示上一級目錄,加一個點就表示上一級
from . bbb.m3 import f3 #. 表示當前目錄,和linux裡面的一樣

如果執行檔案和匯入的包不在同一目錄下,需要在執行檔案中先將包所在的目錄加入環境變數,才能匯入包
import sys
sys.path.append(r'C:\Users\Administrator\PycharmProjects\python19期\day5\7 包的使用\xxx\yyy') 
import aaa

也可以不依賴於__init__.py檔案,直接在執行檔案中把包檔案匯入
import aaa.ccc.m4 #匯入aaa包下的ccc包下的m4.py檔案
aaa.ccc.m4.f4() #呼叫的時候就要把包名都寫上,可以把整個包起別名,呼叫的時候簡單

十、軟體開發規範

import os
print(os.path.abspath(__file__)) #列印當前檔案的絕對路徑
print(os.path.dirname(os.path.abspath(__file__))) #當前檔案所在的目錄
print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #當前檔案的上一級目錄

#start.py
#目錄結構ATM/bin/start.py   ATM/core/src.py  ATM/conf/setting.py
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) #將ATM目錄加入到path變數裡,因為已經新增到環境變數裡了,在任意檔案中都可以用這個環境變數
from core import src
if __name__ == '__main__':
    src.run()
#src.py
from conf import settings
def shop():
    print('shopping',settings.DB_PATH)
def run():
    while True:
        print('''
        1 購物
        2 付款
        3 還款
        4 轉賬
        ''')
        choice=input('>>: ').strip()
        if not choice:continue
        if choice == '1':
            shop()
#setting.py
DB_PATH=r'C:\Users\Administrator\PycharmProjects\python19期\day5\8 軟體開發規範\ATM\db'

相關文章