Python裝飾器徹底解讀

1 引言

裝飾器(Decorators)多是Python中最難掌握的概念之一了,也是最具Pythonic特點的技巧,深刻理解並應用裝飾器,你會更加感慨——人生苦短,我用Python。mysql

2 初步理解裝飾器

2.1 什麼是裝飾器

在解釋什麼是裝飾器以前,咱們有必要回顧一下Python中的一些思想和概念。咱們都知道,Python是一門面向對象的語言,Python基本思想就是一些皆對象,數據類型是對象、類是對象、類實例也是對象……對於接下來咱們要說的裝飾器而言,最重要的是,函數也是對象!sql

你沒看錯,函數也和數據類型等概念同樣,都是對象,那麼既然數據類型能夠進行賦值操做,那麼函數是否是也能夠賦值呢?固然能夠!編程

def do_something():
    print('完成一些功能')

if __name__ == '__main__':
    do = do_something
    do()

輸出:函數

完成一些功能工具

看,本來咱們定義的函數名是do_something,但咱們把函數賦值給do後,也能夠經過do()調用函數。不只如此,函數當作參數傳遞給其餘函數:spa

def do_something():
    print('正在完成功能')

def func(f):
    f()

if __name__ == '__main__':
    func(do_something)

輸出:code

完成一些功能orm

正是由於Python中函數既能夠賦值給其餘變量名,也能夠當作參數參數進其餘函數,因此,上面的代碼沒有任何問題。對象

固然,畢竟被稱呼爲函數,有別有變量之類的概念,因此它也有本身的特性,例如在函數內部,還能夠定義函數,甚至做爲返回值返回:blog

def func():
    def inner_func():
        print('我是內部函數')
    return inner_func

if __name__ == '__main__':
    fun1 = func()
    fun1()

輸出結果:

我是內部函數

咱們來總結一下函數的這幾個特性:

  • 能夠賦值給其餘變量;

  • 能夠做爲參數傳遞給其餘函數;

  • 能夠在內部定義一個函數;

  • 能夠當作返回值返回。

不要疑惑我爲何要着重說明Python中函數的這幾個特性,由於裝飾器中正是這幾個特性爲基石。爲何這麼說呢?從本質上來講,裝飾器就是一個函數,它也具備咱們上面說到的4個特性,並且充分利用了這4個特性。裝飾器接受一個普通函數做爲參數,並在內部定義了一個函數,在這個內部函數中實現了一些功能,並調用了傳遞進來的函數,最後將內部函數做爲返回值返回。若是一個函數把這個步驟全走了一遍,咱們就能夠認爲,這個函數是一個裝飾器函數,或者說裝飾器。

咱們來動手寫一個裝飾器:

def func(f):
    def inner_func():
        print('{}函數開始運行……'.format(f.__name__))
        f()
        print('{}函數結束運行……'.format(f.__name__))
    return inner_func

def do_something():
    print('正在完成功能')

if __name__ == '__main__':
    do_something = func(do_something)
    do_something()

輸出結果:

do_something函數開始運行……

正在完成功能

do_something函數結束運行……

在上面代碼中,咱們將do_something方法做爲參數傳遞給func方法,在func方法內部咱們定義了一個inner_func方法,並在這個方法中添加了函數開始執行和結束執行的提示,在func方法最後,咱們將inner_func做爲參數返回,更加值得一說的是,咱們從新將func函數的返回值賦給了do_something,因此,最後一行咱們再次調用的do_something方法已經再也不是最初的do_something函數,而是func方法內定義的inner_func函數,因此最後執行do_something函數時,會有函數開始執行和結束執行的提示功能,然後面再調用do_something函數時,也都直接使用do_something()。

正如你所預料,func函數就是一個裝飾器,捋一捋你會發現,func函數把咱們上面說過的全部特性、步驟全實現了。

若是你在別處見過Python裝飾器的使用,你可能會疑惑,咱們實現的func裝飾器跟你見過的裝飾器不都同樣,由於在實際應用中,裝飾器大可能是與「@」符號結合起來使用。其實「@」符號所實現的功能就是 do_something = func(do_something)這行代碼的功能。來,咱們嘗試一下使用「@」:

