Python之路 函數進階

名稱空間

又名name space, 顧名思義就是存放名字的地方,存什麼名字呢?舉例說明,若變量x=1,1存放於內存中,那名字x存放在哪裏呢?名稱空間正是存放名字x與1綁定關係的地方程序員

名稱空間共3種,分別以下後端

  • locals: 是函數內的名稱空間,包括局部變量和形參
  • globals: 全局變量,函數定義所在模塊的名字空間
  • builtins: 內置模塊的名字空間

不一樣變量的做用域不一樣就是由這個變量所在的命名空間決定的。閉包

做用域即範圍函數

  • 全局範圍:全局存活,全局有效
  • 局部範圍:臨時存活,局部有效

查看做用域方法 globals(),locals()網站

做用域查找順序

level = 'L0'
n = 22


def func():
    level = 'L1'
    n = 33
    print(locals())

    def outer():
        n = 44
        level = 'L2'
        print(locals(),n)

        def inner():
            level = 'L3'
            print(locals(),n) #此外打印的n是多少?
        inner()
    outer()


func()

問題:在inner()裏的打印的n的值是多少?ui

LEGB 表明名字查找順序: locals -> enclosing function -> globals -> __builtins__spa

  • locals 是函數內的名字空間,包括局部變量和形參
  • enclosing 外部嵌套函數的名字空間
  • globals 全局變量,函數定義所在模塊的名字空間
  • builtins 內置模塊的名字空間

閉包

關於閉包,即函數定義和函數表達式位於另外一個函數的函數體內(嵌套函數)。並且,這些內部函數能夠訪問它們所在的外部函數中聲明的全部局部變量、參數。當其中一個這樣的內部函數在包含它們的外部函數以外被調用時,就會造成閉包。也就是說,內部函數會在外部函數返回後被執行。而當這個內部函數執行時,它仍然必需訪問其外部函數的局部變量、參數以及其餘內部函數。這些局部變量、參數和函數聲明(最初時)的值是外部函數返回時的值,但也會受到內部函數的影響。code

def outer():
    name = 'alex'

    def inner():
        print("在inner裏打印外層函數的變量",name)

    return inner


f = outer() 

f()

閉包的意義:返回的函數對象,不只僅是一個函數對象,在該函數外還包裹了一層做用域,這使得,該函數不管在何處調用,優先使用本身外層包裹的做用域視頻

裝飾器

你是一家視頻網站的後端開發工程師,大家網站有如下幾個版塊。對象

def home():
    print("---首頁----")

def america():
    print("----歐美專區----")

def japan():
    print("----日韓專區----")

def henan():
    print("----河南專區----")

視頻剛上線初期,爲了吸引用戶,大家採起了免費政策,全部視頻免費觀看,迅速吸引了一大批用戶,免費一段時間後,天天巨大的帶寬費用公司承受不了了,因此準備對比較受歡迎的幾個版塊收費,其中包括「歐美」 和 「河南」專區,你拿到這個需求後,想了想,想收費得先讓其進行用戶認證,認證經過後,再斷定這個用戶是不是VIP付費會員就能夠了,是VIP就讓看,不是VIP就不讓看就好了唄。 你以爲這個需求非常簡單,由於要對多個版塊進行認證,那應該把認證功能提取出來單獨寫個模塊,而後每一個版塊裏調用 就能夠了,與是你輕輕的就實現了下面的功能 。

#_*_coding:utf-8_*_

user_status = False #用戶登陸了就把這個改爲True

def login():
    _username = "alex" #僞裝這是DB裏存的用戶信息
    _password = "abc!23" #僞裝這是DB裏存的用戶信息
    global user_status
    if user_status == False:
        username = input("user:")
        password = input("pasword:")
        if username == _username and password == _password:
            print("welcome login....")
            user_status = True
        else:
            print("wrong username or password!")
    else:
        print("用戶已登陸,驗證經過...")

def home():
    print("---首頁----")

def america():
    login() #執行前加上驗證
    print("----歐美專區----")

def japan():
    print("----日韓專區----")

def henan():
    login() #執行前加上驗證
    print("----河南專區----")

home()
america()
henan()

此時你信心滿滿的把這個代碼提交給你的TEAM LEADER審覈,沒成想,沒過5分鐘,代碼就被打回來了, TEAM LEADER給你反饋是,我如今有不少模塊須要加認證模塊,你的代碼雖然實現了功能,可是須要更改須要加認證的各個模塊的代碼,這直接違反了軟件開發中的一個原則「開放-封閉」原則,簡單來講,它規定已經實現的功能代碼不容許被修改,但能夠被擴展,即:

  • 封閉:已實現的功能代碼塊不該該被修改
  • 開放:對現有功能的擴展開放

