Python高階 -- 11 閉包、裝飾器

DJTUDaker發表於2018-03-19

一、閉包


1、什麼是閉包


# 定義一個函式
def test(number):

    """
		在函式內部再定義一個函式,並且這個函式用到了外邊函式的變數,
		同時,外部函式的返回值是內部函式的引用
		那麼將這個函式(內部定義的那個函式)以及用到的一些變數稱之為閉包
    """
    def test_in(number_in):
        print("in test_in 函式, number_in is %d" % number_in)
        return number + number_in
    # 其實這裡返回的就是閉包的結果
    return test_in


# 給test函式賦值,這個20就是給引數number
ret = test(20)

# 注意這裡的100其實是給test函式內部定義的test_in函式的引數number_in賦值
# ret是test函式的返回值,即返回的是test函式內部的定義的test_in函式的引用
print(ret(100))

#注 意這裡的200其實給引數number_in
print(ret(200))


二、裝飾器


1、先理解下面程式碼


#### 第一波 ####
def foo():
    print('foo')

foo  # 表示是函式的引用
foo()  # 表示執行foo函式

#### 第二波 ####
def foo():
    print('foo')

foo = lambda x: x + 1

foo()  # 執行lambda表示式,而不再是原來的foo函式,因為foo這個名字被重新指向了另外一個匿名函式
# 以上程式碼是將lambda表示式賦值給了foo變數,此時,執行foo(),代表的是執行lambda表示式


        函式名僅僅是個變數,只不過指向了定義的函式而已,所以才能通過 函式名()呼叫;如果 變數被修改了,即函式名=xxx ,那麼當在執行 函式名() 時,呼叫的就不是之前的那個函式了,而是被賦值之後的函式


2、裝飾器


使用閉包引入裝飾器概念


def validation(fun):

	# 定義一個內部函式
	def doValidation():
		# 執行前置校驗
		print("放在 fun() 之前的內容是屬於前置校驗")

		# 執行fun函式
		fun()

		# 執行後置校驗
		print("放在 fun() 之後的內容是屬於後置校驗")

	# 外部函式返回值是內部函式的引用
	return doValidation


def test():
	print("test方法中加入校驗")


"""
	例項化外部函式,引數是其他函式(test函式)的引用
	由於外部函式(validation)中還有一個函式定義,
	因此,例項化外部函式的時候,會定義一個內部函式,返回的是內部函式的引用
	而由於,在內部函式中使用其他函式(test函式)的引用來例項化了一個其他函式(test函式)物件,
	因此,例項化內部函式的時候,會進行其他函式(test函式)的例項化操作
	
"""
test = validation(test)	# 此時的test是內部函式的引用

"""
	執行內部函式,由於內部函式中其他函式(test)的例項化操作
	因此,在內部函式中所執行的程式碼(本例中為列印操作)可以放在其他函式(test)前或者後進行操作
	即:達到了在執行其他函式前(或者後)進行校驗的目的
"""
test()

"""
	執行結果:
		放在 fun() 之前的內容是屬於前置校驗
		test方法中加入校驗
		放在 fun() 之後的內容是屬於後置校驗
"""


使用裝飾器來進行上述操作的程式碼:


def validation(fun):

	# 定義一個內部函式
	def doValidation():
		# 執行前置校驗
		print("放在 fun() 之前的內容是屬於前置校驗")

		# 執行fun函式
		fun()

		# 執行後置校驗
		print("放在 fun() 之後的內容是屬於後置校驗")

	# 外部函式返回值是內部函式的引用
	return doValidation

@validation
def test():
	print("test方法中加入校驗")


"""
	例項化外部函式,引數是其他函式(test函式)的引用
	由於外部函式(validation)中還有一個函式定義,
	因此,例項化外部函式的時候,會定義一個內部函式,返回的是內部函式的引用
	而由於,在內部函式中使用其他函式(test函式)的引用來例項化了一個其他函式(test函式)物件,
	因此,例項化內部函式的時候,會進行其他函式(test函式)的例項化操作
	

	⭐⭐⭐在其他函式上使用 @外部函式名 ,這種方式可以替代以下程式碼來進行test方法執行前後的校驗
	test = validation(test)		# 返回值是內部函式的引用

"""