def func(f):
    def inner_func():
        print('{}函數開始運行……'.format(f.__name__))
        f()
        print('{}函數結束運行……'.format(f.__name__))
    return inner_func

@func def do_something():
    print('正在完成功能')

if __name__ == '__main__':
    do_something()

輸出結果:

do_something函數開始運行……

正在完成功能

do_something函數結束運行……

以前咱們知道,func函數就是一個裝飾器,因此使用「@」符號時,咱們只須要在被裝飾的函數前面加上「@func」就表示該函數被func裝飾器裝飾,在須要處直接調用do_something函數便可。

2.2 爲何要用裝飾器

在上面代碼中,咱們寫了一個裝飾器func,在這個裝飾器中,使用裝飾器的好處就已經初見端倪了。

(1)能夠在不對被裝飾函數作任何修改的前提下,給被裝飾函數附加上一些功能。使用@func對do_something函數進行裝飾時,咱們沒有對do_something函數的代碼作什麼的改變,可是被裝飾後的do_something函數卻多了開始運行和結束運行的功能。

(2)不改變被裝飾函數的調用方式。在被裝飾前,咱們經過do_something()調用這個函數,被裝飾後,仍是經過do_something()調用這個函數。

(3)代碼更加精簡。在上面代碼中,咱們只是用@func裝飾了do_something一個函數,可是若是有多個函數須要添加開始運行和結束運行的提示功能,若是不用裝飾器,那麼就須要對每個函數進行修改,則工做量和須要修改的代碼量……用了裝飾器以後,只須要在須要添加這一功能的函數前面添加@func就能夠了。

一言以蓋之,裝飾器能夠在不改變原函數調用方式和代碼狀況下,爲函數添加一些功能,使代碼更加精簡。

咱們在寫一個裝飾器來加深一下理解。相比你們都寫過代碼來統計一個函數的運行時間的功能,咱們使用裝飾器來實現一下這個功能:

import time
def timmer(f):
    def inner_func():
        start_time = time.time()
        f()
        end_time = time.time()
        print('{}函數運行消耗時間爲:{}'.format(f.__name__, end_time-start_time))
    return inner_func

@timmer
def do_something():
    print('do_something函數運行……')
    time.sleep(1)

if __name__ == '__main__':
    do_something()

輸出結果:

do_something函數運行……

do_something函數運行消耗時間爲:1.000662088394165

在上面例子中,咱們首先定義了一個計時裝飾器timmer,當須要統計某個函數運行時間時,只須要在函數定義時,在前面添加一行寫上@timmer便可,例如上面對do_something函數運行時間進行統計,對do_something原來要實現什麼功能就繼續實現這一功能,原來代碼該怎樣還怎樣,該怎麼調用還怎麼調用。因此說,使用裝飾器能夠在不改變原函數代碼和調用方式的狀況下附加上其餘功能。

若是你閱讀到了這裏,我想你對裝飾器已經有了初步的理解。接下來,咱們繼續聊一聊更加複雜的裝飾器。

3 深刻理解裝飾器

3.1 被裝飾的函數帶返回值

咱們上面寫的兩個裝飾器所裝飾的do_something函數是沒有返回值的,但大多數函數可都是有返回值的。針對有返回值的函數,裝飾器該怎麼寫呢?

def func(f):
    def inner_func():
        print('{}函數開始運行……'.format(f.__name__))
        ret = f()
        print('{}函數結束運行……'.format(f.__name__))
        return ret  # 這裏返回值
    return inner_func

@func
def do_something():
    print('正在完成功能')
    return '我是返回值'

if __name__ == '__main__':
    ret = do_something()
    print(ret)

輸出結果:

do_something函數開始運行……

正在完成功能

do_something函數結束運行……

我是返回值

咱們知道,被裝飾後的do_something函數其實再也不是最初的do_something函數,而是裝飾器內部定義的inner_func函數,因此,被裝飾的函數的返回值只須要經過裝飾器內部定義的inner_func函數返回返回便可便可。有點繞,不過對着上面的代碼應該好理解。

3.2 被裝飾函數帶參數