這個原則你仍是第一次據說,我擦,再次感覺了本身這個野生程序員與正規軍的差距,BUT ANYWAY,老大要求的這個怎麼實現呢?如何在不改原有功能代碼的狀況下加上認證功能呢?你一時想不出思路,只好帶着這個問題回家繼續憋,媳婦不在家,去隔壁老王家串門了,你正好落的清靜,一不當心就想到了解決方案,不改源代碼能夠呀。 你師從沙河金角大王時,記得他教過你,高階函數,就是把一個函數當作一個參數傳給另一個函數,當時大王說,有一天,你會用到它的,沒想到這時這個知識點忽然從腦子 裏蹦出來了,我只須要寫個認證方法,每次調用 須要驗證的功能 時,直接 把這個功能 的函數名當作一個參數 傳給 個人驗證模塊不就好了麼,哈哈,機智如我,如是你啪啪啪改寫了以前的代碼。

#_*_coding:utf-8_*_

user_status = False #用戶登陸了就把這個改爲True

def login(func): #把要執行的模塊從這裏傳進來
    _username = "alex" #僞裝這是DB裏存的用戶信息
    _password = "abc!23" #僞裝這是DB裏存的用戶信息
    global user_status

    if user_status == False:
        username = input("user:")
        password = input("pasword:")

        if username == _username and password == _password:
            print("welcome login....")
            user_status = True
        else:
            print("wrong username or password!")

    if user_status == True:
        func() # 看這裏看這裏,只要驗證經過了,就調用相應功能

def home():
    print("---首頁----")

def america():
    #login() #執行前加上驗證
    print("----歐美專區----")

def japan():
    print("----日韓專區----")

def henan():
    #login() #執行前加上驗證
    print("----河南專區----")
home()
login(america) #須要驗證就調用 login,把須要驗證的功能 當作一個參數傳給login
# home()
# america()
login(henan)

你很開心,終於實現了老闆的要求,不改變原功能代碼的前提下,給功能加上了驗證,此時,媳婦回來了,後面還跟着老王,你兩家關係 很是 好,老王常常來串門,老王也是碼農,你跟他分享了你寫的代碼,興奮的等他看完 誇獎你NB,沒成想,老王看後,並無誇你,抱起你的兒子,笑笑說,你這個代碼仍是改改吧, 要否則會被開除的,WHAT? 會開除,明明實現了功能 呀, 老王講,沒錯,你功能 是實現了,可是你又犯了一個大忌,什麼大忌? 你改變了調用方式呀, 想想,如今沒每一個須要認證的模塊,都必須調用你的login()方法,並把本身的函數名傳給你,人家以前可不是這麼調用 的, 試想,若是 有100個模塊須要認證,那這100個模塊都得更改調用方式,這麼多模塊確定不止是一我的寫的,讓每一個人再去修改調用方式 才能加上認證,你會被罵死的。。。。 你以爲老王說的對,但問題是,如何即不改變原功能代碼,又不改變原有調用方式,還能加上認證呢? 你苦思了一會,仍是想不出,老王在逗你的兒子玩,你說,老王呀,快給我點思路 ,實在想不出來,老王背對着你問, 老王:學過匿名函數沒有? 你:學過學過,就是lambda嘛 老王:那lambda與正常函數的區別是什麼? 你:最直接的區別是,正常函數定義時須要寫名字,但lambda不須要 老王:沒錯,那lambda定好後,爲了屢次調用 ,能否也給它命個名? 你:能夠呀,能夠寫成plus = lambda x:x+1相似這樣,之後再調用plus就能夠了,但這樣不就失去了lambda的意義了,明明人家叫匿名函數呀,你起了名字有什麼用呢? 老王:我不是要跟你討論它的意義 ,我想經過這個讓你明白一個事實 說着,老王拿起你兒子的畫板,在上面寫了如下代碼:

def plus(n):
    return n+1

plus2 = lambda x:x+1

老王: 上面這兩種寫法是否是表明 一樣的意思? 你:是的 老王:我給lambda x:x+1 起了個名字叫plus2,是否是至關於def plus2(x) ? 你:我擦,你別說,還真是,但老王呀,你想說明什麼呢? 老王: 沒啥,只想告訴你,給函數賦值變量名就像def func_name 是同樣的效果,以下面的plus(n)函數,你調用時能夠用plus名,還能夠再起個其它名字,如

