一個初學者的辛酸路程-裝飾器-5


前言:

繼續前進

基礎回顧:

1、集合

集合有2個重要作用:關系測試(並集,差集,交集)和去重。

2、文件編碼

2.7上默認文件編碼是ASCII碼,因為不支持中文,就出了GB2312,在2.7上要支持中文就必須申明文件編碼以UTF-8的格式,UTF-8與GB2312的關系?

UTF-8是Unicode的擴展集合,Unicode包括全國地區的編碼,中國很多開始程序還是以GBK的格式,GBK向下兼容GB2312,Windows默認編碼是GBK。

Unicode為何要做出來?為了節省空間,存英文中文都是2個字節,本來我用ASCII碼存英文只用1個字節,但是現在用你2個,所以出了UTF-8 ,存英文是1個字節,中文統一3個字節。

假如1個文件是GBK編碼的,另外一個是UTF-8,如果它要讀這個文件,就要進行一個轉換,但是他們之間不能直接轉換,這個時候就涉及到了轉碼的問題。所以GBK轉換成UTF-8,語法是先decode 成Unicode,然后在encode成utf-8,見下圖:

在3.0中,默認編碼是Unicode,在2.7中要打印中文就得申明字符編碼 # -*- coding:utf-8 -*- 

在3.0可以不寫,默認文件編碼就是Unicode,那么現在文件編碼就是Unicode,因為Unicode本來也支持中文,按2個字節存放,不需要轉換成utf-8,要想變成utf-8也得encode一下,如下所示:

a= '我是'.encode("utf-8")

。當然也可以申明字符編碼 # -*- coding:utf-8 -*- ,那么現在的文件編碼就是utf-8了。

3、函數

 格式如下:

def  func_name():
        pass

位置參數,比如 arg1 和 arg2

def  func_name(arg1,arg2):
      pass

func_name(5,3)

5對應的是arg1  3對應的是arg2

關鍵參數,可以指定參數名,比如:

def  func_name(arg1,arg2,arg3):
        pass

func_name(1,2,arg3=5)

注意,關鍵參數不能寫在位置參數前面。

多個參數,就用到了*args,比如:

def   func_name(arg1,arg2,*args):
        pass

func_name(4,5,6,7,8)
那么打印出來效果
4,5,(6,7,8)

把后面非固定參數寫成了元祖

**kwargs ,打印出來是一個字典,例如

def func_name(arg1,arg2,arg3,*args,**kwargs):
        pass

func_name(3,4,55,666,77,name=xiedi)

打印出來的結果
3,4,55,(666,77),{'name':'xiedi'}

4、局部變量和全局變量

總的來說,局部變量只對函數內生效,對函數外不起作用。

它涉及到一個作用域的問題,只是在函數里生效的,函數執行完畢,變量就沒了,作用域只允許在函數里改東西。

找變量的順序,先從內到外找變量。

如果非得改變它的作用域,就加一個global,但是不建議這么做,例如

age = 22
def change_age():
        global age
        age = 24

5、返回值

返回值是因為我想得到函數的執行結果,它還代表着程序的結束

6、遞歸

遞歸相當於自己調自己,有幾個條件:

1、要有一個明確的結束條件。

因為遞歸相當於一層進入一層。

2、問題規模每遞歸一次都應該比上一次的問題規模有所減少。

3、效率低

7、高階函數

把一個函數當做另一個函數的參數傳進去,返回的時候要用到這個函數。

函數式編程是不需要變量的,純粹是一個映射關系,函數式編程是沒有副作用的,就是傳進去的數據是確定的,得出來的結果也是固定的。

8、文件操作

打開模式:

f = open 

r,w,a 

r是讀,w是寫,它會覆蓋,a是追加,r+是讀寫模式,寫到后面,追加的模式。

w+ 是寫讀,以寫的模式打開文件,如果文件存在,直接覆蓋。

a+追加寫讀

rb二進制模式打開,全部是字節格式

獲得文件句柄

操作:

 f.