對於裝飾器,咱們要深入理解一件事:以上面的裝飾器func和被裝飾函數do_something爲例,被裝飾後的do_something函數已經再也不是原來的do_something函數,而是裝飾器內部的inner_func函數。這句話我已經在上文中我已經不止提過一次,由於真的很重要。若是被裝飾的函數有參數(加入參數爲name),咱們仍是經過do_something(name)的方式傳遞傳輸,不過,既然咱們最終調用的時候,經過do_something實質調用的inner_func函數,那麼在定義裝飾器是,定義的inner_func函數時也須要接受參數。

def func(f):
    def inner_func(name):
        print('{}函數開始運行……'.format(f.__name__))
        ret = f(name)
        print('{}函數結束運行……'.format(f.__name__))
        return ret
    return inner_func

@func
def do_something(name):
    print('你好,{}!'.format(name))
    return '我是返回值'

if __name__ == '__main__':
    ret = do_something('姚明')
    print(ret)

輸出結果:

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

我是返回值

一個裝飾器可用於裝飾千千萬萬個函數,則千千萬萬個函數參數狀況可能各不相同,有的沒有參數,有的可能多個參數,甚至還有關鍵字參數,對於這參數狀況不一樣的函數,咱們不可能爲每一個函數都寫一個func裝飾器,那怎麼辦呢?

Python中提供了*args, **kwargs這種機制來接受任意位置的位置參數和關鍵字參數,參數前面帶*表示接受任意個數位置參數,接收到的全部位置參數存儲在變量名爲args的元組中,帶**表示接受任意個數的關鍵字參數,接收到的全部關鍵字參數以字典的形式參數在變量名爲kwargs的字典中。

當咱們知道只有位置參數,但不知道有多少個位置參數是func裝飾器能夠這麼寫:

def func(f):
    def inner_func(*name):
        print('{}函數開始運行……'.format(f.__name__))
        ret = f(*name)
        print('{}函數結束運行……'.format(f.__name__))
        return ret
    return inner_func

@func
def do_something(name):
    print('你好,{}!'.format(name))

@func
def do_something_2(name_1, name_2):
    print('你好,{}!'.format(name_1))
    print('你好,{}!'.format(name_2))

@func
def do_something_3(*name):
    for n in name:
        print('你好,{}!'.format(n))

if __name__ == '__main__':
    do_something('姚明')
    print('-------------------------------')
    do_something_2('姚大明', '姚小明')
    print('-------------------------------')
    do_something_3('姚一明', '姚二明', '姚三明', '姚四明')

輸出結果:

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

-------------------------------

do_something_2函數開始運行……

你好,姚大明!

你好,姚小明!

do_something_2函數結束運行……

-------------------------------

do_something_3函數開始運行……

你好,姚一明!

你好,姚二明!

你好,姚三明!

你好,姚四明!

do_something_3函數結束運行……

上面例子定義func裝飾器時,咱們用*name來接受任意個數的位置參數,可別覺得只能用*args,args只是一個變量名,只不過約定俗成,用的多一些,實際開發時你愛取啥名就用啥名,對於這個知識點再也不多說,畢竟本篇主角是裝飾器。咱們繼續裝飾器內容!

當咱們知道只有關鍵字參數,殊不知道參數個數時,能夠func裝飾器這麼寫:

def func(f):
    def inner_func(**name):
        print('{}函數開始運行……'.format(f.__name__))
        ret = f(**name)
        print('{}函數結束運行……'.format(f.__name__))
        return ret
    return inner_func

@func
def do_something(name='無名氏'):
    print('你好,{}!'.format(name))

@func
def do_something_2(name_1='無名氏', name_2='無名氏'):
    print('你好,{}!'.format(name_1))
    print('你好,{}!'.format(name_2))

@func
def do_something_3(**name):
    for n in name.keys():
        print('你好,{}!'.format(name[n]))

if __name__ == '__main__':
    do_something(name='姚明')
    print('-------------------------------')
    do_something_2(name_1='姚大明', name_2='姚小明')
    print('-------------------------------')
    do_something_3(name_1='姚一明', name_2='姚二明', name_3='姚三明', name_4='姚四明')

輸出結果:

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

-------------------------------

do_something_2函數開始運行……

你好,姚大明!

你好,姚小明!

do_something_2函數結束運行……

-------------------------------

do_something_3函數開始運行……

你好,姚一明!

你好,姚二明!

你好,姚三明!

你好,姚四明!

do_something_3函數結束運行……