calc = plus

calc(n)

你明白我想傳達什麼意思了麼? 你:。。。。。。。。。。。這。。。。。。嗯 。。。。。不太。。。。明白 。。 老王:。。。。這。。。。。呵呵。。。。。。好吧。。。。,那我在給你點一下,你以前寫的下面這段調用 認證的代碼

home()
login(america) #須要驗證就調用 login,把須要驗證的功能 當作一個參數傳給login
# home()
# america()
login(henan)

你之所改變了調用方式,是由於用戶每次調用時須要執行login(henan),相似的。其實稍一改就能夠了呀

home()
america = login(america)
henan = login(henan)

這樣你,其它人調用henan時,其實至關於調用了login(henan), 經過login裏的驗證後,就會自動調用henan功能。 你:我擦,還真是唉。。。,老王,仍是你nb。。。不過,等等, 我這樣寫了好,那用戶調用時,應該是下面這個樣子

home()
america = login(america) #你在這裏至關於把america這個函數替換了
henan = login(henan)
#那用戶調用時依然寫
america()

但問題在於,還不等用戶調用 ,你的america = login(america)就會先本身把america執行了呀。。。。,你應該等我用戶調用 的時候 再執行纔對呀,不信我試給你看。。。 老王:哈哈,你說的沒錯,這樣搞會出現這個問題? 但你想一想有沒有解決辦法 呢? 你:我擦,你指的思路呀,大哥。。。我哪知道 下一步怎麼走。。。 老王:算了,估計你也想不出來。。。 學過嵌套函數沒有? 你:yes,而後呢? 老王:想實現一開始你寫的america = login(america)不觸發你函數的執行,只須要在這個login裏面再定義一層函數,第一次調用america = login(america)只調用到外層login,這個login雖然會執行,但不會觸發認證了,由於認證的全部代碼被封裝在login裏層的新定義 的函數裏了,login只返回 裏層函數的函數名,這樣下次再執行america()時, 就會調用裏層函數啦。。。 你:。。。。。。什麼? 什麼個意思,我蒙逼了。。。 老王:仍是給你看代碼吧。。

def login(func): #把要執行的模塊從這裏傳進來

    def inner():#再定義一層函數
        _username = "alex" #僞裝這是DB裏存的用戶信息
        _password = "abc!23" #僞裝這是DB裏存的用戶信息
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func() # 看這裏看這裏,只要驗證經過了,就調用相應功能

    return inner #用戶調用login時,只會返回inner的內存地址,下次再調用時加上()纔會執行inner函數

此時你仔細着了老王寫的代碼 ,感受老王真不是通常人呀,連這種奇淫巧技都能想出來。。。,心中默默感謝上天賜你一個大牛鄰居。 你: 老王呀,你這個姿式很nb呀,你首創的? 此時你媳婦噗嗤的笑出聲來,你也不知道 她笑個球。。。 老王:呵呵, 這不是我首創的呀固然 ,這是開發中一個經常使用的玩法,叫語法糖,官方名稱「裝飾器」,其實上面的寫法,還能夠更簡單 能夠把下面代碼去掉

america = login(america) #你在這裏至關於把america這個函數替換了

只在你要裝飾的函數上面加上下面代碼

@login
def america():
    #login() #執行前加上驗證
    print("----歐美專區----")

def japan():
    print("----日韓專區----")

@login
def henan():
    #login() #執行前加上驗證
    print("----河南專區----")

效果是同樣的。 你開心的玩着老王教你的新姿式 ,玩着玩着就手賤給你的「河南專區」版塊 加了個參數,而後,結果 出錯了。。。

你:老王,老王,怎麼傳個參數就不行了呢? 老王:那必然呀,你調用henan時,實際上是至關於調用的login,你的henan第一次調用時henan = login(henan), login就返回了inner的內存地址,第2次用戶本身調用henan("3p"),實際上至關於調用的時inner,但你的inner定義時並無設置參數,但你給他傳了個參數,因此天然就報錯了呀 你:可是個人 版塊須要傳參數呀,你不讓我傳不行呀。。。 老王:沒說不讓你傳,稍作改動即可。。

