#建模大赛预备#认真学习装饰器

Wesley13
• 阅读 349

#建模大赛预备#认真学习装饰器

#1.什么是闭包 装饰器的一个基本原理就是闭包,函数嵌套:

def func1(x):
    return x
def func2(y):
    print y
a = func1(func2)
a('hello')

输出为:

hello

上面的代码中,func1直接返回x,那么a=func2(),然后执行a('hello')等于直接执行func2("hello")

#2.基本函数装饰器入门 而装饰器就是在上面的闭包上实现的一个为函数添加功能的快捷方式: 先看代码:

def printmessage(dosth):
    def wrapper(number):
        print 'we print pow(number,2)'
        return dosth(number)
    return wrapper

@printmessage
def dosomething(number):
    return pow(number, 2)

print dosomething(2)

第一,printmessage函数作为一个dosomething额外的功能用于打印消息,它的形式参数是目标函数
第二,wrapper的形参为printmessage提供参数,同时额外的功能在wrapper里实现,最后要返回wrapper(注意不是wrapper())
然后定义dosomething函数,并通过@使用printmessage这个装饰器
输出的结果为:

we print pow(number,2)
4

实际上,装饰器的作用只是为了解决这一步:

dosomething = printmessage(dosomething)
print dosomething(2)

等号左边的dosomething是添加了输出功能的版本,而括号里的dosomething是最初定义的函数 之所以用装饰器,主要是为了减少代码重构时的修改,不用每一处都像上面一样用赋值写,只需要 在原来的函数上添加个@符号和对应的函数作为装饰器即可

#3.带参数的装饰器与不确定的参数 在上面的例子里,我没法选择输入值作为打印内容,如果我想打印的是其他内容而不是'we print pow(number,2)'
同时,上面的装饰器函数的形参只有一个,如果dosomething是多个形参,就无法使用了,那么解决方法是:

def printmessage(message):            #提供外部配置参数
    def getmessage(func):                  #提供具体的函数功能
        def wrapper(*args, **kwargs):      #为具体的函数功能提供参数
            print message
            return func(*args, **kwargs)
        return wrapper
    return getmessage

#@printmessage('fuck')
def dosth(number, number1):
    return pow(number, number1)

a=printmessage('fuck')

print a
dosth = a(dosth)

print dosth(2, 4)

问题1解决:通过多一层的嵌套,printmessage的形参是一个message,作为输出内容,同时,原来的使用函数作为形参的部分则交给内部的getmessage,事实上,如果只用闭包,也是外层提供不同的参数,里层提供函数的核心。因此getmessage的形参是func

问题2解决:wrapper函数的形参写成了*args和**kwargs,这样就可以满足不同形参数的函数的装饰需求,同时wrapper的形参负责为形参函数提供具体的参数,最后别忘了嵌套返回

参考使用不好的写法,可以看到闭包是怎么做的:

a=printmessage('fuck')
dosth = a(dosth)

首先是用printmessage输入参数,然后a=getmessage(),dosth=wrapper()

不过个人认为,添加的额外功能应该放在getmessage里,而wrapper只负责func的return,这样代码会更规范易懂

#4.docstring遗失? 另外,参考上面的代码,如果我们在dosth中加入docstring的话会怎样呢:

def printmessage(dosth):
    def wrapper(number):
        print 'we print pow(number,2)'
        return dosth(number)
    return wrapper

@printmessage
def dosomething(number):
    """
    Fuck the numbers
    :param number:
    :return:
    """
    return pow(number, 2)

print dosomething(2)
print dosomething.__doc__

计算结果其实是:

we print pow(number,2)
4
None

意味着这种情况下,原函数的docstring还有__name__都被遗失了

这种情况下需要from functools import wraps,然后再wrapper前调用@wraps(dosth),复制dosomething的docstring过来

from functools import wraps