"""
	執行內部函式,由於內部函式中其他函式(test)的例項化操作
	因此,在內部函式中所執行的程式碼(本例中為列印操作)可以放在其他函式(test)前或者後進行操作
	即:達到了在執行其他函式前(或者後)進行校驗的目的
"""
test()

"""
	執行結果:
		放在 fun() 之前的內容是屬於前置校驗
		test方法中加入校驗
		放在 fun() 之後的內容是屬於後置校驗
"""


解釋:在方法上加上 @其他方法名 的方式即為裝飾器的使用,而其他方法是一個包含了內部函式的一個函式,在內部函式中有方法的例項化操作,這種方式就達到了裝飾器的效果


3、對帶有引數的方法進行裝飾器


不使用裝飾器的寫法:


def validation(fun):

	# 定義一個內部函式
	def doValidation(args):
		# 執行前置校驗
		print("放在 fun() 之前的內容是屬於前置校驗")

		# 執行fun函式
		fun(args)

		# 執行後置校驗
		print("放在 fun() 之後的內容是屬於後置校驗")

	# 外部函式返回值是內部函式的引用
	return doValidation


def test(args):
	print("test方法中加入校驗 %s" % args)


test = validation(test)	# 返回值是是內部函式的引用


test(100)


使用裝飾器的寫法:


def validation(fun):

	# 定義一個內部函式
	def doValidation(args):
		# 執行前置校驗
		print("放在 fun() 之前的內容是屬於前置校驗")

		# 執行fun函式
		fun(args)

		# 執行後置校驗
		print("放在 fun() 之後的內容是屬於後置校驗")

	# 外部函式返回值是內部函式的引用
	return doValidation

@validation
def test(args):
	print("test方法中加入校驗 %s" % args)


# test = validation(test)	# 返回值是是內部函式的引用


test(100)


4、不定長引數的函式裝飾器


def validation(fun):

	# 定義一個內部函式
	def doValidation(*args, **kwargs):
		# 執行前置校驗
		print("放在 fun() 之前的內容是屬於前置校驗")

		# 執行fun函式
		fun(*args, **kwargs)

		# 執行後置校驗
		print("放在 fun() 之後的內容是屬於後置校驗")

	# 外部函式返回值是內部函式的引用
	return doValidation

@validation  	# test = validation(test)	 返回值是是內部函式的引用
def test(num, *args, **kwargs):
	print("test方法中引數1 %s" % num)
	print("test方法中引數2 : " , args)
	print("test方法中引數3 : " , kwargs)


test(100)
print("-" * 50)
test(100, 200)
print("-" * 50)
test(100, 200, 300, name='李四')


"""
	執行結果:

		放在 fun() 之前的內容是屬於前置校驗
		test方法中引數1 100
		test方法中引數2 :  ()
		test方法中引數3 :  {}
		放在 fun() 之後的內容是屬於後置校驗
		--------------------------------------------------
		放在 fun() 之前的內容是屬於前置校驗
		test方法中引數1 100
		test方法中引數2 :  (200,)
		test方法中引數3 :  {}
		放在 fun() 之後的內容是屬於後置校驗
		--------------------------------------------------
		放在 fun() 之前的內容是屬於前置校驗
		test方法中引數1 100
		test方法中引數2 :  (200, 300)
		test方法中引數3 :  {'name': '李四'}
		放在 fun() 之後的內容是屬於後置校驗

"""


5、帶有返回值的函式的裝飾器


帶有返回值的函式的裝飾器亦是通用裝飾器(可變引數,有返回值的裝飾器)的寫法:


def validation(fun):

	# 定義一個內部函式
	def doValidation(*args, **kwargs):
		# 執行前置校驗
		print("放在 fun() 之前的內容是屬於前置校驗")

		# 執行fun函式
		return fun(*args, **kwargs)

		# 執行後置校驗
		print("放在 fun() 之後的內容是屬於後置校驗")

	# 外部函式返回值是內部函式的引用
	return doValidation

@validation  	# test = validation(test)	 返回值是是內部函式的引用
def test(num, *args, **kwargs):
	print("test方法中引數1 %s" % num)
	print("test方法中引數2 : " , args)
	print("test方法中引數3 : " , kwargs)
	return "OK"


ret = test(100)
print(ret)
print("-" * 50)

ret = test(100, 200)
print(ret)
print("-" * 50)

ret = test(100, 200, 300, name='李四')
print(ret)

"""
	執行結果:

		放在 fun() 之前的內容是屬於前置校驗
		test方法中引數1 100
		test方法中引數2 :  ()
		test方法中引數3 :  {}
		OK
		--------------------------------------------------
		放在 fun() 之前的內容是屬於前置校驗
		test方法中引數1 100
		test方法中引數2 :  (200,)
		test方法中引數3 :  {}
		OK
		--------------------------------------------------
		放在 fun() 之前的內容是屬於前置校驗
		test方法中引數1 100
		test方法中引數2 :  (200, 300)
		test方法中引數3 :  {'name': '李四'}
		OK

"""


6、多個裝飾器對一個函式進行裝飾


def zhuangshi_1(fun):

	print("定義裝飾1")
	# 定義一個內部函式
	def neibu_1(*args, **kwargs):
		# 執行前置校驗
		print("執行 zhuangshi_1 中的裝飾內容")

		# 執行fun函式
		return fun(*args, **kwargs)

	# 外部函式返回值是內部函式的引用
	return neibu_1


def zhuangshi_2(fun):

	print("定義裝飾2")
	# 定義一個內部函式
	def neibu_2(*args, **kwargs):
		# 執行前置校驗
		print("執行 zhuangshi_2 中的裝飾內容")

		# 執行fun函式
		return fun(*args, **kwargs)

	# 外部函式返回值是內部函式的引用
	return neibu_2


@zhuangshi_1
@zhuangshi_2
def test():
	pass

test()

"""
	執行結果:
		定義裝飾2
		定義裝飾1
		執行 zhuangshi_1 中的裝飾內容
		執行 zhuangshi_2 中的裝飾內容

	解釋:
		定義裝飾器的時候,先執行離方法最近的裝飾器的定義
		執行裝飾器的時候,先執行最上層的裝飾器

"""


7、多個裝飾器裝飾一個函式的應用


def html_1(func):
	def td():
		return "<td>" + func() +"</td>"
	return td


def html_2(func):
	def p1():
		return "<p1>" + func() + "</p1>"
	return p1


@html_1
@html_2
def test():
	return "hello world"

print(test())

	
"""
	執行結果:

		<td><p1>hello world</p1></td>
"""


8、帶有引數的裝飾器


def parent(arg):
	def validation(func):
		def call_func(*args, **kwargs):
			if arg == 1:
				print("level 1")
			elif arg == 2:
				print("level 2")
			return func()
		return call_func
	return validation

"""
	⭐⭐⭐:帶有引數的裝飾器呼叫的時候:
		首先會將引數當作實參,傳遞到裝飾器中,進行第一層函式的呼叫,並返回第二層函式的引用;
		然後,將第二層函式的引用當作真正的裝飾器,進行裝飾


		以下呼叫中,首先@parent(1)會先執行parent方法,把引數1傳遞進去,返回的是validation的引用
		然後,使用validation作為真正的裝飾器,對test1方法進行裝飾

"""

@parent(1)
def test1():
	return "OK"
test1()


@parent(2)
def test2():
	return "OK"
test2()


9、用類對函式進行裝飾


class Test(object):

	def __init__(self, func):
		self.func = func

	def __call__(self):
		print("對函式進行裝飾的方法")
		return self.func()


@Test
def demo():
	return "呵呵"

print(demo())


相關文章