老王:你再試試就行了 。 你: 果真好使,大神就是大神呀。 。。 不過,若是有多個參數呢? 老王:。。。。老弟,你不要什麼都讓我教你吧,非固定參數你沒學過麼? *args,**kwargs... 你:噢 。。。還能這麼搞?,nb,我再試試。 你身陷這種新玩法中沒法自拔,竟沒注意到老王已經離開,你媳婦告訴你說爲了避免打擾你加班,今晚帶孩子去跟她姐妹住 ,你以爲媳婦真體貼,最終,你終於搞定了全部需求,徹底遵循開放-封閉原則,最終代碼以下。

#_*_coding:utf-8_*_

user_status = False #用戶登陸了就把這個改爲True

def login(func): #把要執行的模塊從這裏傳進來

    def inner(*args,**kwargs):#再定義一層函數
        _username = "alex" #僞裝這是DB裏存的用戶信息
        _password = "abc!23" #僞裝這是DB裏存的用戶信息
        global user_status

        if user_status == False:
            username = input("user:")
            password = input("pasword:")

            if username == _username and password == _password:
                print("welcome login....")
                user_status = True
            else:
                print("wrong username or password!")

        if user_status == True:
            func(*args,**kwargs) # 看這裏看這裏,只要驗證經過了,就調用相應功能

    return inner #用戶調用login時,只會返回inner的內存地址,下次再調用時加上()纔會執行inner函數


def home():
    print("---首頁----")

@login
def america():
    #login() #執行前加上驗證
    print("----歐美專區----")

def japan():
    print("----日韓專區----")

# @login
def henan(style):
    '''
    :param style: 喜歡看什麼類型的,就傳進來
    :return:
    '''
    #login() #執行前加上驗證
    print("----河南專區----")

home()
# america = login(america) #你在這裏至關於把america這個函數替換了
henan = login(henan)

# #那用戶調用時依然寫
america()

henan("3p")

此時,你已累的不行了,洗洗就抓緊睡了,半夜,上廁所,隱隱聽到隔壁老王家有微弱的女人的聲音傳來,你會心一笑,老王這傢伙,不聲不響找了女友也不帶給我看看,改天必定要見下真人。。。。 第二2天早上,產品經理又提了新的需求,要容許用戶選擇用qq\weibo\weixin認證,此時的你,已深諳裝飾器各類裝逼技巧,輕鬆的就實現了新的需求。

帶參數裝飾器

#_*_coding:utf-8_*_
user_status = False #用戶登陸了就把這個改爲True

def login(auth_type): #把要執行的模塊從這裏傳進來
    def auth(func):
        def inner(*args,**kwargs):#再定義一層函數
            if auth_type == "qq":
                _username = "alex" #僞裝這是DB裏存的用戶信息
                _password = "abc!23" #僞裝這是DB裏存的用戶信息
                global user_status

                if user_status == False:
                    username = input("user:")
                    password = input("pasword:")

                    if username == _username and password == _password:
                        print("welcome login....")
                        user_status = True
                    else:
                        print("wrong username or password!")

                if user_status == True:
                    return func(*args,**kwargs) # 看這裏看這裏,只要驗證經過了,就調用相應功能
            else:
                print("only support qq ")
        return inner #用戶調用login時,只會返回inner的內存地址,下次再調用時加上()纔會執行inner函數

    return auth

def home():
    print("---首頁----")

@login('qq')
def america():
    #login() #執行前加上驗證
    print("----歐美專區----")

def japan():
    print("----日韓專區----")

@login('weibo')
def henan(style):
    '''
    :param style: 喜歡看什麼類型的,就傳進來
    :return:
    '''
    #login() #執行前加上驗證
    print("----河南專區----")

home()
# america = login(america) #你在這裏至關於把america這個函數替換了
#henan = login(henan)

# #那用戶調用時依然寫
america()

# henan("3p")

練習題

一:編寫3個函數,每一個函數執行的時間是不同的,

提示:可使用time.sleep(2),讓程序sleep 2s或更多,

二:編寫裝飾器,爲每一個函數加上統計運行時間的功能

提示:在函數開始執行時加上start=time.time()就可紀錄當前執行的時間戳,函數執行結束後在time.time() - start就能夠拿到執行所用時間

三:編寫裝飾器,爲函數加上認證的功能,即要求認證成功後才能執行函數

四:編寫裝飾器,爲多個函數加上認證的功能(用戶的帳號密碼來源於文件),要求登陸成功一次,後續的函數都無需再輸入用戶名和密碼

提示:從文件中讀出字符串形式的字典,能夠用eval('{"name":"egon","password":"123"}')轉成字典格式

相關文章
相關標籤/搜索