關閉:

f.close

 

接下來就是重點了,先來個裝飾器。顧名思義,裝飾一下。

一、裝飾器

從字面意思來看,器代表函數的意思,可以說,裝飾器本身就是函數,都是用def語法來定義的。

裝飾器:

定義:本質是函數,(裝飾其他函數)

為其他函數添加附加功能。

①先來看個簡單的,在沒學函數之前,我想給定義的函數打個日志,寫法如下:

def  test1():
    pass
    print('logging')

def test2():
    pass
    print('logging')
#調用
test1()
test2()

②接下來學了函數,我就把打日志定義成一個函數

# -*- coding: utf-8 -*-
#Author: Leon xie

def logger():
    print('logging')

def  test1():
    pass
    logger()

def test2():
    pass
    logger()
#調用
test1()
test2()

假設我寫的函數已經上線運行了,某一天,我有個需求,在這個里面新增一個功能,那怎么去做這個事?

最簡單的就是:挨個找到100個函數,加上去。但是問題是程序已經運行了,我剛才操作是修改我程序的源代碼,會有風險發生。

所以說,我要新增一個功能,不能夠修改函數的源代碼,函數一旦寫好了,原則上不能動源代碼了。

所以就有了下面的原則:

原則:

1、不能修改被裝飾函數的源代碼。

2、不能修改被裝飾的函數的調用方式。

裝飾器對於被裝飾函數是完全透明的。他沒有動我的源代碼,我該怎么調用運行就怎么運行。

 

舉例子:

定義1個函數

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

import time

def test1():
    time.sleep(3)
    print('in the test1')

test1()
這個函數實現的就是 睡3秒然后打印

接下來寫個裝飾器:

用的時候只要在函數前面加一個“@函數名”, 即可

先睡3s然后打印,隨后統計了一個test1函數的運行時間。

第一:裝飾器本質就是一個函數

第二:裝飾器不修改被裝飾函數的源代碼和調用方式

第三:對於函數 test1來說,裝飾器完全不存在。

實現這個裝飾器的功能需要哪些知識呢?

1、函數即變量

2、高階函數

3、嵌套函數

最終:

高階函數+嵌套函數===>裝飾器

 

我們來復習一下變量:

變量是存在內存當中,比如我x=1,那么它是如何存在變量中呢?如下圖:

其實我要說的就是函數即變量。

變量調用加上變量名直接調用。

函數調用呢就是函數加個小括號。    test()

 python解釋器中有一個概念叫做引用計數。

比如x=1 ,y=x,那么就是2次計數。

x和y相當於房間的門牌號,如果沒有門牌號了,那么內存里的1就會被清空。

匿名函數:

有的函數是不定義名字的。

例如:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie
#為了后面調用,我起了一個變量名,這個函數沒有名字
calc = lambda x:x*3
print(calc(3))
輸出結果
9

匿名函數沒有def起函數名。

小結:

函數就是一個變量,定義一個函數,就是把函數體付給了這個函數名。

變量特性是:內存回收。

 

既然說函數即變量那么下面這個函數如何存放呢?

def foo():

   print('in the foo')

       bar()

foo()

這個函數就回報錯,如下圖所示:

變量是先定義,后引用,函數也是一樣。

看下面這個例子:可以正常調用,只要在調用之前存在就可以調用

def foo():

   print('in the foo')

       bar()

def bar():

  print('in the bar')

foo()

 

 

 高階函數:(滿足下面2個條件)

a:把一個函數名當做實參傳給另外一個函數(在不修改被裝飾函數源代碼的情況下為其添加功能)

b:返回值中包含函數名

按照第一條原則寫一個

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

def bar():
    print('in the bar')

def test1(func):
    print(func)

test1(bar)

輸出結果

<function bar at 0x0000000000A69268>
一段內存地址

上面相當於

func= bar 是一個門牌地址

func()是可以運行的,所以可以寫成這樣 類似於x=1   y=x

那么就有了下面的函數,附加一個計數的功能。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

