06 python開發之函式

drrug發表於2020-12-12

06 python開發之函式

目錄

6 函式

6.1 基本使用

6.1.1 基本概念

  • 函式就是用來盛放程式碼的容器

    ​ 具備某一功能的工具-->工具

    ​ 實現準備好工具的過程-->函式的定義

    ​ 遇到應用場景,拿來就用

  • 使用函式可以解決程式碼可讀性差以及擴充性差的問題

  • 先定義後引用

6.1.2 定義函式

  • 定義函式的語法
def 函式名(引數1,引數2,...):
    """文件描述"""
    函式體
    return 值

# def: 定義函式的關鍵字;
# 函式名:函式名指向函式記憶體地址,是對函式體程式碼的引用。函式的命名應該反映出函式的功能;
# 括號:括號內定義引數,引數是可有可無的,且無需指定引數的型別;
# 冒號:括號後要加冒號,然後在下一行開始縮排編寫函式體的程式碼;
# """文件描述""": 描述函式功能,引數介紹等資訊的文件,非必要,但是建議加上,從而增強函式的可讀性;
# 函式體:由語句和表示式組成;
# return 值:定義函式的返回值,return是可有可無的。
def say_hello():  # say_hello=函式的記憶體地址
    print("======")
    print("hello world")
    print("======")

print(say_hello)  # <function say_hello at 0x00000241DA0273A0>
say_hello()

x = 10            # x=10的記憶體地址
print(x)
  • 函式定義階段與呼叫階段發生的事情
# 函式定義階段:只檢測語法,不執行程式碼
def foo():
    xxx
    print('from foo')

# 函式呼叫階段:執行函式體程式碼
foo()
  • 示例
示例1
def bar():
    print('from bar')

def foo():
    print('from foo')
    bar()

foo()		# from foo | from bar

示例二
def bar():
    print('from bar')
    foo()

def foo():
    print('from foo')

bar()		# from bar | from foo

示例三
def bar():
    print('from bar')
    foo()

bar()		# 報錯,要先定義後引用

def foo():
    print('from foo')
  • 定義函式的三種形式

    • 無參函式
    # 無參函式
    def f1():
        print("hello")
        print("你好")
        print("Hi")
    f1()			# hello | 你好 | Hi
    
    
    def login():
        name = input('username>>>:').strip()
        pwd = input('password>>>:').strip()
        if name == "ccc" and pwd == '111':
            print('ok')
        else:
            print('error')
    
    login()			# username>>>:
    
    • 有參函式
    def login(name,pwd):
        if name == "ccc" and pwd == "123":
            print('ok')
        else:
            print('error')
    
    login("ccc","123")	# ok
    login("ccc","1234")	# error
    
    def f2(x, y):
        print(x)
        print(y)
    
    f2(111, 222)		# 111 | 222
    
    def add(x, y):
        print(x+y)
    
    add(10, 20)	        # 30
    add(30, 40)		# 70
    
    • 空函式
    def login():
        pass
    def transfer():
        pass
    def withdraw():
        pass
    def check_balance():
        pass
    

6.2 呼叫函式與函式返回值

6.2.1 呼叫函式三種形式

  • 語句形式
len("hello")
  • 表示式形式
res = len("hello") + 10
print(res)		      # 15
  • 函式呼叫作為引數的形式
def add(x, y):
    z = x + y
    return z

print(add(5, 5))	      # 10
res = add(add(1, 2), 3)
print(res)		      # 6

6.2.2 函式的返回值return(函式的產品)

  • return作用1:控制返回值
Ⅰ 沒有return-->預設返回就是None
def func():
    print(111)

res = func()		      # 111
print(res)		      # None

Ⅱ return 值-->返回就是那一個值
def max2(x, y):
    if x > y:
        return x
    else:
        return y

res = max2(100, 200) * 5
print(res)		      # 1000

Ⅲ return 值1,值2,值3--->返回的是一個元組
def func():
    return [11, 22], 2, 3

res = func()
print(res, type(res))         # ([11, 22], 2, 3) <class 'tuple'>
  • return作用2:函式內可以有多個return,但只要執行一個就會立即種植函式的執行,並且會將return後的值當做本次呼叫的產品返回
def func():
    print(111)
    return 2201202
    print(2222)
    return 11112222
    print(3333)

res = func()
print(res)          # 111 2201202
  • return就是函式的處理結果

    函式內沒有return關鍵字,或者說return後面沒有值-->None

  • return 值
    return 值1,值2,值3--->(值1,值2,值3)

  • 可以有多個return,但只要執行一次整個函式就會立即結束,並且返回值

def f1():
    x = 10
    def f2():
        print('from f2')
    return f2

res = f1()  # 即res=f2的記憶體地址
print(res)  # <function f1.<locals>.f2 at 0x000001F5886D8700>
res()       # from f2

6.3 Type hinting

6.3.1 定義

  • python3新增型別提示功能,例如我們可以為函式增加型別提示資訊,而不影響函式本身的執行:

  • 註釋的一般規則是引數名後跟一個冒號(:),然後再跟一個expression,這個expression可以是任何形式。

6.3.2 使用

# Type hinting
def add(x: int, y: int) -> int:
    res = x + y
    return res

print(add.__annotations__)
# {'x': <class 'int'>, 'y': <class 'int'>, 'return': <class 'int'>}

6.4 函式引數

