摘要:裝飾器的高級(jí)用法介紹下面這些旨在介紹裝飾器的一些更有趣的用法。裝飾器在定義時(shí)向函數(shù)和方法添加功能,它們不用于在運(yùn)行時(shí)添加功能。接受參數(shù)的裝飾器有時(shí),除了裝飾的函數(shù)之外,裝飾器還可以使用參數(shù)。
介紹
首先我要承認(rèn),裝飾器非常難!你在本教程中看到的一些代碼將會(huì)有一些復(fù)雜。大多數(shù)人在學(xué)習(xí)Python時(shí)都跟裝飾器做過斗爭(zhēng),所以如果這對(duì)你來說很奇怪,不要感到沮喪,因?yàn)橥瑯拥拇蠖鄶?shù)人都可以克服這種苦難。在本教程中,我將逐步介紹了解裝飾器的過程。首先我假設(shè)你已經(jīng)可以編寫基本函數(shù)和基本類。如果你不能做這些事,那么我建議你在回到這里之前先學(xué)習(xí)如何去做到編寫基本函數(shù)和基本類(除非你迷路了,在這種情況下你可以原諒)。
用例:計(jì)時(shí)函數(shù)執(zhí)行假設(shè)我們正在執(zhí)行一段代碼,執(zhí)行時(shí)間比我們想的還要長(zhǎng)一些。這段代碼由一堆函數(shù)調(diào)用組成,我們確信這些調(diào)用中至少有一個(gè)調(diào)用構(gòu)成了我們代碼中的瓶頸。我們?nèi)绾握业狡款i?現(xiàn)在有一個(gè)解決方案,就是我們現(xiàn)在要關(guān)注的解決方案,就是對(duì)函數(shù)執(zhí)行進(jìn)行計(jì)時(shí)。
讓我們從一個(gè)簡(jiǎn)單的例子開始。我們只有一個(gè)函數(shù)需要計(jì)時(shí),func_a
def func_a(stuff): do_important_things_1() do_important_things_2() do_important_things_3()
一種方法是將時(shí)鐘代碼放在每個(gè)函數(shù)調(diào)用周圍。所以就像這樣:
func_a(current_stuff)
看起來會(huì)更像這樣:
before = datetime.datetime.now() func_a(current_stuff) after = datetime.datetime.now() print ("Elapsed Time = {0}".format(after-before))
這樣就可以了。但是如果我們有多次調(diào)用func_a并且我們想要為所有這些計(jì)時(shí)會(huì)發(fā)生什么呢?我們可以用計(jì)時(shí)代碼包圍func_a的每個(gè)調(diào)用,但是這樣做也有不好的效果。它只準(zhǔn)備編寫一次計(jì)時(shí)代碼。因此,我們將其放在函數(shù)定義中,而不是將其放在函數(shù)之外。
def func_a(stuff): before = datetime.datetime.now() do_important_things_1() do_important_things_2() do_important_things_3() after = datetime.datetime.now() print("Elapsed Time = {0}".format(after-before))
這種方法的好處是:
我們將代碼放在一個(gè)地方,所以如果我們想要更改它(例如,如果我們想將經(jīng)過的時(shí)間存儲(chǔ)在數(shù)據(jù)庫(kù)或日志中)那么我們只需要在一個(gè)地方而不是每一個(gè)函數(shù)調(diào)用中更改它
我們不需要記住每次調(diào)用func_a都要寫四行代碼而不是一行,這是非常好的
好的,但是只需要計(jì)算一個(gè)函數(shù)的時(shí)間是不現(xiàn)實(shí)的。如果你需要對(duì)一件事進(jìn)行計(jì)時(shí),你很有可能需要至少對(duì)兩件事進(jìn)行計(jì)時(shí)。所以我們會(huì)選擇三個(gè)。
def func_a(stuff): before = datetime.datetime.now() do_important_things_1() do_important_things_2() do_important_things_3() after = datetime.datetime.now() print("Elapsed Time = {0}".format(after-before)) def func_b(stuff): before = datetime.datetime.now() do_important_things_4() do_important_things_5() do_important_things_6() after = datetime.datetime.now() print("Elapsed Time = {0}".format(after-before)) def func_c(stuff): before = datetime.datetime.now() do_important_things_7() do_important_things_8() do_important_things_9() after = datetime.datetime.now() print("Elapsed Time = {0}".format(after-before))
這看起來很糟糕。如果我們想要對(duì)8個(gè)函數(shù)進(jìn)行計(jì)時(shí)的時(shí)候怎么辦?然后我們決定將計(jì)時(shí)的信息存儲(chǔ)在日志文件中。然后我們決定建立一個(gè)更好的數(shù)據(jù)庫(kù)。我們這里需要的是將一種相同的代碼合并到func_a,func_b和func_c中的方法,這種方法不會(huì)讓我們到處復(fù)制粘貼代碼。
一個(gè)簡(jiǎn)單的繞道:返回函數(shù)的函數(shù)Python是一種非常特殊的語(yǔ)言,因?yàn)?strong>函數(shù)是第一類對(duì)象。這意味著一旦函數(shù)在作用域中被定義,它就可以傳遞給函數(shù),賦值給變量,甚至從函數(shù)返回。這個(gè)簡(jiǎn)單的事實(shí)是使python裝飾器成為可能的原因。查看下面的代碼,看看你是否可以猜出標(biāo)記為A,B,C和D的行會(huì)發(fā)生什么。
def get_function(): print ("inside get_function") def returned_function(): print("inside returned_function") return 1 print("outside returned_function") return returned_function returned_function() # A x = get_function() # B x # C x() # D
A
這一行給出了一個(gè)NameError并聲明returned_function不存在。但我們只是定義了它,對(duì)吧?你在這里需要知道的是,它是在get_function的范圍內(nèi)定義的。也就是說,在get_function里面定義了它。它不是在get_function之外。如果這讓你感到困惑,那么你可以嘗試使用該locals()函數(shù),并閱讀Python的范圍。
B
這行代碼打印出以下內(nèi)容:
inside get_function outside returned_function
此時(shí)Python不執(zhí)行returned_function的任何內(nèi)容。
C
這一行輸出:
也就是說,get_function()返回的值x本身就是一個(gè)函數(shù)。
嘗試再次運(yùn)行B和C行。請(qǐng)注意,每次重復(fù)此過程時(shí),返回的returned_function地址都是不同。每次調(diào)用get_function都會(huì)生成新的returned function。
d
因?yàn)?b>x是函數(shù),所以就可以調(diào)用它。調(diào)用x就是調(diào)用returned_function的一個(gè)實(shí)例。這里輸出的是:
inside returned_function 1
也就是說,它打印字符串,并返回值1。
回到時(shí)間問題你現(xiàn)在仍然在看么?如此我們有了新的知識(shí),那么我們?nèi)绾谓鉀Q我們的老問題?我建議我們創(chuàng)建一個(gè)函數(shù),讓我們調(diào)用它并稱為time_this,它將接收另一個(gè)函數(shù)作為參數(shù),并將參數(shù)函數(shù)封裝在某些計(jì)時(shí)代碼中。有點(diǎn)像:
def time_this(original_function): # 1 def new_function(*args,**kwargs): # 2 before = datetime.datetime.now() # 3 x = original_function(*args,**kwargs) # 4 after = datetime.datetime.now() # 5 print("Elapsed Time = {0}".format(after-before)) # 6 return x # 7 return new_function() # 8
我承認(rèn)它有點(diǎn)瘋狂,所以讓我們一行一行的看下去:
1這只是time_this的原型。time_this是一個(gè)函數(shù)就像任何其他函數(shù)一樣,并且只有一個(gè)參數(shù)。?2我們?cè)趦?nèi)部定義一個(gè)函數(shù)time_this。每當(dāng)time_this執(zhí)行時(shí)它都會(huì)創(chuàng)建一個(gè)新函數(shù)。?3計(jì)時(shí)代碼,就像之前一樣。?4我們調(diào)用原始函數(shù)并保留結(jié)果以供日后使用。?5,6剩余的計(jì)時(shí)代碼。?7new_function必須像原始函數(shù)一樣運(yùn)行,因此返回存儲(chǔ)的結(jié)果。?8返回在time_this中創(chuàng)建的函數(shù)。
現(xiàn)在我們要確保我們的函數(shù)是計(jì)時(shí)的:
def func_a(stuff): do_important_things_1() do_important_things_2() do_important_things_3() func_a = time_this(func_a) # <--------- def func_b(stuff): do_important_things_4() do_important_things_5() do_important_things_6() func_b = time_this(func_b) # <--------- def func_c(stuff): do_important_things_7() do_important_things_8() do_important_things_9() func_c = time_this(func_c) # <---------
看看func_a,當(dāng)我們執(zhí)行時(shí)func_a = time_this(func_a)我們用time_this返回的函數(shù)替換func_a。所以我們用一個(gè)函數(shù)替換func_A該函數(shù)執(zhí)行一些計(jì)時(shí)操作(上面的第3行),將func a的結(jié)果存儲(chǔ)在一個(gè)名為x的變量中(第4行),執(zhí)行更多的計(jì)時(shí)操作(第5行和第6行),然后返回func_a返回的內(nèi)容。換句話說func_a,仍然以相同的方式調(diào)用并返回相同的東西,它也只是被計(jì)時(shí)了。是不是感覺很整潔?
介紹裝飾器我們所做的工作很好,而且非常棒,但是很難看,非常難讀懂。所以Python可愛的作者給了我們一種不同的,更漂亮的寫作方式:
@time_this def func_a(stuff): do_important_things_1() do_important_things_2() do_important_things_3()
完全等同于:
def func_a(stuff): do_important_things_1() do_important_things_2() do_important_things_3() func_a = time_this(func_a)
這通常被稱為語(yǔ)法糖。@沒有什么神奇的。這只是一個(gè)已達(dá)成一致的慣例。沿著這條路上的某個(gè)地方?jīng)Q定了。
總結(jié)裝飾器只是一個(gè)返回函數(shù)的函數(shù)。如果這些東西看起來非常的 - 那么請(qǐng)確保以下主題對(duì)你有意義然后再回到本教程:
Python函數(shù)
范圍
Python作為第一類對(duì)象(甚至可以查找lambda函數(shù),它可能使它更容易理解)。
另一方面,如果你對(duì)更多的話題感興趣的話,你可能會(huì)發(fā)現(xiàn):
如裝飾類:
python @add_class_functionality class MyClass: ...
具有更多參數(shù)的裝飾器, 例如:
python @requires_permission(name="edit") def save_changes(stuff): ...
下面就是我要介紹的高級(jí)裝飾器的主題。
裝飾器的高級(jí)用法 介紹下面這些旨在介紹裝飾器的一些更有趣的用法。具體來說,如何在類上使用裝飾器,以及如何將額外的參數(shù)傳遞給裝飾器函數(shù)。
裝飾者與裝飾者模式裝飾器模式是一種面向?qū)ο蟮脑O(shè)計(jì)模式,其允許動(dòng)態(tài)地將行為添加到現(xiàn)有的對(duì)象當(dāng)中。當(dāng)你裝飾對(duì)象時(shí),你將以獨(dú)立于同類的其他實(shí)例方式擴(kuò)展它的功能。
Python裝飾器不是裝飾器模式的實(shí)現(xiàn)。Python裝飾器在定義時(shí)向函數(shù)和方法添加功能,它們不用于在運(yùn)行時(shí)添加功能。裝飾器模式本身可以在Python中實(shí)現(xiàn),但由于Python是Duck-teped的,因此這是一件非常簡(jiǎn)單的事情。
一個(gè)基本的裝飾這是裝飾器可以做的一個(gè)非常基本的例子。我只是把它作為一個(gè)參考點(diǎn)。在繼續(xù)之前,請(qǐng)確保你完全理解這段代碼。
def time_this(original_function): def new_function(*args,**kwargs): import datetime before = datetime.datetime.now() x = original_function(*args,**kwargs) after = datetime.datetime.now() print ("Elapsed Time = {0}".format(after-before)) return x return new_function @time_this def func_a(stuff): import time time.sleep(3) func_a(1)接受參數(shù)的裝飾器
有時(shí),除了裝飾的函數(shù)之外,裝飾器還可以使用參數(shù)。這種技術(shù)經(jīng)常用于函數(shù)注冊(cè)等事情。一個(gè)著名的例子是Pyramid Web應(yīng)用程序框架中的視圖配置。例如:
@view_config(route_name="home", renderer="templates/mytemplate.pt") def my_view(request): return {"project": "hello decorators"}
假設(shè)我們有一個(gè)應(yīng)用程序,用戶可以登錄并與一個(gè)漂亮的gui(圖形用戶界面)進(jìn)行交互。用戶與gui的交互觸發(fā)事件,而這些事件導(dǎo)致Python函數(shù)被執(zhí)行。讓我們假設(shè)有很多用戶使用這個(gè)應(yīng)用程序,并且他們有許多不同的權(quán)限級(jí)別。執(zhí)行不同的功能需要不同的權(quán)限類型。例如,考慮以下功能:
#這些功能是存在的 def current_user_id(): """ 此函數(shù)返回當(dāng)前登錄的用戶ID,如果沒有經(jīng)過身份驗(yàn)證,則返回None """ def get_permissions(iUserId): """ 返回給定用戶的權(quán)限字符串列表,例如 ["logged_in","administrator","premium_member"] """ #我們需要對(duì)這些函數(shù)進(jìn)行權(quán)限檢查 def delete_user(iUserId): """ 刪除具有給定ID的用戶,只有管理員權(quán)限才能訪問此函數(shù) """ def new_game(): """ 任何已登錄的用戶都可以啟動(dòng)一個(gè)新游戲 """ def premium_checkpoint(): """ 保存游戲進(jìn)程,只允許高級(jí)成員訪問 """
實(shí)現(xiàn)這些權(quán)限的一種方法是創(chuàng)建多個(gè)裝飾器,例如:
def requires_admin(fn): def ret_fn(*args,**kwargs): lPermissions = get_permissions(current_user_id()) if "administrator" in lPermissions: return fn(*args,**kwargs) else: raise Exception("Not allowed") return ret_fn def requires_logged_in(fn): def ret_fn(*args,**kwargs): lPermissions = get_permissions(current_user_id()) if "logged_in" in lPermissions: return fn(*args,**kwargs) else: raise Exception("Not allowed") return ret_fn def requires_premium_member(fn): def ret_fn(*args,**kwargs): lPermissions = get_permissions(current_user_id()) if "premium_member" in lPermissions: return fn(*args,**kwargs) else: raise Exception("Not allowed") return ret_fn @requires_admin def delete_user(iUserId): """ 刪除具有給定Id的用戶,只有具有管理員權(quán)限的用戶才能訪問此函數(shù) """ @requires_logged_in def new_game(): """ 任何已登錄的用戶都可以啟動(dòng)一個(gè)新游戲 """ @requires_premium_member def premium_checkpoint(): """ 保存游戲進(jìn)程,只允許高級(jí)成員訪問 """
但這太可怕了。它需要大量的復(fù)制粘貼,并且每個(gè)裝飾器需要不同的名稱,如果對(duì)權(quán)限的檢查方式進(jìn)行了任何更改,則必須更新每個(gè)裝飾器。有一個(gè)裝飾器可以完成這三個(gè)工作不是很好嗎?
為此,我們需要一個(gè)返回裝飾器的函數(shù):
def requires_permission(sPermission): def decorator(fn): def decorated(*args,**kwargs): lPermissions = get_permissions(current_user_id()) if sPermission in lPermissions: return fn(*args,**kwargs) raise Exception("permission denied") return decorated return decorator def get_permissions(iUserId): #這樣裝飾器就不會(huì)拋出NameError return ["logged_in",] def current_user_id(): #名稱錯(cuò)誤也是如此 return 1 #現(xiàn)在我們可以進(jìn)行裝飾了 @requires_permission("administrator") def delete_user(iUserId): """ 刪除具有給定Id的用戶,只有具有管理員權(quán)限的用戶才能訪問此函數(shù) """ @requires_permission("logged_in") def new_game(): """ 任何已登錄的用戶都可以啟動(dòng)一個(gè)新游戲 """ @requires_permission("premium_member") def premium_checkpoint(): """ 保存游戲進(jìn)程,只允許高級(jí)成員訪問 """
嘗試調(diào)用delete_user,new_game和premium_checkpoint看看會(huì)發(fā)生什么。
premium_checkpoint和delete_user都在消息“權(quán)限被拒絕”的情況下引發(fā)異常,new_game執(zhí)行得很好(但沒有太多的作用)。
下面是裝飾器的一般形式,帶有參數(shù)和使用說明:
def outer_decorator(*outer_args,**outer_kwargs): def decorator(fn): def decorated(*args,**kwargs): do_something(*outer_args,**outer_kwargs) return fn(*args,**kwargs) return decorated return decorator @outer_decorator(1,2,3) def foo(a,b,c): print (a) print (b) print (c) foo()
這相當(dāng)于:
def decorator(fn): def decorated(*args,**kwargs): do_something(1,2,3) return fn(*args,**kwargs) return decorated return decorator @decorator def foo(a,b,c): print (a) print (b) print (c) foo()裝飾課程
裝飾器不僅限于對(duì)函數(shù)進(jìn)行操作,它們也可以對(duì)類進(jìn)行操作。比方說,我們有一個(gè)類可以做很多非常重要的事情,我們想要把它所做的一切都進(jìn)行計(jì)時(shí)。然后我們可以使用time_this像以前一樣使用裝飾器:
class ImportantStuff(object): @time_this def do_stuff_1(self): ... @time_this def do_stuff_2(self): ... @time_this def do_stuff_3(self): ...
這樣就可以了。但是這個(gè)類中還有一些額外的代碼行。如果我們寫一些更多的類方法并忘記裝飾它們中的一個(gè)呢?如果我們決定不再為進(jìn)行計(jì)時(shí)怎么辦?這里肯定存在人為錯(cuò)誤的空間。這樣編寫它會(huì)好得多:
@time_all_class_methods class ImportantStuff: def do_stuff_1(self): ... def do_stuff_2(self): ... def do_stuff_3(self): ...
如你所知,該代碼相當(dāng)于:
class ImportantStuff: def do_stuff_1(self): ... def do_stuff_2(self): ... def do_stuff_3(self): ... ImportantStuff = time_all_class_methods(ImportantStuff)
那么time_all_class_methods是如何工作的? 首先,我們知道它需要將一個(gè)類作為參數(shù),并返回一個(gè)類。我們也知道返回類的函數(shù)應(yīng)該與原始ImportantStuff類的函數(shù)相同。也就是說,我們?nèi)匀幌M胍瓿芍匾氖虑椋覀冃枰M(jìn)行計(jì)時(shí)。以下是我們將如何做到這一點(diǎn):
def time_this(original_function): print ("decorating") def new_function(*args,**kwargs): print ("starting timer") import datetime before = datetime.datetime.now() x = original_function(*args,**kwargs) after = datetime.datetime.now() print ("Elapsed Time = {0}".format(after-before)) return x return new_function def time_all_class_methods(Cls): class NewCls(object): def __init__(self,*args,**kwargs): self.oInstance = Cls(*args,**kwargs) def __getattribute__(self,s): """ 每當(dāng)訪問NewCls對(duì)象的任何屬性時(shí),都會(huì)調(diào)用這個(gè)函數(shù)。這個(gè)函數(shù)首先嘗試 從NewCls獲取屬性。如果失敗,則嘗試從self獲取屬性。oInstance(一個(gè) 修飾類的實(shí)例)。如果它設(shè)法從self獲取屬性。oInstance, 屬性是一個(gè)實(shí)例方法,然后應(yīng)用" time_this "。 """ try: x = super(NewCls,self).__getattribute__(s) except AttributeError: pass else: return x x = self.oInstance.__getattribute__(s) if type(x) == type(self.__init__): # 這是一個(gè)實(shí)例方法 return time_this(x) # 這等價(jià)于用time_this修飾方法 else: return x return NewCls #現(xiàn)在讓我們做一個(gè)虛擬類來測(cè)試它: @time_all_class_methods class Foo(object): def a(self): print ("entering a") import time time.sleep(3) print ("exiting a") oF = Foo() oF.a()結(jié)論
在裝飾器的高級(jí)用法中,我向你展示了使用Python裝飾器的一些技巧 - 我已經(jīng)向你展示了如何將參數(shù)傳遞給裝飾器,以及如何裝飾類。但這仍然只是冰山的一角。在各種奇怪的情況下,有大量的方法用于裝飾器。你甚至可以裝飾你的裝飾器(但如果你到達(dá)那一點(diǎn),那么做一個(gè)全面的檢查可能是個(gè)好主意)。Python同時(shí)內(nèi)置了一些值得了解的裝飾器,例如裝飾器staticmethod及classmethod。
接下來要怎么做?除了我在這篇文章中向你展示的內(nèi)容外,通常不需要對(duì)裝飾器執(zhí)行任何更復(fù)雜的操作。如果你對(duì)更改類功能的更多方法感興趣,那么我建議閱讀有關(guān)繼承和一般OO設(shè)計(jì)原則的數(shù)據(jù)。或者,如果你真的想學(xué)會(huì)他們,那么請(qǐng)閱讀元類(但同樣,處理這些東西幾乎不需要)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/45051.html
摘要:常規(guī)的使用來統(tǒng)計(jì)一段代碼運(yùn)行時(shí)間的例子輸出結(jié)果總結(jié)其實(shí)是一門特別人性化的語(yǔ)言,但凡在工程中經(jīng)常遇到的問題,處理起來比較棘手的模式基本都有對(duì)應(yīng)的比較優(yōu)雅的解決方案。 python的高級(jí)特性 名詞與翻譯對(duì)照表 generator 生成器 iterator 迭代器 collection 集合 pack/unpack 打包/解包 decorator 裝飾器 context manager ...
摘要:在中,裝飾器一般用來修飾函數(shù),實(shí)現(xiàn)公共功能,達(dá)到代碼復(fù)用的目的。智能裝飾器上節(jié)介紹的寫法,嵌套層次較多,如果每個(gè)類似的裝飾器都用這種方法實(shí)現(xiàn),還是比較費(fèi)勁的腦子不夠用,也比較容易出錯(cuò)。假設(shè)有一個(gè)智能裝飾器,修飾裝飾器,便可獲得同樣的能力。 在Python中,裝飾器一般用來修飾函數(shù),實(shí)現(xiàn)公共功能,達(dá)到代碼復(fù)用的目的。在函數(shù)定義前加上@xxxx,然后函數(shù)就注入了某些行為,很神奇!然而,這只...
摘要:為了避免重復(fù)調(diào)用,可以適當(dāng)?shù)刈鼍彺妫难b飾器可以完美的完成這一任務(wù)。這意味著我們可以為方法創(chuàng)建裝飾器,只是要記得考慮。裝飾器封裝了函數(shù),這使得調(diào)試函數(shù)變得困難。另外,使用裝飾器去管理緩存和權(quán)限。 原文地址 之前用python簡(jiǎn)單寫了一下斐波那契數(shù)列的遞歸實(shí)現(xiàn)(如下),發(fā)現(xiàn)運(yùn)行速度很慢。 def fib_direct(n): assert n > 0, invalid n ...
摘要:今天我們一起探討一下裝飾器的另類用法。語(yǔ)法回顧開始之前我們?cè)賹⒀b飾器的語(yǔ)法回顧一下。例子本身只是演示了裝飾器的一種用法,但不是推薦你就這樣使用裝飾器。類裝飾器在以前,還不支持類裝飾器。 之前有比較系統(tǒng)介紹過Python的裝飾器(請(qǐng)查閱《詳解Python裝飾器》),本文算是一個(gè)補(bǔ)充。今天我們一起探討一下裝飾器的另類用法。 語(yǔ)法回顧 開始之前我們?cè)賹ython裝飾器的語(yǔ)法回顧一下。 @d...
摘要:接下來手工實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的裝飾器原型,緊接著引入中的裝飾器語(yǔ)法。最后還列出了一些裝飾器的高級(jí)用法,包括給裝飾器傳遞參數(shù)等。讀完整個(gè)答案,一定能對(duì)裝飾器有較深的理解,并且知道理解裝飾器的思考過程。 作為一名程序員,如果沒有聽過 Stackoverflow,那么你最好去面壁思過一下。程序員最需要閱讀的一本編程書籍(其實(shí)編程書留下這本就夠了!): showImg(https://segmen...
閱讀 917·2021-11-08 13:22
閱讀 2841·2021-09-29 09:45
閱讀 2824·2021-09-09 11:52
閱讀 2257·2019-08-30 13:20
閱讀 3739·2019-08-29 13:28
閱讀 1356·2019-08-29 12:32
閱讀 2719·2019-08-29 11:10
閱讀 1643·2019-08-26 13:34