python装饰器学习小结

本文深入讲解Python装饰器的基础概念及应用,包括基本语法、参数处理、内置装饰器等,并通过实例展示如何提升代码效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、应用场景

装饰模式有很多经典的使用场景,例如插入日志、性能测试、事物处理等等,有了装饰器,就可以提取大量函数中与本身功能无关的类似代码,从而达到代码重用的目的。好好利用装饰器可以方便代码结构,让代码更清晰可观。

 

二、几个简单的入门例子

现在我们需要通过代码来得到一个函数的大概执行时间,写法如下:

import time
def get_time(func):
    startTime = time.time()
    func()
    endTime = time.time()
    totalTime = (endTime - startTime) * 1000
    print "it takes %f ms" %totalTime
def func():
    print "start"
    time.sleep(0.5)
    print "end"
get_time(func)
func()




针对这个代码有个问题就是,我们在每次调用一个函数时,如果想知道这个函数的执行时间,就要把所有的函数调用处的代码”func()”改为get_time(func)

 

python中一切都是对象,函数也是,因此对代码稍作修改如下:

import time
def get_time(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        totalTime = (endTime - startTime) *1000
        print "it takes %f ms" %totalTime
 
    return wrapper
def func():
    print "start"
    time.sleep(0.5)
    print "end"
func =get_time(func)
func()

 

到这里,一个比较的完整的装饰器(get_time)就实现了,装饰器没有影响原来的函数,以及函数调用的代码。代码中改变了func对应的函数对象。

 

---------------引入@-------------------

下面我们引入装饰器的语法,用“@”来精简装饰器的代码,代码如下:

import time
def get_time(func):
    def wrapper():
        startTime = time.time()
        func()
        endTime = time.time()
        totalTime = (endTime - startTime) *1000
        print "it takes %f ms" %totalTime
 
    return wrapper
 
@get_time
def func():
    print "start"
    time.sleep(0.5)
    print "end"
 
func()

输出如下:

 

使用了“@”后,就不要额外代码来给func重新赋值了,也就是说在func上“@get_time”等同于“func = get_time(func)”


----------------被装饰的函数可以带参数--------------

被装饰的函数可以带参数,举个栗子如下:

import time
def get_time(func):
    def wrapper(a, b):
        startTime = time.time()
        func(a, b)
        endTime = time.time()
        totalTime = (endTime - startTime) *1000
        print "it takes %f ms" %totalTime
 
    return wrapper
 
@get_time
def func(a, b):
    print "start"
    time.sleep(0.5)
    print "a+b=%d" % (a+b)
    print "end"
 
func(2, 3)


这时,func(2, 3) = get_time(func(2,3))

 

这时有另外一个问题,如果同时有多个函数,它们的参数都不一样,但是现在都需要使用get_time装饰器,要怎么办?

在python中,函数可以支持(*args, **kwargs)可变参数,所以装饰器可以通过可变参数形式来实现内嵌函数的签名。

 

注意:*args和**kwargs这两个魔法变量,其实并不一定要写成这样,只有变量前面的*(星号)才是必须的,也就是说你完全可以写成*var和**vars。而写成*args和**kwargs只是一个通俗的命名约定。

 

补充

*args和**kwargs主要用于函数定义,你可以将不定数量的参数传递给一个函数。这里不定的意思是:预先不知道函数使用者会传递多少个参数给你,此时就使用这两个关键字。

*args是用来发送一个非键值对的可变数量的参数列表给一个函数。

例如:

def deco(arg,*argv):
    print "first normal arg:", arg
    for arg in argv:
        print "another arg through*argv", arg
 
 
deco('a', 'b','c', 'd')

输出:

 

**kwargs允许你将不定长度的键值对,作为参数传递给一个函数。如果你需要在一个函数里处理带名字的参数,应该使用**kwargs

def deco(**kwargs):
    for key, value in kwargs.items():
        print "{0} =={1}".format(key, value)
 
deco(name ="hello")

输出:

 

------------装饰器本身可以带参数------------------

另外,装饰器本身也可以带参数,比如通过装饰器的参数来禁止计时功能。栗子如下:

import time
def get_time(arg=True):
    if arg:
        def deco(func):
            def wrapper(*args, **kwargs):
                startTime = time.time()
                func(*args, **kwargs)
                endTime = time.time()
                totalTime = (endTime -startTime) * 1000
                print "it takes %fms" % totalTime
 
            return wrapper
    else:
        def deco(func):
            return func
    return deco
 
@get_time(True)
def func(a, b):
    print "func start"
    time.sleep(0.5)
    print "a+b=%d" % (a + b)
    print "func end"
 
@get_time(False)
def func1():
    print "func1 start"
    time.sleep(0.2)
    print "func1 end"
 
func(2, 3)
func1()

输出:

 

如果装饰器本身需要支持参数,那么装饰器就需要多一层的内嵌函数。

这时 func(2, 3) = get_time(True)(func(2, 3))

func1() =get_time(False)(func1())

 

一个函数可以同时使用多个装饰器,然而装饰器的调用顺序与使用@语法声明的顺序相反。

import time
 
def deco1(func):
    print "entered into deco1"
 
    def wrapper(a, b):
        print "entered intodeco1_wrapper"
        func(a, b)
 
    return wrapper
 
 
def deco2(func):
    print "entered into deco2"
 
    def wrapper(a, b):
        print "entered intodeco2_wrapper"
        func(a, b)
 
    return wrapper
 
 
@deco1
@deco2
def func(a, b):
    print "func start"
    time.sleep(0.5)
    print "a+b=%d" % (a + b)
    print "func end"
 
 
func(2, 3)

输出:

 

这时 func(3, 8) = deco1(deco2(func(3, 8)))

调用顺序这一块要好好深究一下,待补充......

 

三、python内置装饰器

在python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod和property

(1)staticmethod是类静态方法,其跟成员方法的区别是没有self参数,并且可以在类不进行实例化的情况下调用

适用的场景:有时候在多个类中都会使用到相同的一些工具函数,或者在一个类中,有一个在别的函数中使用到的通用函数,那么这时候,如果每次使用都要实例化后再调用会造成多余的资源浪费。

此时,使用@staticmethod装饰器来装饰函数,就可以直接使用类名来调用函数。例如:

1、不使用staticmethod时

class A(object):
       def func(self):
              print “hello world!”
# 调用时要先实例化
a = A()
a.func()

2、使用staticmethod时,代码修改如下:

class A(object)
       @staticmethod
       def func():   # self去掉
              print “hello world”
# 不需要实例化可直接调用
A.func()

 

(2)classmethod与成员方法的区别在于所接收的第一个参数不是self(类实例的指针),而是cls(当前类的具体类型)。用法和场景与staticmethod相似。因为参数中有cls参数,所以可以用来调用类的属性,类的方法,实例化对象等。

例子:

# coding=utf-8
classTest(object):
    def func1(self):
        print "func1"
 
    @staticmethod
    def func2():
        print "func2"
 
    @classmethod
    def func3(cls):
        cls().func1()
        cls.func2()
        print "func3"
 
a = Test()
a.func1()
Test.func2()
Test.func3()

输出:

 

(3)property是属性的意思,表示可以通过类实例直接访问的信息

对于一个Student类

class Student(object):
       def __init__(self, name, score):
              self.name = name
              self.score = score
s = Student(‘Bob’,59)
s.score=60

当我们要修改一个s的score属性时可以这么写:

s.score = 1000

但是这样直接给属性赋值无法检查分数的有效性

 

下面进行一些修改,添加get和set函数

classStudent(object):
       def __init__(self, name, score):
              self._name = name
              self._score = score
       def get(self):
              return self._score
       def set(self, score):
              if score<0 or score>100:
                     raise ValueError(‘invalidscore’)
              self._score = score

这时,如果要修改s的score属性时,使用s.set(1000)就会报错。

使用get/set方法来封装对一个属性的访问在许多面向编程的语言中都很常见。

 

python中还可使用装饰器函数把get/set方法装饰成属性调用

class Student(object):
       def __init__(self, name, score):
              self._name = name
              self._score = score
 
       @property
       def score(self):
              return self._score
       @score.setter
       def score(self, score):
              if score<0 or score>100:
                     raise ValueError(‘invalidscore’)
              self._score = score

这里,第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。

 

此时可以像使用属性一样设置score了。例如

s = Student(‘Bob’,60)

s.score = 1000  # 这一句会报错

 

注意这里s.score里的score对应于@property下面的函数名,如果函数名是

@property

def get(self):

       return self._score

 

那么调用的时候就是s.get = 1000


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值