6.4.1 函式分類

  • 形參

    在定義函式時,在括號內指定引數/變數名,稱之為形式引數,簡稱形參

    形參的本質就是變數名

  • 實參

    在呼叫函式時,括號內傳入的值,稱之為實際引數,簡稱實參

    實參的本質就是變數值

  • 實參與形參的關係

    在呼叫函式時,實參的值會繫結給形參,該繫結關係可以在函式內使用

    在函式呼叫結束後,會解除繫結關係

6.4.2 引數詳解

6.4.2.1 位置形參

  • 在定義函式時,按照從左往右的順序依次定義的變數名,稱之為位置形參

  • 特點:必須被傳值,多一個少一個都不行

def func(x, y):
    print(x, y)
func(1, 2)
func(1, 2, 3)       # 報錯
func(1)             # 報錯

6.4.2.2 位置實參

  • 在呼叫函式時,按照從左往右的順序依次傳入的值,稱之為位置實參

  • 特點:按照位置與形參一一對應

def func(x, y):
    print(x, y)
func(1, 2)
func(2, 1)

6.4.2.3 預設形參

  • 在定義函式時就已經為某個形參賦值了,該形參就稱為預設引數

  • 特點:在呼叫階段可以不用為預設形參傳值

def func(x, y=111):
    print(x, y)
func(1, 2)          # 1 2
func(1)             # 1 111
def register(name, age, gender="male"):
    print(name, age, gender)
register("egon", 18)
register("lxx", 38)
register("hxx", 32, "female")
register("李建國", 30)
register("alex", 31)
register("xxx", 18)

# egon 18 male
# lxx 38 male
# hxx 32 female
# 李建國 30 male
# alex 31 male
# xxx 18 male

6.4.2.4 關鍵字實參

  • 在呼叫函式時,按照key=value的形式指定的實參,稱之為關鍵字實參

  • 特點:可以打亂順序,仍能指名道姓為指定的形參賦值

def func(x, y=2222):
    print(x, y)
func(y=222, x=1212)       # 1212 222
func(x=111)               # 111 2222

6.4.3 實參的混用

  • 位置實參和關鍵字實參可以混用,注意點:

    Ⅰ 位置實參必須放在關鍵字實參前面

    Ⅱ 不能為同一個形參重複賦值

def func(x, y=2222):
    print(x, y)
func(1, y=2)              # 1 2
func(y=2, 1)              # 報錯
func(1, y=2, x=3)         # 報錯

6.4.4 形參的混用

  • 位置形參和預設形參可以混用,注意點:

    位置形參必須放在預設形參前面

def func(x, y=111):
    print(x, y)

def func(y=111, x):     # 報錯
    print(x, y)

6.4.5 預設形參使用的注意點

  • 預設形參的值最好是不可變型別
m = 222
def func(x, y=m):
    print(x, y)
m = 666
func(111)               # 得到111 222
def register(name,hobby,hobbies=[]):
    hobbies.append(hobby)
    print('%s 的愛好是 %s' %(name, hobbies))
def register(name, hobby, hobbies=None):
    if hobbies is None:
        hobbies=[]
    hobbies.append(hobby)
    print('%s 的愛好是 %s' %(name, hobbies))
register("egon", "smoke")
register("lxx", "dance")
register("hxx", "drink")

6.5 可變長函式(*與**的應用)

6.5.1 由來

  • 可變長函式是指在呼叫函式時,傳入引數個數不固定,而實參是為形參賦值的

  • 因此必須有對應格式的形參來接受溢位的實參

6.5.2 在形參中使用

6.5.2.1 *args的使用

  • 在形參中帶*,*會將溢位的位置實參彙總成元組,然後賦值給其後變數名args
def func(x, y, *z):
    print(x, y, z)
func(1, 2, 3, 4, 5)      # 1 2 (3, 4, 5)
func(1, 2)               # 1 2 ()
func(1)                  # 報錯

def my_sum(*args):
    res = 0
    for i in args:
        res += i
    print(res)
my_sum(1)               # 1
my_sum(1, 2)            # 3
my_sum(1, 2, 3)         # 6

6.5.2.2 **kwargs的使用

  • 在形參中帶**,**通常會將溢位的關鍵字實參彙總成字典,然後賦值給其後變數名kwargs
def func(x, y, **kwargs):
    print(x, y, kwargs)
func(1, y=2, a=1, b=2, c=3)        # 1 2 {'a': 1, 'b': 2, 'c': 3}

6.5.3 在實參中使用

6.5.3.1 *的使用

  • 在實參中帶*:*會將緊跟其後的實參打散成位置實參
  • 注意*後跟的應該是一個可以被for迴圈迴圈的型別
def func(a, b, c, d):
    print(a, b, c, d)
func(*"hello")                          # 報錯,不對應
func(*"hell")                           # h e l l
func(*[11, 22, 33, 44])                 # 11 22 33 44
func(11, 22, *[33, 44])                 # 11 22 33 44
func(11, 22, *{"k1": 111, "k2": 222})   # 11 22 k1 k2

6.5.3.2 **的使用

  • 在實參中帶**?*會將緊跟其後的實參打散成關鍵字實參

  • 注意**後面跟的必須是一個字典

def func(a, b, c, d):
    print(a, b, c, d)
func(**{"k1": 333, "k2": 444})                              # 報錯
func(**{"d": 333, "b": 444, "a": 111, "c": 222})            # 111 444 222 333
func(**[("d", 333), ("b", 444), ("a", 111), ("c", 222)])    # 報錯

6.5.4 混用

  • 在形參中,*必須在**前

  • 在實參中,*必須在**前

def index(x, y, z):
    print('index------>', x, y, z)
def wrapper(*arges, **kwargs):
    index(*arges, **kwargs)