def printmessage(dosth):
    @wraps(dosth)
    def wrapper(number):
        print 'we print pow(number,2)'
        return dosth(number)
    return wrapper

再次执行:

we print pow(number,2)
4
    Fuck the numbers
    :param number:
    :return:

这样就能看到原函数的docstring了

-------------------------------------------------------分割线------------------------------------------------------------------------------------------------------------------------------------------

#5.类装饰器 上面的是函数装饰器,那么还有类的装饰器:

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')


@Foo
def bar():
    print ('bar')

bar()

输出结果为:

class decorator runing
bar
class decorator ending

只要实现了__call__,就可以变成一个可以调用的对象:

class Edu(object):
    def __call__(self, *args, **kwargs):
        return "yeshi1"

p = Edu()
print p()
#显示“yeshi1”

上面的代码是实例化了Edu类,然后调用p()时就会自动执行我们的代码,return一个值

同样,在上面的Foo类中,定义了__call__函数,于是Foo的实例就是可以调用的,调用时会自动执行func() 实际上,去掉@Foo,代码应该是这样的:

class Foo(object):
    def __init__(self, func):
        self._func = func
    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

def bar():
    print ('bar')

bar = Foo(bar)
bar()

这个似乎跟函数式的装饰器差不了多少,至少在调用闭包时没什么区别,用了bar函数来实例化Foo类并赋值给bar,bar的执行时就会打印那些信息。看来重点是实现__call__,而其他功能通过其他成员函数实现。

#6.函数装饰器+类参数 而我们还可以搭配函数装饰器和类使用,请参考此例子:

class locker:
    def __init__(self):
        print("locker.__init__() should be not called.")
         
    @staticmethod
    def acquire():
        print("locker.acquire() called.(这是静态方法)")
         
    @staticmethod
    def release():
        print("  locker.release() called.(不需要对象实例)")
 
def deco(cls):
    '''cls 必须实现acquire和release静态方法'''
    def _deco(func):
        def __deco():
            print("before %s called [%s]." % (func.__name__, cls))
            cls.acquire()
            try:
                return func()
            finally:
                cls.release()
        return __deco
    return _deco
 
@deco(locker)
def myfunc():
    print(" myfunc() called.")
 
myfunc()

上面的代码通过实现类的静态方法,在不需要实例化的情况下调用上锁和释放的功能,并通过嵌套函数实现带参装饰器,而装饰器形参为包含静态方法的类

#7.其他东西 python还有些内置的装饰器:@staticmathod、@classmethod、@property
分别是静态方法、类方法和属性装饰器,平常静态方法用得更多,但是在设计模式上回用到classmethod等,详细的介绍可以参考:
classmethod:classmethod
property:property

而关于多重装饰器的使用:

@a
@b
@c
def f ():

这个代码等效于:

f = a(b(c(f)))

reference:
装饰器

装饰器

装饰器

装饰器

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
半臻 半臻
2年前
Python基础5——装饰器
13、装饰器其本质:在不需要做任何代码变动的前提下,增加额外的功能。装饰器返回的是一个函数对象。相当于把函数作为参数传递进去,然后对函数进行装饰其本质就是一个闭包作用:1.给原来的函数增加额外的功能2.把原来的函数作为参数传递进去13.1基本用法标准版的装饰器pythondefwrapper(func):func为原来的函数名defin
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Wesley13 Wesley13
2年前
Java获得今日零时零分零秒的时间(Date型)
publicDatezeroTime()throwsParseException{    DatetimenewDate();    SimpleDateFormatsimpnewSimpleDateFormat("yyyyMMdd00:00:00");    SimpleDateFormatsimp2newS
Stella981 Stella981
2年前
Python之time模块的时间戳、时间字符串格式化与转换
Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
Sass
嵌套规则(NestedRules)Sass允许将一套CSS样式嵌套进另一套样式中,内层的样式将它外层的选择器作为父选择器mainp{color:00ff00;width:97%;.redbox{
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这