精华内容
下载资源
问答
  • 方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类...

    Typescript-ts 装饰器源码分析——方法装饰器

    这里写图片描述
    方法装饰器声明在一个方法的声明之前(紧靠着方法声明)。 它会被应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义。 方法装饰器不能用在声明文件( .d.ts),重载或者任何外部上下文(比如declare的类)中。

    方法装饰器表达式会在运行时当作函数被调用,传入下列3个参数:

    1. 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
    2. 成员的名字。
    3. 成员的属性描述符

    思考一个问题,装饰器是在什么时候被传递进参数的?
    为了解决这个疑问,我分两个方面去看,第一个是单个方法装饰器的情况,第二个是多个方法装饰器的情况,其实在了解了单个装饰器的情况之后,多个装饰器的情况就迎刃而解了

    在开始之前,我们需要了解ts是如何定义类的,也就是它的面具之下的js是怎么样的

    Ts的类

    为了方便解释,先写一个简单的类,就叫它Person吧

    "use strict";
    var Person = /** @class */ (function () {
        function Person() {
        }
        Person.prototype.sayHello = function () {
            console.log("hello");
        };
        return Person;
    }());

    经过ts编译后的代码:

    "use strict";
    var Person = /** @class */ (function () {
        function Person() {
        }
        Person.prototype.sayHello = function () {
            console.log("hello");
        };
        return Person;
    }());

    很明了了吧,其实ts类的本质就是一个立即执行函数,然后在内部声明了一个Person函数,给原型添加方法,最后把它导出来

    废话不多说了,来看一下装饰器吧!

    单个装饰器

    装饰器的作用有很多,比如可以应用到方法的 属性描述符上,可以用来监视,修改或者替换方法定义,下面写一个简单的例子,改变方法的定义

    function methodDeractor(msg:string):Function{
        return function(target:any, name:string, descriptor:PropertyDescriptor){
            descriptor.value = () => console.log(msg)
        }
    }
    
    class Person{
        @methodDeractor("hello world")
        sayHello(){
            console.log("hello")
        }
    }
    
    let a = new Person();
    a.sayHello(); //hello world

    上面的例子是通过属性描述符来修改方法的定义的
    methodDeractor内部return的函数才是ts所关心的装饰器,这个装饰器接收三个参数

    • target 目标方法所属类(函数)的原型
    • name 目标方法的名字
    • descriptor 目标方法的属性描述符

    这样又回到了开始的问题,那么这些参数是在什么时候被传递的呢?我们来看一下编译过后的js代码:

    "use strict";
    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };
    function methodDeractor(msg) {
        return function (target, name, descriptor) {
            descriptor.value = function () { return console.log(msg); };
        };
    }
    var Person = /** @class */ (function () {
        function Person() {
        }
        Person.prototype.sayHello = function () {
            console.log("hello");
        };
        __decorate([
            methodDeractor("hello world")
        ], Person.prototype, "sayHello", null);
        return Person;
    }());
    var a = new Person();
    a.sayHello();

    代码底部是类的定义,上面我们已经说过了,ts里的类其实就是一个普通的函数,我们的关注点是下面这些

    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
        else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };

    这是装饰器的定义, 也就是代码中的__decorate的定义。我们稍稍把它变成我们容易理解的形式

    //装饰器,类的原型对象,方法名,属性描述符
    var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
        var c = arguments.length,
            //参数个数<3为目标方法,>3为属性描述符
            r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
            //装饰器
            d;
    
        //检测新特性
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function"){
            r = Reflect.decorate(decorators, target, key, desc);
    
        }
        //无新特性
        else {
            //遍历装饰器个数
            for(var i = decorators.length - 1; i >= 0; i--){
                if (d = decorators[i]){
                    // console.log(d(target, key));
                    r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
                }
            }
        }
    
        return c > 3 && r && Object.defineProperty(target, key, r), r;
    };

    从头部开始
    首先__decorate会检查是否已经存在__decorate,检查的方法是检查this上的__decorator是否存在,在浏览器端,全局的this指代window对象,在node端,this指代module.exports,module.exports的默认值是{},一个空对象,由此判断,这里的__decorate被赋值为后面那个函数。

    function (decorators, target, key, desc) {
        var c = arguments.length,
            //参数个数<3为目标方法,>3为属性描述符
            r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
            //装饰器
            d;
    
        //检测新特性
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function"){
            r = Reflect.decorate(decorators, target, key, desc);
    
        }
        //无新特性
        else {
            //遍历装饰器个数
            for(var i = decorators.length - 1; i >= 0; i--){
                if (d = decorators[i]){
                    // console.log(d(target, key));
                    r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
                }
            }
        }

    这个函数接收4个参数

    • decorators 其类型为一个数组,表示应用到目标方法的所有装饰器
    • target 其类型为一个对象,是该方法所属类的原型对象
    • key 其类型为字符串,是该方法的名称
    • desc 其类型也为一个对象,是目标方法的属性描述符

    在开头,定义了三个变量,c,r,d

    • c为参数的个数,后文通过判断参数的个数来进行传参
    • r为目标方法的属性描述符或该方法所属类的原型对象
    • d为具体的装饰器

    下面看一下c,r,d的赋值:
    c:

     var c = arguments.length //通过arguments.length直接获取到参数个数

    r:

    r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc 
    //通过划分3这个参数个数来进行分别传值,小于3就是<=2,通常只有装饰器数组和该方法所属类的原型对象被传递进来,此时的r为原型对象,大于3也就是有4个参数,r通过Object.getOwnPropertyDescriptor被赋值为该方法的属性描述符

    d:
    d没有被明确的赋初始值,在后文,会通过遍历装饰器数组对其进行赋值,现在知道d是一个具体的装饰器就行了

    再接着看下一段代码:

    //检测新特性
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function"){
        r = Reflect.decorate(decorators, target, key, desc);
    }

    这里是检测是否已经支持新特性了,该新特性是能够支持JS元数据反射的API

    如果没有该特性的话直接进入else代码块

     //遍历装饰器个数
    for(var i = decorators.length - 1; i >= 0; i--){
        if (d = decorators[i]){
            // console.log(d(target, key));
            r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        }
    }

    这里做的事情很简单,就是遍历传进来的装饰器个数,然后把遍历出的每个装饰器赋值给d,然后通过传递进来的target,key,r参数对其进行传参执行,此时也是通过判断参数个数来进行的传参,不同个数的参数进行不同的传参。

    最后一句,返回目标方法的属性描述符,也就是r

    return c > 3 && r && Object.defineProperty(target, key, r), r;

    理解完了这段代码,我们再结合起来,综合来看一下,下面两段是:

    function methodDeractor(msg) {
        return function (target, name, descriptor) {
            descriptor.value = function () { return console.log(msg); };
        };
    }
    var Person = /** @class */ (function () {
        function Person() {
        }
        Person.prototype.sayHello = function () {
            console.log("hello");
        };
        __decorate([
            methodDeractor("hello world")
        ], Person.prototype, "sayHello", null);
        return Person;
    }());
    var a = new Person();
    a.sayHello();

    开头的methodDeractor是我一开始就声明的装饰器,这里没有做任何的变动,原样的写了下来

    下面是我声明的Person类,里面除了给Person附加sayHello方法以外,还做了一件事情,那就是执行装饰器,这里调用了上面声明的__decorate函数,传入四个参数,装饰器列表,该类的原型,目标方法的名称,还有就是目标方法的属性描述符,该属性描述符在此被传递为null,而真正的传值在__decorate内部,也就是r的值。

    现在我们回到之前讨论的问题

    装饰器是在什么时候被传递进参数的?

    答案非常明了了,__decorate调用在A类的定义中,因此,可以说,装饰器是在Person类构造的时候就已经传值了,这也就意味着,装饰器不等Person类new出实例,直接执行,恰恰可以体现装饰器的作用,比如在类的构造阶段为类添加各种元数据进行装饰或者改变目标方法的定义等等。

    多个装饰器

    上面说了一大堆都是单个装饰器,那多个装饰器呢?

    其实多个装饰器和单个装饰器并没有太大的差别,仅仅是decorators扩大了,也就是装饰器数组扩大了,在内部还是得遍历装饰器数组一个一个执行,因此,我们可以得出

    就拿文档上的一个例子来说

    function f() {
        console.log("f(): evaluated");
        return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
            console.log("f(): called");
        }
    }
    
    function g() {
        console.log("g(): evaluated");
        return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
            console.log("g(): called");
        }
    }
    
    class C {
        @f()
        @g()
        method() {}
    }

    看一下这个结果

    开始在C类的立即执行函数里会执行__decorate函数,f(),g()会被直接传递进去,此时的f,g已经被执行了,因此先打印的应该是:

    f(): evaluated
    g(): evaluated

    在__decorate函数中,通过遍历装饰器列表进行执行,由于遍历的时候是倒序遍历的

    for (var i = decorators.length - 1; i >= 0; i--)

    因此g装饰器会被先执行

    g(): called
    f(): called

    综上

    f(): evaluated
    g(): evaluated
    g(): called
    f(): called

    先执行f函数,然后执行g,之后通过倒序遍历执行g装饰器,然后再执行f装饰器

    总结

    至此,源码分析完了,是不是感觉还是很简单的
    装饰器会使我们的代码变得优雅,同一个装饰器可以用于不同的目标,因此提高了复用度,像nest框架,基于express上,用ts写的一套框架,如果你喜欢ts,不妨去试试

    展开全文
  • python 装饰器 @property介绍——把一个方法变成属性的装饰器介绍Python内置的@property装饰器就是负责把一个方法变成属性调用的:@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证参数进行...

    python 装饰器 @property介绍——把一个方法变成属性的装饰器介绍

    Python内置的@property装饰器就是负责把一个方法变成属性调用的:@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。


    使用@property


    在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

    s = Student()
    s.score = 9999
    

    这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:

    class Student(object):
    
        def get_score(self):
            return self._score
    
        def set_score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    

    现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:

    >>> s = Student()
    >>> s.set_score(60) # ok!
    >>> s.get_score()
    60
    >>> s.set_score(9999)
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    

    但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。

    有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!

    还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

    class Student(object):
    
        @property
        def score(self):
            return self._score
    
        @score.setter
        def score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    

    @property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

    >>> s = Student()
    >>> s.score = 60 # OK,实际转化为s.set_score(60)
    >>> s.score # OK,实际转化为s.get_score()
    60
    >>> s.score = 9999
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    

    注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。

    还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

    class Student(object):
    
        @property
        def birth(self):
            return self._birth
    
        @birth.setter
        def birth(self, value):
            self._birth = value
    
        @property
        def age(self):
            return 2014 - self._birth
    

    上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

    小结

    @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

    参考网址:https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000

    展开全文
  • 闲来无事写一个试试,在程序中使用还需要改一些地方地方 import threading import queue import time q = queue.Queue(maxsize=10) def create_threading(num,func,data): for i in range(num): t = threading....

    闲来无事写一个试试,在程序中使用还需要改一些地方地方

    import threading
    import queue
    import time
    q = queue.Queue(maxsize=10)
    def create_threading(num,func,data):
        for i in range(num):
            t = threading.Thread(target=func,args=(data,))
            t.start()
    def q_get_do(num,func):
        while True:
            if q.qsize() > 0:
                get_data = q.get()
                data = create_threading(num=num,func=func,data=get_data)
                return data
            else:
                time.sleep(1)
    
    def q_put(data):
        q.put(data)
    
    def set_func(thread_num):
        def de_func(func):
            def blu_func(*args,**kwargs):
                print("a")
                del_data = q_get_do(num=thread_num,func=func)
                q.put(del_data)
                # ret = func(*args,**kwargs)
                print("b")
                return del_data
            return blu_func
        return de_func
    
    @set_func(thread_num=2)
    def func(num):
        for i in range(10):
            import time
            print(num)
            time.sleep(1)
            print(i)
    for i in range(10):
        time.sleep(0.5)
        q.put(1)
    func(2)
    
    展开全文
  • Js 方法装饰器

    千次阅读 2018-08-29 18:11:17
    利用apply(),我们还可以动态改变函数的行为。 JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。 现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找...

     

    利用apply(),我们还可以动态改变函数的行为。

    JavaScript的所有对象都是动态的,即使内置的函数,我们也可以重新指向新的函数。

    现在假定我们想统计一下代码一共调用了多少次parseInt(),可以把所有的调用都找出来,然后手动加上count += 1,不过这样做太傻了。最佳方案是用我们自己的函数替换掉默认的parseInt()

    'use strict';
    
    var count = 0;
    var oldParseInt = parseInt; // 保存原函数
    
    window.parseInt = function () {
        count += 1;
        return oldParseInt.apply(null, arguments); // 调用原函数
    };
    
    展开全文
  • Python 装饰器 装饰类的方法

    千次阅读 2018-04-22 11:51:00
    Python 装饰器 装饰类的方法 在用scrapy写爬虫时,需要装饰器来装饰Spider类中的parse方法,但是parse...check是一个类内的方法(不是类方法)的装饰器,因此self是必须写的位置参数,response才是调用时的位置...
  • python 装饰器
  • 6 多个装饰器的使用 def make_div(func): """被装饰的函数的返回值 div标签""" def inner(*args, **kwargs): return "<div>" + func() + "</div>" return inner def make_p(func): """被装饰...
  • 第7.17节 Python类中的静态方法装饰器staticmethod 定义的静态方法深入剖析 静态方法也是通过类定义的种方法,一般将不需要访问类属性但是类需要具有的一些能力可以静态方法提供。 、 静态方法定义 1. 语法 @ ...
  • 先上代码: #!/usr/bin/env python3 ...# File : 用类实现一个装饰器.py # Author: DaShenHan&道长-----先苦后甜,任凭晚风拂柳颜------ # Date : 2019/11/10 # def check(func): # def inner(): # p...
  • 装饰器 在代码运行期间动态增加功能的方式,称为装饰...装饰器一个函数作为参数,并返回一个函数 def log(f): def write_log(*args, **kw): # 使用*是说可变参数,不限制参数的数量 with open('./module2/a.t...
  • 装饰器模式

    千次阅读 2019-09-25 20:42:08
    装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。 这种模式创建了一个装饰类,用来包装原有的类,并在保持...
  • 在react-ts项目中实现一个自定义装饰器 安装依赖 npm i @babel/plugin-proposal-decorators -D 配置.babelrc { "plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] } ...
  • python给类的所有方法加上装饰器

    千次阅读 2019-10-30 22:56:22
    今天遇到一个需求,需要类的所有方法进行监听其返回值,本来想的是用装饰器,但是方法太多了一个一个装饰器会爆炸。 所以这里发现一个有用的东西 参考:https://www.cnblogs.com/nkwy2012/p/6264031.html __...
  • 程序猿在做优化时经常会计算一个函数的用时,从而针对某几个函数进行优化,下面我就用一个简单的装饰器来实现计算函数运行时间:import timedef set_fun(func): def call_fun(*args, **kwargs): start_time = time...
  • ES6装饰器

    千次阅读 2018-04-27 21:46:04
    装饰器由@符号紧接一个函数名称,形如@expression,expression求值后必须是一个函数,在函数执行的时候装饰器的声明方法会被立即执行。装饰器是用来给附着的主体进行装饰,添加额外行为的一种方式。 许多面向对象的...
  • 装饰器一个面向切面编程,主要作用就是权限控制,插入日志,性能测试,事务处理,缓存等。 对于重要的系统我们仅仅控制登录是不够的,对于固定人员使用到的系统我们还是要进行权限的细分。下面是bollte框架下的...
  • python装饰器

    千次阅读 2020-02-22 14:21:07
    通过这么一个例子来解释装饰器存在的意义,假使我写了几个函数来实现同一个需求,我想测试一下这个函数的效率,通过测运行耗时的方法。但是,如果在每个函数中都去写同样的测时间的操作,三五个函数还好说,一旦想测...
  • 在python中,装饰器...1. 定义类装饰器定义类装饰器非常简单,仅接受唯一的参数——待装饰的类,这里以统计类的初始化实例为例,每初始化一个实例,则计数加1,如下:def instCounter(cls): oldInit = cls.__init__
  • [Python]多个装饰器合并

    千次阅读 2015-01-30 16:20:46
    django程序,需要写很多api,每函数都需要几个装饰...既然那么多个方法都需要写2个装饰器,或者多,有啥办法把多合并成行呢? 上面的函数执行过程应该是 csrf_exempt(require_POST(foo)) 修改成 def compose(*
  • Python装饰器实现一个代码计时器?

    千次阅读 2018-08-15 10:48:17
    本文用两种方式实现了代码计时器的功能,第种方式是采用装饰器来实现,第二种方式采用上下文管理器实现。 其实计算代码的运行时间,最朴素的想法就是先记录下来某段代码刚开始运行时的时间,等到运行完之后,再看...
  • 答案是线程池futures,为了使用方便,我将其封装成了一个装饰器,代码如下: import functools from concurrent import futures import time executor = futures.ThreadPoolExecutor(1) def timeout(seconds): ...
  • def execute_time(func): from time import time ... # 定义嵌套函数,用来打印出装饰的函数的执行时间 def wrapper(*args, **kwargs): # 定义开始时间 start = time() # 执行函数 func_return = func(*ar...
  • Python装饰器详解

    千次阅读 2018-06-05 19:39:54
    说到装饰器,它是个什么东西有什么作用,如何编写一个装饰器,具体的应用场景又有哪些呢?下面一一进行讲解。 一、什么是装饰器? 顾名思义,装饰器就是用来修饰某个函数,在不改变原来方法代码的前提下,额外的...
  • 装饰器基础Python的函数都是对象要了解装饰器,你必须了解Python中的函数都是对象.这个意义非常重要.让我们看看一个简单例子:def shout(word="yes"): return word.capitalize()+"!" print ...
  • Python装饰器的实现和万能装饰器

    千次阅读 2019-06-01 14:37:08
    装饰器接收一个函数作为参数,返回值也是一个函数。 在Python中,实现装饰器的方式叫做闭包。 一、闭包的实现 闭包是指函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的...
  • Python各种类型装饰器详解说明

    千次阅读 多人点赞 2018-10-27 19:32:42
    装饰器本身需要接受一个被装饰的对象作为参数,该参数通常为函数、方法、类等对象。 装饰器需要返回一个对象,该对象可以是 经过处理的原参数对象、一个包装且类似原参数的对象;或者返回一个不相干内容(通常不...
  • 其实,python 中的装饰器本质上就是一个函数,这个函数接收其他的函数作为参数,并将其以一个全新的修改后的函数替换它 关于装饰器的知识是python面试中比较常见的,对于装饰器的理解可以看这篇文章:理解Python中的...
  • 第7.26节 Python中的@property装饰器定义属性访问方法getter、setter、...Python总共包括三内置装饰器(注意abstractmethod这装饰器是从abc模块导入的,不是内置的),除了前面介绍的类方法装饰器classmeth...
  • python装饰器Decorators

    千次阅读 2014-12-12 23:49:15
    Python装饰器要考虑装饰器本身的定义和被装饰器对象的定义。 对于无参数的装饰器,其装饰器函数的参数是要被装饰的函数对象名; 对于有参数的装饰器在调用时使用的是应用的参数,@timeStumpFunc_args(argv)的argv,已...
  • typescript 深入理解装饰器

    千次阅读 2019-09-20 15:30:11
    安装 // cmd cnpm i -g typescript 配置 // cmd tsc --init 将tsconfig.json的experimentalDecorators设为true, 启动实验阶段的装饰器功能 ...方法装饰器>静态属性装饰器>静态方法装饰器>类装饰器 // ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 143,108
精华内容 57,243
关键字:

如何对一个方法装饰器