import  time
def bar():
    time.sleep(3)
    print('in the bar')

def test1(func):
    start_time =time.time()
    #運行一下func
    func()
    stop_time =time.time()
    #傳進來的運行時間不是test1
    print("the func run time is %s" %(stop_time-start_time))

test1(bar)


輸出結果

in the bar
the func run time is 3.0002999305725098

這里在沒有修改源代碼的基礎上新增了一個計數的功能。不過我們知道裝飾器還有一個條件就是不改變調用方式。所以我們接着往下看

嵌套函數舉例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

def foo():
    print('in the foo')
    def bar():
        print('in the bar')

    bar()
foo()
輸出結果

in the foo
in the bar

 

最后裝飾器效果:

#寫個裝飾器統計運行的時間

import time

def timer(func):  #timer(test1)  test1 的內存地址給了func
    def deco(*args,**kwargs):
        start_time=time.time()
        func(*args,**kwargs)
        stop_time= time.time()
        print('the func run time is %s' %(stop_time-start_time))
    return deco     #返回了deco的內存地址

#嵌套函數寫成下面的形式
#def timer():
 #   def deco():
   #     pass

@timer   #test1= timer(test1)
def  test1():
    time.sleep(1)
    print('in the test1')

@timer   #test2= timer(test2)
def test2(name,age):
    time.sleep(1)
    print("test2:",name,age)

test1()
test2("xiedi",22)

輸出結果

in the test1
the func run time is 1.0
test2: xiedi 22
the func run time is 1.0

升級

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

#需求:公司有網站,有很多頁面,模擬1個頁面1個函數,在之前情況誰都可以登錄沒有任何驗證
#100個頁面有20個登錄以后才能看到,就說給20個加入驗證功能。

import time
user,passwd = 'xiedi','123'
def auth(func):
    def wrapper(*args,**kwargs):
        username = input("Username:").strip()
        password = input("Password:").strip()

        if user == username and passwd == password:
            print("\033[32;1mUser has passed authentication\033[0m")
            res = func(*args,**kwargs)
            print("--afterauthenticaion")
            return res
        else:
            exit("\033[31;1mInvalid username or password\033[0m")
    return wrapper

def index():
    print("welcome to index page")

@auth
def home():
    print("welcome to hoem page")
    return "from home"

@auth
def bbs():
    print("welcome to bbs page")

index()
print(home())
bbs()
View Code

輸出結果

welcome to index page
Username:xiedi
Password:123
User has passed authentication
welcome to hoem page
--afterauthenticaion
from home
Username:

 升級,加入新的判斷,登錄判斷

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

#需求:公司有網站,有很多頁面,模擬1個頁面1個函數,在之前情況誰都可以登錄沒有任何驗證
#100個頁面有20個登錄以后才能看到,就說給20個加入驗證功能。

#可不可以讓home認證的時候使用本地認證,bbs用遠程認證

import time
user,passwd = 'xiedi','123'
def auth(auth_type):
    print("auth func:",auth_type)
    def outer_wrapper(func):
        def wrapper(*args,**kwargs):
            print("wrapper func args:",args,**kwargs)
            username = input("Username:").strip()
            password = input("Password:").strip()

            if user == username and passwd == password:
                print("\033[32;1mUser has passed authentication\033[0m")
                res = func(*args,**kwargs)
                print("--afterauthenticaion")
                return res
            else:
                exit("\033[31;1mInvalid username or password\033[0m")
        return wrapper
    return outer_wrapper

def index():
    print("welcome to index page")

@auth(auth_type = "local")
def home():
    print("welcome to hoem page")
    return "from home"

@auth(auth_type = "ldap")
def bbs():
    print("welcome to bbs page")

index()
home()
bbs()

輸出結果

auth func: local
auth func: ldap
welcome to index page
wrapper func args: ()
Username:xiedi
Password:123
User has passed authentication
welcome to hoem page
--afterauthenticaion
wrapper func args: ()
Username:xiedi
Password:123
User has passed authentication
welcome to bbs page
--afterauthenticaion