# wrapper(1, 2, 3, 4, a=1, b=2, c=3)      # 不能一一對應 報錯
wrapper(1, 2, 3)                          # index------> 1 2 3
wrapper(z=3, y=2, x=1)                    # index------> 1 2 3


def wrapper(*arges,**kwargs):
    print(arges)
    print(kwargs)

wrapper(1, 2, a=1,b=2,c=3)      # (1, 2) {'a': 1, 'b': 2, 'c': 3}

6.5.5 命名關鍵字形參

  • 在*與**之間定義的形參稱為關鍵字形參

  • 特點:必須按照key=value的形式傳值

def func(x, y=1, *args, a=666, b, **kwargs):
    print(x)
    print(y)
    print(args)
    print(a)
    print(b)
    print(kwargs)
func(1, 2, 3, 4, 5, 6, 7, a=111, b=222, c=333)      # 1 2 (3, 4, 5, 6, 7) 111 222 {'c': 333}
func(1, 2, 3, 4, 5, 6, 7, b=222, c=333)             # 1 2 (3, 4, 5, 6, 7) 666 222 {'c': 333}
func(1, 2, 3, 4, 5, 6, 7, b=222, 333)               # 報錯

6.6 函式物件

  • 函式是第一等公民(充當變數)
def func():
    print('from func')

6.6.1可以賦值

def func():
    print('from func')

f = func
f()                 # from func

6.6.2 可以當作引數傳給另一個函式

def func():
    print('from func')

def bar(x):
    print(x)
mmm = 11111
bar(mmm)           # 11111
bar(func)          # <function func at 0x000002D4F29B63A0>

6.6.3 可以當一個函式的返回值

def add(x):        # x=函式func的記憶體地址
    return x       # return 函式func的記憶體地址
res = add(func)    # res=add(func的記憶體地址)
print(res)         # <function func at 0x000002D4F29B63A0>

6.6.4 可以當容器型別的元素

x = 10
l = [x, func]
print(l)           # [10, <function func at 0x000002C48C4463A0>]
l[-1]()            # from func

6.6.5 示例

# 新的功能只需要在字典中加入即可,無需動迴圈
def login():
    print('login')
def register():
    print('register')
def transfer():
    print('transfer')
def withdraw():
    print('withdraw')

func_dic = {
    "1": [login, "登入"],
    "2": [register, "註冊"],
    "3": [transfer, "轉賬"],
    "4": [withdraw, "提現"]
}

while True:
    print("0 退出")
    for k in func_dic:
        print(k, func_dic[k][-1])
    choice = input("請輸入操作編號:").strip()
    if choice == "0":
        break
    if choice in func_dic:
        func_dic[choice][0]()
    else:
        print("輸入的操作不存在")

6.7 函式巢狀

6.7.1 函式巢狀定義

def f1():
    print('from f1')
    def f2():
        print('from f2')
    print(f2)
    f2()
    x=1111
f1()

# from f1
# <function f1.<locals>.f2 at 0x00000274E0EB8700>
# from f2
from math import pi

def circle(radius, mode=0):
    def perimiter(radius):
        return 2 * pi *radius
    def area(radius):
        return pi * (radius ** 2)
    if mode == 0:
        return perimiter(radius)
    elif mode == 1:
        return area(radius)
res1 = circle(10, mode=0)
print(res1)                 # 62.83185307179586
res2 = circle(10, mode=1)
print(res2)                 # 314.1592653589793

6.7.2 函式巢狀呼叫

def max2(x, y):
    if x > y:
        return x
    else:
        return y

def max4(a, b, c, d):
    res1 = max2(a, b)
    res2 = max2(res1, c)
    res3 = max2(res2, d)
    return res3


res = max4(1, 2, 3, 4)
print(res)              # 4

6.8 名稱空間與作用域

6.8.1 名稱空間

  • 名稱空間namespace

    名稱空間就是存放名字的地方

  • 三類名稱空間(內建名稱空間、全域性名稱空間、區域性名稱空間)

    Ⅰ 內建名稱空間:存放內建的名字

    ​ 隨著直譯器啟動就產生,直譯器關閉就銷燬

    ​ print

    ​ input

    ​ len

    Ⅱ 全域性名稱空間:存放的是頂級的名字

    ​ 執行頂級程式碼前產生,檔案執行完畢則銷燬

    Ⅲ 區域性名稱空間:在函式內定義的名字

    ​ 呼叫函式則產生一個函式的區域性名稱空間,該函式呼叫結束則立即銷燬

名稱空間示例
x = 10              # 全域性名稱空間

def f1():           # f1本身是全域性名稱空間
    y = 2           # 區域性名稱空間

if 1 > 0:
    z = 3           # 全域性名稱空間
    if 3 > 1:
        m = 333     # 全域性名稱空間

6.8.2 名字的查詢順序

  • 區域性名稱空間-->全域性名稱空間-->內建名稱空間

  • Tips:在全域性無法檢視區域性的,在區域性可以看全域性的

優先順序示例
x = 111
def f1():
    print(x)
    x = 222
f1()		# 報錯

x = 111
def f1():
    x = 222
    print(x)
f1()		# 222

6.8.3 global與nonlocal

  • 名稱空間的巢狀關係是在函式定義階段,即檢測語法時確定的

    與呼叫函式的位置無關,與函式定義位置有關

  • global 區域性對全域性變數修改

x = 111
def f1():
    global x
    x = 222

f1()
print(x)        # 222
  • nonlocal 宣告的是來自外部巢狀函式定義的作用域(非全域性)