事實上,大多數狀況下,咱們對被裝飾函數是一無所知的——咱們不知道有多少個位置參數、多少個關鍵字參數,甚至對有沒有位置參數、關鍵字參數都不知道,這時候,咱們就只能*args和**kwargs齊上陣了:

def func(f):
    def inner_func(*name1, **name2):
        print('{}函數開始運行……'.format(f.__name__))
        ret = f(*name1, **name2)
        print('{}函數結束運行……'.format(f.__name__))
        return ret
    return inner_func

@func
def do_something(name):
    print('你好,{}!'.format(name))

@func
def do_something_2(name_1, name_2='無名氏'):
    print('你好,{}!'.format(name_1))
    print('你好,{}!'.format(name_2))

@func
def do_something_3(*name1, **name2):
    for n in name1:
        print('你好,{}!'.format(n))
    for n in name2.keys():
        print('你好,{}!'.format(name2[n]))

if __name__ == '__main__':
    do_something(name='姚明')
    print('-------------------------------')
    do_something_2(name_1='姚大明', name_2='姚小明')
    print('-------------------------------')
    do_something_3('姚一明', '姚二明', '姚三明', name_4='姚四明')

輸出結果:

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

-------------------------------

do_something_2函數開始運行……

你好,姚大明!

你好,姚小明!

do_something_2函數結束運行……

-------------------------------

do_something_3函數開始運行……

你好,姚一明!

你好,姚二明!

你好,姚三明!

你好,姚四明!

do_something_3函數結束運行……

3.3 裝飾器自己帶參數

咱們上面寫的裝飾器都沒有參數,或者說只有一個自帶參數,也就是被裝飾函數f。其實,裝飾器也是能夠有其餘參數的,這樣的裝飾器更加靈活。咱們經過實例來講明:如今咱們要對上面的func裝飾器進行改進,須要作到靈活控制裝飾器是用中文輸出仍是用英文輸出,代碼以下。

def language(lang='中文'):  # 這裏帶參數
    def func(f):  # 往裏嵌套了一層
        def inner_func(*name1, **name2):
            if lang=='中文':
                print('{}函數開始運行……'.format(f.__name__))
            else:
                print('The function of {} starts runging…'.format(f.__name__))
            ret = f(*name1, **name2)
            if lang=='中文':
                print('{}函數結束運行……'.format(f.__name__))
            else:
                print('The function of {} ends runging…'.format(f.__name__))
            return ret
        return inner_func
    return func

@language('中文')
def do_something(name):
    print('你好,{}!'.format(name))

@language('English')
def do_something_2(name):
    print('你好,{}!'.format(name))


if __name__ == '__main__':
    do_something(name='姚明')
    print('-------------------------')
    do_something_2(name='姚明')

輸出以下:

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

-------------------------

The function of do_something_2 starts runging…

你好,姚明!

The function of do_something_2 ends runging…

能夠看到,經過裝飾器帶參數的方式,咱們只須要在定義被裝飾函數時,指定裝飾器參數,就能夠靈活控制每一個被裝飾函數提示的語言。

固然,必須認可,裝飾器帶參數後,看起來更加複雜,須要多嵌套一層函數,由最外層的函數接受參數,裏層函數纔是真正的裝飾器。使用裝飾器時,會首先運行帶參數的最外層函數,返回裝飾器,這一步Python會自動幫咱們完成。因此,帶參數的裝飾器甚至能夠這麼使用:

h = language('中文')
@h
def do_something(name):
    print('你好,{}!'.format(name))

3.4 多層裝飾器

裝飾器也是能夠多層嵌套使用的,也就是說,一個函數能夠經過是被多個裝飾器所裝飾,執行順序是從下到上的優先順序加載裝飾:

# -*- coding: utf-8 -*-
import time

print(1)
def func(f):
    print(2)
    def inner_func(*name1, **name2):
        print('{}函數開始運行……'.format(f.__name__))
        f(*name1, **name2)
        print('{}函數結束運行……'.format(f.__name__))
    print(3)
    return inner_func

print(4)
def timmer(f):
    print(5)
    def inner_timmer(*args, **kwargs):
        print('開始計時……')
        start_time = time.time()
        f(*args, **kwargs)
        end_time = time.time()
        print('開始結束……')
        time_cost = end_time - start_time
        print('{}函數運行時長爲:{}秒'.format(f.__name__, time_cost))
    print(6)
    return inner_timmer