Process finished with exit code 0

二、迭代器和生成器

列表生成式:

我們到列表的定義,比如a=[1,2,3],我們還可以這么寫[i*2 for i in range(10)]

就是i在range(10)做一個for循環,然后乘以2得到一個列表。這個就叫做列表生成式。主要作用是使代碼更簡潔。

還可以在前面執行一個函數,如下圖:

 

生成器:

通過列表生成式,我們可以直接創建一個列表,但是,收到內存限制,列表容量肯定是有限的。

比如我創建100W元素的列表,我只用前面幾個,后面都不用,是不是浪費?

所以,如果列表元素可以按照某種算法推算出來,那我們就不必創建完整的list,從而節省大量的空間,在Python中,這種一邊循環一邊計算的機制,稱為生成器:generator。

怎么去節省內存呢?循環列表是1個1個循環,列表從頭循環到尾的時候,我循環10次,循環到第5次的時候,后面的5個數據是已經准備好的。剩下的就很占用空間,那么我能不能搞個機制出來,我循環到第4次的時候,第4次的數據才剛生成。剩下的我不調用就沒有

這樣我就不需要提前把數據准備好了,省了空間了。

那么數據是怎么生成呢?有規律的做法

這樣就是沒循環一次乘以2了。你訪問它,它才會生成。

生成器,只有在調用時才會生成相應的數據。

生成器只記住當前這個位置,它也不知道前面,也不知道后面,前面用完了對它來講沒了,它只保存一個值。

1、只記錄當前位置

2、只有一個_next_()方法。

(i*i  for  i in range(10))這個語句高了一個生成器。

如果后面生成數據沒有規律那怎么辦?

再次,創建一個生成器:

用函數來做一個生成器。

斐波拉契數列,除第一個和第二個數外,任意一個數都可以由前面2個數相加得到

1,1,2,3,5,8,13,21,34。。。。

規則就是如此。

他是有一定規律就可以推導出來。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

def fib(max):
    n, a, b = 0,0,1
    while  n <max:
        print(b)
        a,b =b ,a+b
        n=n+1
    return 'done'

fib(10)

結果

1
1
2
3
5
8
13
21
34
55

分析:

a,b=1,2

a=1

b=2

t=(b,a+b)

所以這個時候

a=2 b=3了

把上面函數改成生成器,1步即可

變成了一個生成器。

這樣做的好處在哪呢?

之前,我們調用函數,如果函數在執行時候需要花費10分鍾,那么我接下來的操作就要在10分鍾后進行。程序就卡在這了

現在這個呢?現在函數變成生成器之后,我直接調用一下next,它就在里面循環一次,停在這了,程序就跑到外面了,我可以干點別的事在回去。例如:

這樣就把函數做成了一個生成器。

接下來有個問題,就是如果我取得數大於10,用next 方法取不到就會報一個異常。如何解決呢?

就是要抓住這個異常: try一下

g = fib(6)

while True:
    try:
        x = next(g)
        print('g:',x)
    except StopIteration as e:
        print('Generator return value:',e.value)
        break

yield是保存了函數的中斷狀態,返回當前狀態的值,函數停在這了,一會還可以回來。

工作中如何使用呢?

我們可以通過yield來實現單線程的情況下實現並發運算的效果

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#Author: Leon xie

import time

#典型的生產者消費者模型
def consumer(name):
    print("%s 准備吃包子啦!!" %name)

    while True:
        baozi = yield

        print("包子[%s]來了,被[%s]吃了" %(baozi,name))

c = consumer("xiedi")
c.__next__()

b1 = "韭菜餡"
c.send(b1)
#c.__next__()

def producer(name):
    c = consumer('A')
    c2 = consumer('B')
    c.__next__()
    c2.__next__()
    print("老子開始准備做包子了!")
    for i in range(10):
        time.sleep(1)
        print("做了2個包子")
        c.send(i)