x = 111
def f1():
    x = 222
    def f2():
        nonlocal x
        x = 333
    f2()
    print(x)
f1()		# 333

6.8.4 作用域

  • 作用域即範圍

    全域性範圍(內建、全域性名稱空間屬該範圍):全域性存活,全域性有效

    區域性範圍(區域性名稱空間屬該範圍):臨時存活,區域性有效

  • 作用域關係是在函式定義階段固定的,與函式呼叫位置無關

  • 檢視作用域

    LEGB代表名字查詢順序:locals -> enclosing function -> globals -> _builtings_

    locals 是函式內的名字空間,包括區域性變數和形參

    enclosing 外部巢狀函式的名字空間(閉包中常見)

    globals 全域性變數,函式定義所在模組的名字空間

    builtings 內建模組的名字空間

6.9 閉包函式

6.9.1 簡介

  • 閉函式

    該函式就是定義在一個函式內部的函式

def outter():
    def wrapper():
        pass
  • 包函式

    閉函式引用了一個來源於外層函式的變數

def outter():
    x = 111
    def wrapper():
        print(x)

    wrapper()

x = 222         # 不妨礙
outter()        # 111

6.9.2 意義與應用

  • 意義:

    返回的函式物件外部還包了一層作用域

    該函式無論在何處呼叫都優先使用自己外層包裹的作用域

  • 應用:

    延遲計算

from urllib.request import urlopen

def index(url):
	def get():
		return urlopen(url).read()
	return get

baidu=index('http://www.baidu.com')
print(baidu().decode('utf-8'))

6.10 裝飾器

6.10.1 簡介

  • 什麼是裝飾器

    器---->工具

    裝飾-->新增新功能

    裝飾器就是定義個函式,用該函式去為其他函式新增新功能

  • 為什麼用裝飾器

    開放封閉原則:針對上線的功能對擴充是開放的,但是對修改原始碼以及呼叫方式是封閉的

6.10.2 無參裝飾器

  • 需求:為index加上新功能-->統計其運算時間

    import time

    def index():

    ​ time.sleep(1)

    ​ print('from index')

    index()

# 1、首先考慮直接在執行的程式碼體上下方加入時間模組
import time
def index():
    start = time.time()
    
    time.sleep(1)
    print('from index')
    
    stop = time.time()
    print('run time is %s' % (stop - start))
index()
# 但是這樣改修改了原始碼
# 2、不修改原始碼
import time
def index():
    time.sleep(1)
    print('from index')

start = time.time()
index()
stop = time.time()
print('run time is %s' % (stop - start))
# 這樣修改每次呼叫函式都要輸重複程式碼,不方便
# 3、把呼叫放進函式
import time
def index():
    time.sleep(1)
    print('from index')
    
def wrapper():
    start = time.time()
    index()
    stop = time.time()
    print('run time is %s' % (stop - start))
wrapper()
# 呼叫方便,但是改變了呼叫方式
# 3.1改進一
import time
def index():
    time.sleep(1)
    print('from index')
    
def wrapper(f):
    start = time.time()
    f()
    stop = time.time()
    print('run time is %s' % (stop -start))
wrapper(index)
# 3.2改進二
import time
def index():
    time.sleep(1)
    print('from index')
    
def outter(f):
    def wrapper():
        start = time.time()
        f()
        stop = time.time()
        print('run time is %s' % (stop - start))
    return wrapper
index = outter(index)
index()
# 新增新功能home
import time
def index():
    time.sleep(1)
    print('from index')
    
def home(name):
    time.sleep(1)
    print('homepage welcome %s' % name)