print(7)
@func
@timmer
def do_something(name):
    time.sleep(1)
    print('你好,{}!'.format(name))

print(8)
def do_something_2(name):
    time.sleep(1)
    print('你好,{}!'.format(name))


if __name__ == '__main__':
    print(9)
    do_something(name='姚明')
    print('-------------------------')
    func(timmer(do_something_2))(name='姚明')  # 執行效果與上面使用了@符號的do_something同樣

輸出結果:

1

4

7

5

6

2

3

8

9

inner_timmer函數開始運行……

開始計時……

你好,姚明!

開始結束……

do_something函數運行時長爲:1.0004358291625977秒

inner_timmer函數結束運行……

-------------------------

5

6

2

3

inner_timmer函數開始運行……

開始計時……

你好,姚明!

開始結束……

do_something_2函數運行時長爲:1.000028133392334秒

inner_timmer函數結束運行……

在上面代碼中,咱們同時用func和timmer兩個裝飾器來裝飾do_something,從運行結果中能夠看出,兩個裝飾器都發揮了做用。同時,爲了方便你們理解,咱們使用不帶@符號的來使用兩個裝飾器裝飾,二者運行結果是同樣的,結合代碼中的輸出標記,咱們來分析一下裝飾器的執行過程:開始運行後,1->4->7這幾個過程我相信你們都是能夠理解的,到了位置7後,遇到了@符號標識的裝飾器,並且是多層的,兩個@裝飾器至關於func(timmer(do_something_2)),因此是先執行timmer函數獲取返回值做爲參數傳遞給func,因此有了7以後是5->6,timmer函數返回值是inner_timmer函數,這時候就至關於func(inner_timmer),因此程序退出timmer函數後進入func函數,就有了2->3,從func函數返回後,繼續向下執行遇到位置8,而後就進入了主函數運行,因此是8->9,此時的函數是被裝飾過的,本質已是func函數返回的inner_func函數了,因此最終在主函數中執行do_something時執行的是inner_func方法,因此先輸出了func裝飾器的函數開始提示,而後纔是timmer裝飾器的計時開始提示。

嗯,有點複雜!

3.5 類裝飾器

經過上面的介紹,我想你已經知道,@符號是裝飾器的一個表示,或者說一個裝飾器語法糖,當使用@時,例如@A,這種語法糖會自動將被裝飾函數f做爲參數傳遞給A函數,而後將A函數的返回值從新f給f這個變量名,這就是@語法糖幫咱們作的事情,歸納來講就是f=A(f)()。

咱們如今發散一下思惟,假設A若是是一個類會怎麼樣呢?咱們知道當A是一個類時,A()表示調用A類的構造函數__init__實例化一個A類對象,那麼A(f)就表示將函數f做爲參數傳遞給A類的構造方法__init__來構造一個A類實例對象,若是使用了@符號,那麼這種語法機制就還會在A(f)後面加一個括號變成A(f)(),這是什麼鬼?執行一個類實例對象?不過話要說回來,若是A(f)()這種結構要是沒有問題,可以成功執行,是否是就意味着Python中類也能夠成爲裝飾器了呢?確實如此。咱們先看看經過A()()執行一個類實例對象會怎麼樣:

class A(object):
    def __init__(self):
        print('實例化一個A類對象')
    def __call__(self, *args, **kwargs):
        print('__call__方法被調用……')

if __name__ == '__main__':
    A()()

輸出結果:

實例化一個A類對象

__call__方法被調用……

看到沒,經過A()()執行一個類實例對象時,執行的是A類內部的__call__方法。那麼若是用A用做裝飾器時,@A返回的就是A類內部定義的__call__方法,至關於函數裝飾器func內的inner_func。來,咱們感覺一下類裝飾器:

class A(object):
    def __init__(self, f):
        print('實例化一個A類對象……')
        self.f = f
    def __call__(self, *args, **kwargs):
        print('{}函數開始運行……'.format(self.f.__name__))
        self.f(*args, **kwargs)
        print('{}函數結束運行……'.format(self.f.__name__))

@A
def do_something(name):
    print('你好,{}!'.format(name))
if __name__ == '__main__':
    do_something('姚明')