producer("dd")

輸出結果

xiedi 准備吃包子啦!!
包子[韭菜餡]來了,被[xiedi]吃了
A 准備吃包子啦!!
B 准備吃包子啦!!
老子開始准備做包子了!
做了2個包子
包子[0]來了,被[A]吃了
做了2個包子
包子[1]來了,被[A]吃了
做了2個包子
包子[2]來了,被[A]吃了
做了2個包子
包子[3]來了,被[A]吃了
做了2個包子
包子[4]來了,被[A]吃了
做了2個包子
包子[5]來了,被[A]吃了
做了2個包子
包子[6]來了,被[A]吃了
做了2個包子
包子[7]來了,被[A]吃了
做了2個包子
包子[8]來了,被[A]吃了
做了2個包子
包子[9]來了,被[A]吃了

 迭代器:

可直接作用於for循環的數據類型有以下幾種:

一類是集合數據類型,如list,tuple ,dict ,set ,str等。

一類是generator,包括生成器和帶yield的 generator function。

可以使用isinstance()判斷一個對象是否是Iterable對象。

  

可以被next()函數調用並不斷返回下一個值得對象統稱為迭代器。

可以直接作用於for循環的對象統稱為可迭代對象:Iterable 。

三、軟件目錄結構規范

目錄結構目的

  1. 可讀性高: 不熟悉這個項目的代碼的人,一眼就能看懂目錄結構,知道程序啟動腳本是哪個,測試目錄在哪兒,配置文件在哪兒等等。從而非常快速的了解這個項目。
  2. 可維護性高: 定義好組織規則后,維護者就能很明確地知道,新增的哪個文件和代碼應該放在什么目錄之下。這個好處是,隨着時間的推移,代碼/配置的規模增加,項目結構不會混亂,仍然能夠組織良好。

假設你的項目名為foo, 我比較建議的最方便快捷目錄結構這樣就足夠了:

Foo/    項目名
|-- bin/   可執行放的目錄
|   |-- foo   啟動foo調用main
|
|-- foo/    主程序目錄
|   |-- tests/   測試的,程序的主邏輯,測試代碼
|   |   |-- __init__.py   
|   |   |-- test_main.py
|   |
|   |-- __init__.py   必須有,這是一個空文件
|   |-- main.py    程序主入口,啟動foo去調用main
|
|-- docs/    文檔
|   |-- conf.py
|   |-- abc.rst
|
|-- setup.py    安裝部署的腳步
|-- requirements.txt    依賴關系,比如依賴安裝mysql
|-- README 
---conf 配置文件目錄

簡要解釋一下:

  1. bin/: 存放項目的一些可執行文件,當然你可以起名script/之類的也行。
  2. foo/: 存放項目的所有源代碼。(1) 源代碼中的所有模塊、包都應該放在此目錄。不要置於頂層目錄。(2) 其子目錄tests/存放單元測試代碼; (3) 程序的入口最好命名為main.py
  3. docs/: 存放一些文檔。
  4. setup.py: 安裝、部署、打包的腳本。
  5. requirements.txt: 存放軟件依賴的外部Python包列表。
  6. README: 項目說明文件。
  7. conf:配置文件目錄

關於README的內容

這個我覺得是每個項目都應該有的一個文件,目的是能簡要描述該項目的信息,讓讀者快速了解這個項目。

它需要說明以下幾個事項:

  1. 軟件定位,軟件的基本功能。
  2. 運行代碼的方法: 安裝環境、啟動命令等。
  3. 簡要的使用說明。
  4. 代碼目錄結構說明,更詳細點可以說明軟件的基本原理。
  5. 常見問題說明。

我覺得有以上幾點是比較好的一個README。在軟件開發初期,由於開發過程中以上內容可能不明確或者發生變化,並不是一定要在一開始就將所有信息都補全。但是在項目完結的時候,是需要撰寫這樣的一個文檔的。

可參考:https://github.com/antirez/redis#what-is-redi

 


免責聲明!

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



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