def outter(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
    return wrapper
index = outter(index)
index()
home = outter(home)
home('ccc')
# 最終版
import time
def index():
    time.sleep(1)
    print('from index')
    
def home(name):
    time.sleep(1)
    print('homepage welcome %s' % name)
    return 123


def outter(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res
    return wrapper
index = outter(index)
home = outter(home)

res = index()
print(res)		# None

res = home('ccc')
print(res)		# 123

6.10.3 裝飾器的語法糖@

  • 在被裝飾物件的正上方單獨一行新增@timer,當直譯器解釋到@timer時就會呼叫timer函式,且把它正下方的函式名當做實參傳入,然後將返回的結果重新賦值給原函式名

  • 原函式

import time
def index():
    time.sleep(1)
    print('from index')
    
def home(name):
    time.sleep(1)
    print('homepage welcome %s' % name)
    return 123


def outter(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res
    return wrapper
index = outter(index)
home = outter(home)

res = index()
print(res)		# None

res = home('ccc')
print(res)		# 123
  • 用語法糖@
import time

def timmer(f):
    def wrapper(*args, **kwargs):
        start = time.time()
        res = f(*args, **kwargs)
        stop = time.time()
        print('run time is %s' % (stop - start))
        return res
    return wrapper


@timmer  # index = timmer(index)
def index():
    time.sleep(1)
    print('from index')


@timmer  # home = time(home)
def home(name):
    time.sleep(2)
    print('welcome to %s' % name)
    return 123


index()
res = home("ccc")
print(res)

# from index
# run time is 1.0003390312194824

# welcome to ccc
# run time is 2.0008108615875244
# 123

6.10.4 有參裝飾器

  • 在函式func外部再包一層函式auth,專門用來接收額外的引數,保證在auth函式內無論多少層都可以引用到引數
def auth(driver):
    def func(x):
        ...
    return func

@auth(driver='file')
def index():
    pass
@auth(driver='mysql')
def home():
    pass
import time

def login(x, engine='file'):
    def auth(func):
        def wrapper(*args, **kwargs):
            print("========>", x)
            inp_user = input("username>>>>:").strip()
            inp_pwd = input("password>>>:").strip()

            if engine == "file":
                print("基於file認證")
                if inp_user == "egon" and inp_pwd == "123":
                    print('login successful')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print("基於mysql認證")
            elif engine == "ldap":
                print("基於ldap認證")
            else:
                print("未知認證來源")
        return wrapper
    return auth


@login(11, engine='file')  # @auth  # index = auth(index)  # index=wrapper
def index():
    time.sleep(1)
    print('from index')


@login(22, engine='file')  # @auth  # home=auth(home)  # home=wrapper
def home(name):
    time.sleep(2)
    print('home page,welcome %s' % name)
    return 123


index()
home('egon')

# ========> 11
# username>>>>:egon
# password>>>:123
# 基於file認證
# login successful
# from index

# ========> 22
# username>>>>:egon3
# password>>>:123
# 基於file認證
# login successful
# home page,welcome egon

6.10.5 疊加多個裝飾器

  • 載入順序(outter函式的呼叫順序):自下而上

  • 執行順序(wrapper函式的執行順序):自上而下

    @deco1
    @deco2
    @deco3
    def foo():
    pass
    相當於

    foo=deco1(deco2(deco3(foo)))

def outter1(func1):  # func1 = wrapper2的記憶體地址
    print('============>outter1')
    def wrapper1(*args, **kwargs):
        print('=============wrapper1')
        res1 = func1(*args, **kwargs)
        return res1
    return wrapper1

def outter2(func2):  # func2 = wrapper3的記憶體地址
    print('============>outter2')
    def wrapper2(*args, **kwargs):
        print('=============wrapper2')
        res2 = func2(*args, **kwargs)
        return res2
    return wrapper2

def outter3(func3):  # func3 = index的記憶體地址
    print('============>outter3')
    def wrapper3(*args, **kwargs):
        print('=============wrapper3')
        res3 = func3(*args, **kwargs)
        return res3
    return wrapper3

          # index = wrapper1的記憶體地址
@outter1  # outter1(wrapper2的記憶體地址)-->wrapper1的記憶體地址
@outter2  # outter2(wrapper3的記憶體地址)-->wrapper2的記憶體地址
@outter3  # outter3(index的記憶體地址)-->wrapper3的記憶體地址
def index():
    print('from index')


print('*'*25)
# print(index)
index()

# ============>outter3
# ============>outter2
# ============>outter1
# *************************
# =============wrapper1
# =============wrapper2
# =============wrapper3
# from index
import time
def timmer(func):
    def wrapper1(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('run time is:%s' % (stop - start))
        return res
    return wrapper1

def login(x, engine='file'):
    def auth(func):
        def wrapper2(*args, **kwargs):
            print("========>", x)
            inp_user = input("username>>>>:").strip()
            inp_pwd = input("password>>>:").strip()

            if engine == "file":
                print("基於file認證")
                if inp_user == "egon" and inp_pwd == "123":
                    print('login successful')
                    res = func(*args, **kwargs)
                    return res
                else:
                    print('username or password error')
            elif engine == 'mysql':
                print("基於mysql認證")
            elif engine == "ldap":
                print("基於ldap認證")
            else:
                print("未知認證來源")
        return wrapper2
    return auth


# 場景一
@timmer
@login(11, engine='file')
def index():
    time.sleep(1)
    print('from index')

index()
# ========> 11
# username>>>>:egon
# password>>>:123
# 基於file認證
# login successful
# from index
# run time is:9.147817134857178


# 場景二
@login(11, engine='file')
@timmer
def index():
    time.sleep(1)
    print('from index')

index()
# ========> 11
# username>>>>:egon
# password>>>:123
# 基於file認證
# login successful
# from index
# run time is:1.0001623630523682

6.10.6 wraps裝飾器

  • functools模組下提供一個裝飾器wraps專門用來幫我們保留原函式的文件和函式名屬性,修正裝飾器
import time
from functools import wraps


def timmer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        res = func(*args, **kwargs)
        stop = time.time()
        print('run time is:%s' % (stop - start))
        return res
    return wrapper


@ timmer
def index():
    """index函式"""
    time.sleep(1)
    print('from index')


help(index)

6.10.7 匿名函式lambda

def func(x, y):  # func=函式的記憶體地址
    return x+y

匿名函式:沒有名字的函式
應用場景:臨時用一次,通常用於與其他函式配合使用
# 無用呼叫方式
f = lambda x, y: x+y
print(f)
res = f(1, 2)
print(res)

# 呼叫方式1
res = (lambda x, y: x+y)(1, 2)
print(res)

# 呼叫方式2
salaries = {
    "egon": 1000,
    "alex": 2000,
    "jack": 3000,
    "rose": 4000
}
# 求薪資最高的那個人的名字

def get_salary(name):
    return salaries[name]

print(max(salaries, key = get_salary))  # rose
print(max(salaries, key=lambda name: salaries[name]))  # rose
print(min(salaries, key=lambda name: salaries[name]))  # egon
print(sorted(salaries))  # ['alex', 'egon', 'jack', 'rose']
print(sorted(salaries, key=lambda name: salaries[name]))  # ['egon', 'alex', 'jack', 'rose']
print(sorted(salaries, key=lambda name: salaries[name], reverse=True))  # ['rose', 'jack', 'alex', 'egon']

6.11 內建函式

# abs 返回絕對值
print(abs(-11))  # 11
print(abs(0))    # 0
print(abs(11))   # 11

# all 如果是空或布林值為真則返回True
print(all(''))   # True
print(all([]))   # True
print(all([11, 22, 333, 0]))  # False

# any 只要有一個值的布林值為真就返回True
print(any(''))   # False
print(any([]))   # False
print(any([0, None, '', 1]))  # True
print(any([0, None, '']))     # False

# chr 對比ASCⅡ表:65-90 A-Z
print(chr(65))   # 數字->字母A
print(ord('A'))  # 字母->數字65
print(chr(90))   # Z

# bin/oct/hex 進位制轉換
print(bin(11))   # 十進位制->二進位制 0b1011
print(oct(11))   # 十進位制->八進位制 0o13
print(hex(11))   # 十進位制->十六進位制 0xb

# 工廠函式
int
float
str
list
tuple
dict
set
bool
bytes

# callable檢視物件是否可呼叫
print(callable(len))  # True

# 物件導向重點
classmethod
staticmethod
setattr
getattr
delattr
hasattr
dir
exec

# eval 讀取內容程式碼時執行
l = eval("[1, 2, 3]")
print(l[0])  # 1
with open('user1.txt', mode='wt', encoding='utf-8') as f:
    dic = {"ccc": "123", "zzz": "456", "yyy": "789"}
    f.write(str(dic))

with open('user1.txt', mode='rt', encoding='utf-8') as f:
    data = f.read()
    print(data, type(data))  # {'ccc': '123', 'zzz': '456', 'yyy': '789'} <class 'str'>
    dic = eval(data)
    print(dic["ccc"])  # 123
# 1、map 對映
names = ["egon", "lxx", "hxx"]
res = (name if name == "egon" else name+"_sb" for name in names)
print(list(res))  # ['egon', 'lxx_sb', 'hxx_sb']

用map實現
names = ["egon", "lxx", "hxx"]
res = map(lambda name:name if name == "egon" else name+"_sb", names)
print(res)  # <map object at 0x0000020E897D5B50>
print(list(res))  # ['egon', 'lxx_sb', 'hxx_sb']


# 2、filter 過濾
names = ['egon', 'lxx_sb', 'hxx_sb']
res = (name for name in names if name.endswith("sb"))
print(list(res))  # ['lxx_sb', 'hxx_sb']

用filter實現
names = ['egon', 'lxx_sb', 'hxx_sb']
res = filter(lambda name:name.endswith("sb"), names)
print(list(res))  # ['lxx_sb', 'hxx_sb']


# 3、reduce
from functools import reduce
res = reduce(lambda x,y:x+y, "hello", "xxx")
print(res)  # xxxhello

from functools import reduce
res = reduce(lambda x,y:x+y, [1, 2, 3], 100)
print(res)  # 106


# 4、set可變集合/frozenset不可變集合
s = frozenset({1, 2, 3})   # 不可變
s1 = set({1, 2, 3})        # 可變
s1.add(4)
print(s1)   # {1, 2, 3, 4}


# 5、locals區域性變數/globals全域性變數
x = 3333
def func():
    x = 1
    y = 2
    print(locals())
    print(globals())
func()  # {'x': 1, 'y': 2}
	# {'__name__': '__main__', ...}

a = 1111
print(locals())  # {'__name__': '__main__', ...}
print(globals()) # {'__name__': '__main__', ...}
print(locals() is globals())  # True


# 6、hash返回雜湊值
print(hash("asaasdf"))  # -9196404338305892359


# 7、pow(base ** exp % mod)
res = pow(10, 3, 3)     # 10 ** 3 % 3
print(res)              # 1


# 8、reversed 反序
res = reversed([11, 333, 222, 77])
print(list(res))       # [77, 222, 333, 11]沒有排序,直接反序


# 9、round 四捨五入
print(round(3.5))      # 4


# 10、slice 切片
l = [11, 22, 33, 44, 55, 66, 77, 88, 99]
res = l[0: 7: 2]
print(res)  # [11, 33, 55, 77]

用slice實現
l = [11, 22, 33, 44, 55, 66, 77, 88, 99]
s = slice(0, 7, 2)
res = l[s]
print(res)  # [11, 33, 55, 77]


# 11、zip 拉鍊
s1 = "hello"
l = [11, 22, 33, 44, 55, 66]
res1 = zip(s1, l)
print(list(res1))  # [('h', 11), ('e', 22), ('l', 33), ('l', 44), ('o', 55)]
res2 = zip(l, s1)
print(list(res2))  # [(11, 'h'), (22, 'e'), (33, 'l'), (44, 'l'), (55, 'o')]
zip多的元素會直接丟掉

6.12 迭代器

6.12.1 迭代器概述

  • 迭代器指的是迭代取值的工具

  • 迭代是一個重複的過程(不是單純重複),每次重複都是基於上一次的結果進行的

    count = 1

    while count < 5:

    ​ print(count)

    ​ count += 1

  • 使用迭代器的目的

    Ⅰ 為了找到一種通用的迭代取值的方案

    Ⅱ 節省記憶體

6.12.2 使用迭代器

  • 可迭代物件

    內建有__iter__方法的型別都稱之為可迭代物件

    但凡呼叫了__iter__方法,就會將該型別轉換成迭代物件

    ​ res = 值.__iter__() 或 res = iter(值)

  • 迭代器物件

    Ⅰ 內建有__next__方法

    Ⅱ 內建有__iter__方法(為了方便for迴圈)

  • 迭代器物件一定是可迭代物件

  • 可迭代物件不一定是迭代器物件

6.12.3 迭代器優缺點

  • 優點:

    Ⅰ 提供了一種不依賴索引的迭代取值方案

    Ⅱ 惰性計算,節省記憶體

  • 缺點:

    Ⅰ 取值麻煩

    Ⅱ 無法預測值的長度

    Ⅲ 是一次性的

6.12.4 可迭代物件

"hello".__iter__()              # 字串
[].__iter__()                   # 列表
(11,).__iter__()                # 元組
{"k1": 12, }.__iter__()         # 字典
{11, 22}.__iter__()             # 集合
f = open("a.txt", mode="wt")    # 檔案
f.__iter__()

6.12.5 迭代器物件

l = [11, 22, 33]
iter_l = l.__iter__()
print(iter_l.__next__())    # 11
print(iter_l.__next__())    # 22
print(iter_l.__next__())    # 33

info = {'name': "ccc", "age": 18, "gender": 'male'}
iter_info = info.__iter__()
print(iter_info.__next__())     # name
print(iter_info.__next__())     # age
print(iter_info.__next__())     # gender
print(iter_info.__next__())     # 丟擲異常StopIteration
迭代器的iter與本身
print(iter_info.__iter__().__iter__().__iter__() is iter_info)  # True
l = [11, 222, 333, 444, 555]
iter_l = iter(l)  # l.__iter__
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break
# 11
# 222
# 333
# 444
# 555

l = {'name': "ccc", "age": 18, "gender": 'male'}
iter_l = iter(l)
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break

print('--------------------')
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break
# name
# age
# gender
# --------------------

l = {'name': "ccc", "age": 18, "gender": 'male'}
iter_l = iter(l)
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break

print('--------------------')
iter_l = iter(l)
while True:
    try:
        print(next(iter_l))
    except StopIteration:
        break
# name
# age
# gender
# --------------------
# name
# age
# gender

l = [11, 222, 333, 444, 555]
iter_l = iter(l)
for item in iter_l:
    print(item)
print('====================')
for item in iter_l:
    print(item)
# 11
# 222
# 333
# 444
# 555
# ====================

l = [11, 222, 333, 444, 555]
iter_l = iter(l)
for item in iter_l:
    print(item)
print('====================')
iter_l = iter(l)
for item in iter_l:
    print(item)
# 11
# 222
# 333
# 444
# 555
# ====================
# 11
# 222
# 333
# 444
# 555
  • for迴圈原理
for迴圈原理:
1、呼叫可迭代物件.__iter__(),拿到一個迭代器物件
2、呼叫next(迭代物件),將返回值賦值變數item
3、迴圈往復,直到丟擲異常Stopiteration,for會檢測異常然後結束迴圈

f = open('a.txt', mode='rt', encoding='utf-8')
for line in f:
    print(line)
print('=======')
for line in f:
    print(line)
f.close()
# 111
#
# 222
#
# 333
#
# 444
# =======

6.13 生成器

6.13.1 基本概念

  • 生成器:生成器就是一種自定義迭代器(生成器內建iter和next方法)

  • 使用生成器是為了省記憶體

  • 使用生成器的方式:

    函式體內但凡出現yield關鍵字,呼叫函式不會觸發函式體程式碼的執行,而是會返回一個生成器物件

  • yield與return

    相同點:在返回值角度的用法一致

    不同點:Ⅰ yield可以返回多次值,return只能返回一次

    ​ Ⅱ yield可以暫停函式,然後可以用next方法觸發函式體程式碼的執行->協程(或用list)

6.13.2 基本使用

def func():
    print("111")
    yield 1
    print("222")
    yield 2
    print("333")
    yield 3
    print("444")
    
g = func()	      # 生成器本質就是一個迭代器
print(g)	      # <generator object func at 0x0000024D38B57900>

res = next(g)	      # 111
print(res)	      # 1
res = next(g)	      # 222
print(res)	      # 2
res = next(g)	      # 333
print(res)	      # 3
next(g)		      # 444 檢測異常StopIteration
g = func()
print(g)
for i in g:
    print(i)
# <generator object func at 0x0000020C4D217900>
# 111
# 1
# 222
# 2
# 333
# 3
# 444
def my_range(start, stop, step=1):
    while start < stop:
        yield start
        start += step

for i in my_range(0, 5, 2):
    print(i)
# 0
# 2
# 4

6.14 三元表示式

6.14.1 語法

res = "條件成立時返回的值" if "條件" else "條件不成立時返回的值"

6.14.2 使用場景

def max2(x, y):
    if x > y:
        return x
    else:
        return y
res = max2(1, 2)
# 可用三元表示式一行解決
x = 1
y = 2
res = x if x > y else y

6.14.3 生成式

[表示式 for i in 可迭代物件 if 條件]
{k:v for i in 可迭代物件 if 條件}
{k for i in 可迭代物件 if 條件}

6.14.3.1 列表生成式

l = []
for i in range(8):
    l.append(i)
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7]

# 可用列表生成式一行解決
l = [i for i in range(10)]
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7]
names = ["egon", "lxx_sb", "hxx_sb"]
name = []
for item in names:
    if item.endswith('sb'):
        name.append(item)
print(name)  # ['lxx_sb', 'hxx_sb']

# 可用列表生成式一行解決
names = ["egon", "lxx_sb", "hxx_sb"]
name = [name for name in names if name.endswith('sb')]
print(name)  # ['lxx_sb', 'hxx_sb']

6.14.3.2 字典生成式

res = {i: i for i in range(5) if i > 2}
print(res)  # {3: 3, 4: 4}

6.14.3.3 集合生成式

res = {i for i in range(5) if i > 2}
print(res, type(res))  # {3, 4} <class 'set'>

6.14.4 生成器表示式

6.14.4.1 建立生成器物件的兩種方式

  • 呼叫帶yield的函式

  • 生成器表示式,將列表生成式的[]換成()即可

(expression for item in iterable if condition)

6.14.4.2 較列表生成式的比較

  • 列表生成式返回的是一個列表,生成器表示式返回的是一個生成器物件

  • 生成器表示式更加節省記憶體(一次只產生一個值在記憶體中)

  • 如果要讀取一個大檔案的位元組數應該是基於生成器表示式的方式完成的

6.14.4.3 使用

res = (i for i in range(5))
print(res)		# <generator object <genexpr> at 0x00000274941E7900>
print(next(res))	# 0
print(next(res))	# 1
print(next(res))	# 2
print(next(res))	# 3
print(next(res))	# 4
print(next(res))	# 丟擲異常StopIteration
with open(r'a.txt', mode='rt', encoding='utf-8') as f:
    res = 0
    for line in f:
        res += len(line)
    print(res)		# 15
    
# 生成式表示式
with open(r'a.txt', mode='rt',encoding='uyf-8') as f:
    res = sum(len(line) for line in f)
    print(res)		# 15	依次執行next(res),然後累加到一起得到結果

6.14.5 附錄a.txt

111
222
333
444

6.15 程式導向程式設計

6.15.1 程式導向簡介

  • 核心是過程二字

  • 過程指的是解決問題的步驟,即先幹什麼後幹什麼再幹什麼

  • 基於程式導向開發程式好比是在設計一條條流水線,是一種機械式的思維方式,正好契合計算機的執行原理

  • 計算機執行原理:任何程式的執行最終都要轉換成cpu的指令流水按過程排程執行

    即無論採用什麼語言、何種程式設計正規化設計出的程式,最終的執行都是過程式的

6.15.2 總結

  • 優點:將複雜的問題流程化,進而簡單化

  • 缺點:程式的可擴充套件性差

6.15.3 應用場景

程式導向的程式設計一般用於那些功能一旦實現之後就很少需要改變的場景,
如果你只是寫一些簡單的指令碼,去做一些一次性任務,用程式導向去實現是極好的,
但如果你要處理的任務是複雜的,且需要不斷迭代和維護,那還是用物件導向最為方便。

6.16 函式遞迴

6.16.1 基本概念

  • 函式遞迴呼叫:在呼叫一個函式的過程中又呼叫了自己

  • 本質:迴圈的過程-->用函式來實現的迴圈

  • 遞迴呼叫必須在滿足某種條件下結束,不能無限遞迴呼叫下去

  • python不是函數語言程式設計語言,無法對遞迴進行尾遞迴優化

  • 遞迴的兩個階段:

    Ⅰ 回溯-->一層層往下爬

    Ⅱ 遞推-->一層層往上爬

6.16.2 示例

def f1():
    print("from f1")
    f1()

f1()  # 先遞迴執行,超過最大限制後報錯RecursionError
# 可用getrecursionlimit修改限制
import sys
print(sys.getrecursionlimit())		# 預設限制是1000
sys.setrecursionlimit(2000)		# 可修改,但一般不修改(受主機作業系統棧大小限制)
print(sys.getrecursionlimit(2000))      # 此時修改為2000

6.16.3 回溯與遞推

# 問題
某公司四個員工坐在一起,問第四個人薪水,他說比第三個人多1000,問第三個人薪水,第他說比第二個人多1000,問第二個人薪水,他說比第一個人多1000,最後第一人說自己每月5000,請問第四個人的薪水是多少?

# 思路解析
要知道第四個人的月薪,就必須知道第三個人的,第三個人的又取決於第二個人的,第二個人的又取決於第一個人的,而且每一個員工都比前一個多一千,數學表示式即
salary(4)=salary(3)+1000 
salary(3)=salary(2)+1000 
salary(2)=salary(1)+1000 
salary(1)=5000
總結為: 
salary(n)=salary(n-1)+1000 (n>1) 
salary(1)=5000 (n=1)

# 回溯階段
要求第n個員工的薪水,需要回溯得到(n-1)個員工的薪水,以此類推,直到得到第一個員工的薪水,此時,salary(1)已知,因而不必再向前回溯了。然後進入遞推階段:從第一個員工的薪水可以推算出第二個員工的薪水(6000),從第二個員工的薪水可以推算出第三個員工的薪水(7000),以此類推,一直推算出第第四個員工的薪水(8000)為止,遞迴結束。需要注意的一點是,遞迴一定要有一個結束條件,這裡n=1就是結束條件。

# 程式碼實現
def salary(n):
    if n == 1:
        return 5000
    return salary(n-1)+1000

s=salary(4)
print(s)  # 8000

# 程式分析
在未滿足n==1的條件時,一直進行遞迴呼叫,即一直回溯。而在滿足n==1的條件時,終止遞迴呼叫,即結束回溯,從而進入遞推階段,依次推導直到得到最終的結果。
items=[[1, 2], 3, [4, [5, [6, 7]]]]
def foo(items):
    for i in items:
        if isinstance(i, list):  #滿足未遍歷完items以及if判斷成立的條件時,一直進行遞迴呼叫
            foo(i) 
        else:
            print(i, end=' ')

foo(items)                       #列印結果1 2 3 4 5 6 7

6.13.4 總結

使用遞迴,我們只需要分析出要重複執行的程式碼邏輯,然後提取進入下一次遞迴呼叫的條件或者說遞迴結束的條件即可,程式碼實現起來簡潔清晰

相關文章