輸出結果:

實例化一個A類對象……

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

若是類裝飾器帶參數呢?這時候,類裝飾器的參數也可定是經過@A(t)的形式傳遞,這時候,由於@語法糖會自動加括號的緣由,結構就編程這樣A(t)(),A(t)是類實例對象,A(t)()就是__call__方法,因此,@語法糖會把被裝飾函數f做爲參數傳遞給__call__方法,被裝飾函數的參數須要在__call__內部定義一個函數來接受。也就是話說,定義類裝飾器時,裝飾器的參數經過__init__構造方法接收,被裝飾函數的做爲參數被__call__方法接收,。(這段子很繞,有點燒腦,沒點兒Python基礎還真很差理解,語言表達能力優先感受要枯竭了)

import time
class A(object):
    def __init__(self, t):
        print('實例化一個A類對象……')
        self.t = t
    def __call__(self, f):
        def inner_A(*args, **kwargs):
            print('延遲{}秒後開始執行……'.format(self.t))
            time.sleep(self.t)
            print('{}函數開始運行……'.format(f.__name__))
            f(*args, **kwargs)
            print('{}函數結束運行……'.format(f.__name__))
        return inner_A

@A(1)
def do_something(name):
    print('你好,{}!'.format(name))

if __name__ == '__main__':
    do_something('姚明')

輸出結果:

實例化一個A類對象……

延遲1秒後開始執行……

do_something函數開始運行……

你好,姚明!

do_something函數結束運行……

不管是函數裝飾器仍是類裝飾器,原理上是同樣的,區別在於若是A是函數,A()是直接調用函數,而A是類時,A()是實例化,經過A()()是調用A類的__call__方法。

4 Python中內置的裝飾器

4.1 @property,@setter,@deleter

@property,@setter,@deleter這三個裝飾器提供了更加友好的方式來獲取、設置或刪除類中的屬性。@property裝飾器所裝飾的函數能夠像訪問屬性同樣調用函數,注意,@property裝飾器必須先於@setter,@deleter使用,且三者說裝飾的函數必須同名。

class A(object):
    def __init__(self, v):
        print('實例化一個A類對象……')
        self.__value = v

    @property
    def value(self):
        print('取值時被調用')
        return self.__value

    @value.setter
    def value(self, value):
        print('賦值時被調用')
        self.__value = value

    @value.deleter
    def value(self):
        print('刪除值時被調用……')
        del self.__value


if __name__ == '__main__':
    a = A(123)
    print('-------------')
    print('__value的值爲:{}'.format(a.value))
    print('-------------')
    a.value = 234
    print('__value的值爲:{}'.format(a.value))
    print('--------------')
    del a.value
    print('__value的值爲:{}'.format(a.value))

輸出爲:

Traceback (most recent call last):

實例化一個A類對象……

-------------

取值時被調用

File "E:/WorkProjectCode/study_pymysql/study_secorators/test2.py", line 33, in <module>

__value的值爲:123

-------------

print('__value的值爲:{}'.format(a.value))

賦值時被調用

取值時被調用

__value的值爲:234

File "E:/WorkProjectCode/study_pymysql/study_secorators/test2.py", line 11, in value

--------------

return self.__value

刪除值時被調用……

取值時被調用

AttributeError: 'A' object has no attribute '_A__value'

運行產生異常,由於最後訪問了已經刪除了的元素。

4.2 @classmethod

在一個類中,若是一個方法被@classmethod所裝飾,就表明該方法與類綁定,而不是與實例對象綁定,第一個參數cls由Python機制自動傳遞,表示類自己。

class A(object):
    @classmethod
    def f(cls):
        print('當前類名爲:{}'.format(cls.__name__))


if __name__ == '__main__':
    A.f()

輸出結果:

當前類名爲:A

4.3 @staticmethod

被@staticmethod所裝飾的方法爲靜態方法,靜態方法通常使用場景就是和類相關的操做,可是又不會依賴和改變類、實例的狀態,好比一些工具方法。

import time
class A(object):
    @staticmethod
    def f():
        time_now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time()))
        return time_now


if __name__ == '__main__':
    print(A.f())
    print(A().f())

輸出:

2019-08-16 19:29:32

2019-08-16 19:29:32

相關文章
相關標籤/搜索