当前位置: 澳门新濠3559 > 编程 > 正文

希望可以记录下函数的执行日志,通过Decorator接

时间:2019-10-06 08:30来源:编程
前言 在公司采用我的angular后台方案后,除业务代码外,我就一直在维护及扩展一个增删改查的可配置组件,很多方法都需要相当的灵活性,如灵活的格式化管道,灵活的排序方法,灵

前言

在公司采用我的angular后台方案后,除业务代码外,我就一直在维护及扩展一个增删改查的可配置组件,很多方法都需要相当的灵活性,如灵活的格式化管道,灵活的排序方法,灵活的验证器。这里面就涉及了很多的方法公用,特别有时逻辑是一样的,但需要的返回类型不一样(Promise希望可以记录下函数的执行日志,通过Decorator接口的各类实现。Observabvle)。然后就发现此时装饰器有大用处了。

Python的decorator

使用python能够非常轻易地实现装饰器@refactor_test。代码如下(GitHub):

import functools
import logging

LOGGER = logging.getLogger('refactor_test')

def refactor_test(comp_func):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kws):
            comp_res = comp_func(*args, **kws)
            res = func(*args, **kws)
            if res != comp_res:
                message = "not equals for function:{} from {} 
                        with arguments:{}-{}".format(func.__name__, 
                                comp_func.__name__, args, kws)
                LOGGER.debug(message)
                print(message)
            return res
        return wrapper
    return decorator

def refactor_from(message):
    return message

@refactor_test(refactor_from)
def refactor_to0(message):
    return message

@refactor_test(refactor_from)
def refactor_to1(message):
    return "!"   message

if __name__ == '__main__':
    refactor_to0('Hello python!')
    refactor_to1('Hello python!')

这是非常经典的python decorator实现,是完全透明的,调用者无需关注到我们在调用时候执行了一个refacor_test的过程。refactor_to0是一个符合要求的重构实现,而refactor_to1不是。

Python3.5开始Python把Typing作为标准库引入,低版本可以使用独立的Typing包

2、让装饰器同时支持带参数或不带参数

def new_logging_tool(obj):
    if isinstanc(obj, str):  # 带参数的情况,参数类型为str
        def decorator(func):
            @functools.wraps(func)
            def wrapper(*arg, **kwargs):
                if obj == 'error':
                    logging.error('%s is running...' % func.__name__)
                elif obj == 'warn':
                    logging.warn('%s is running...' % func.__name__)
                else:
                    logging.info('%s is running...' % func.__name__)
                func()
            return wrapper
        return decorator
    else:  # 不带参数的情况,参数类型为函数类型,即被装饰的函数
        @functools.wraps(obj)
        def wrapper(*args, **kwargs):
            logging.info('%s is running...' % obj.__name__)
            obj()
        return wrapper

@new_logging_tool
def yesterday():
    print('2018-05-24')

yesterday()

@new_logging_tool('warn')
def today(name='devin'):
    print('Hello, %s! Today is 208-05-25' % name)

today()

 

如上所示,参数有两种类型,一种是字符串,另一种是可调用的函数类型。因此,通过对参数类型的判断即可实现支持带参数和不带参数的两种情况。

把函数结果转换成Observable类型

  • target 是构造函数,注意不是实例
  • key 是方法名
  • descriptor 解析器,value对象就是要装饰的函数
import { of, Observable, isObservable } from 'rxjs';function ResultToObservable(target: any, key: string, descriptor: any) { const originalMethod = descriptor.value; const newMethod = function(...args: any[]): any { const result: any = originalMethod.apply(this, args); if (isObservable { return result; } else if (result instanceof Promise) { return new Observable<any>(ob => { result .then(_ => { ob.next; ob.complete .catch(e => ob.error; } else { return of; } }; descriptor.value = newMethod;}

这样就可以简单的共用函数了

@ResultToObservablegetObservable(){ return anyFn()}

如果觉得文章对你有点用的话,麻烦拿出手机,这里有一个你我都有的小福利: 打开支付宝首页搜索“8601304”,即可领红包。谢谢支持

Java实现

由于java语法的限制,无法像动态语言python一样透明地为给定方法添加decorator。当然可以按照经典的设计实现,如下图所示。
[图片上传失败...(image-5dfcd6-1510413037923)]

对于我们想要解决的问题,在python中,通过装饰器语法,在编码时,就指定了由重构后方法到重构前方法的映射。而如果按照传统的方法实现,我们首先,需要维护一个重构后的方法到重构之前方法的映射表,另外,我们不能为每一个重构的方法都编写一个装饰器方法,不够灵活,过于繁琐。所以,我们需要使用java的反射机制,动态调用方法。第一点也是很繁琐的,或者写到配置文件,或者hard code到代码里,都是极不好的。我们通过java的annotation注解功能来实现。Oracle的官方tutorial中,有对java annotations比较细致的说明。我们来看看如何实现。

RefactorUtil.java (GitHub):

import org.slf4j.Logger;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.Map;

public class RefactorTestUtil {
    private static Logger LOGGER = null;

    public interface Equality <T, S> {
        public boolean isEqual(T obj0, S obj1);
    }

    public static void setLogger(Logger logger) {
        LOGGER = logger;
    }

    @Target( ElementType.METHOD )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface RefactorTest {
        String classRef();
        String methodName();
        int[] paramClassIndex2ThisParams() default {};
    }

    private static void testFailLog(String message, Map.Entry<Class<?>, String> migTo, Map.Entry<Class<?>, String>
            migFrom, Object ... params) {
        String argsStr = null;
        if (params != null && params.length > 0) {
            StringBuilder args = new StringBuilder();
            for (Object param : params) {
                args.append(param).append(":").append(param.getClass().getSimpleName());
                args.append(",");
            }
            if (args.length() > 0) {
                argsStr = args.substring(0, args.length() - 1);
            }
            else {
                argsStr = args.toString();
            }
        }
        String logStr = String.format("[MigrationTest]%s-TO(%s)-FROM-(%s)-ARGS(%s)", message, migTo.toString(),
                migFrom.toString(), argsStr);

        if (LOGGER != null) {
            LOGGER.error(logStr);
        }
        else {
            System.err.println(logStr);
        }
    }

    public static <T> T decorateFunctionWithRefactorTest(Class<?> cls, String method, Object ... params) throws
            NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        return decorateFunctionWithRefactorTest(cls, method, new Equality<T, Object>() {
            public boolean isEqual(T obj0, Object obj1) {
                return obj0.equals(obj1);
            }
        }, params);
    }

    public static <T, S> T decorateFunctionWithRefactorTest(Class<?> cls, String method, 
            Equality<T, S> equals, Object... params) throws NoSuchMethodException,   
            InvocationTargetException, IllegalAccessException {
        Method refactorTo = TypeUtil.getClassMethodWithNotAccurateTypedParams(cls, method,
                 params);
        if (refactorTo == null) {
            throw new NoSuchMethodException(String.format("There is no method %s in class 
                    %s", method, cls
                    .getSimpleName()));
        }

        T toResult = (T) refactorTo.invoke(null, params);

        RefactorTest refactorAnno = refactorTo.getAnnotation(RefactorTest.class);
        String refactorFromCls =  refactorAnno.classRef();
        String refactorFromMethod = refactorAnno.methodName();
        int[] paramClassesIndex = refactorAnno.paramClassIndex2ThisParams();

        try {
            Class<?> refactorFromClass = ClassLoader.getSystemClassLoader()
                    .loadClass(refactorFromCls);


            Object[] fromParams = null;
            if (paramClassesIndex != null && paramClassesIndex.length > 0) {
                fromParams = new Object[paramClassesIndex.length];
                for (int i = 0; i < paramClassesIndex.length; i   ) {
                    fromParams[i] = params[paramClassesIndex[i]];
                }
            }
            else {
                fromParams = params;
            }

            Method refactorFrom = TypeUtil.getClassMethodWithNotAccurateTypedParams
                    (refactorFromClass, refactorFromMethod,
                    fromParams);
            if (refactorFrom == null) {
                testFailLog("No refactor-from method found", new AbstractMap.
                        SimpleEntry<Class<?>, String>(cls, method)
                        , new AbstractMap.SimpleEntry<Class<?>,String>
                        (refactorFromClass, refactorFromMethod), params);
                return toResult;
            }

            S fromResult = (S) refactorFrom.invoke(null, fromParams);

            if (! equals.isEqual(toResult, fromResult)) {
                testFailLog("Not equal after refactoring", new AbstractMap.SimpleEntry
                        <Class<?>, String>(cls, method)
                        , new AbstractMap.SimpleEntry<Class<?>, String>
                        (refactorFromClass, refactorFromMethod), params);
            }


        } catch (ClassNotFoundException e) {
            testFailLog("No refactor-from Class found", new AbstractMap.SimpleEntry
                    <Class<?>, String>(cls, method), new AbstractMap.SimpleEntry<Class<?>,  
                    String>(null, refactorFromMethod), params);

        } finally {
            return toResult;
        }
    }
}

RefactorTestUtil.decorateFunctionWithRefactorTest()方法通过传入对应类与方法名,还有参数列表,通过RefactorTest注解获取该方法对应重构前方法,动态比较两次调用的结果是否一致,决定是否计入日志。
@interface RefactorTest是一个注解的声明,再待注解的方法前添加@RefactorTest(...),通过三个属性classRef,methodName,paramClassIndex2ThisParams来给定重构前方法及调用参数的不对齐问题。
通过注解和反射我们实现了这个功能,而由于java反射的限制,对于参数列表的类型不是方法签名中参数列表的类型完全匹配无法找到确定的方法,我实现了TypeUtil,提供了简单的动态机制,找到对应方法。比如size(Collection)方法,再传入一个Set时,仅仅通过java的反射API,无法找到size(Collection)方法。

TypeUtil.java(GitHub):

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class TypeUtil {
    public static boolean isMatchedBoxingType(Class<?> cls0, Class<?> cls1) {
        if (cls0 == null || cls1 == null) {
            return false;
        }
        if (! cls0.isPrimitive() && ! cls1.isPrimitive()) {
            return cls0.equals(cls1);
        }
        if (cls0.isPrimitive() && cls1.isPrimitive()) {
            return cls0.equals(cls1);
        }

        Class<?> primitive = cls0.isPrimitive() ? cls0 : cls1, boxing = cls1.isPrimitive() ? cls0 : cls1;

        if (primitive.equals(int.class)) {
            return boxing.equals(Integer.class);
        }
        if (primitive.equals(short.class)) {
            return boxing.equals(Short.class);
        }
        if (primitive.equals(float.class)) {
            return boxing.equals(Float.class);
        }
        if (primitive.equals(double.class)) {
            return boxing.equals(Double.class);
        }
        if (primitive.equals(boolean.class)) {
            return boxing.equals(Boolean.class);
        }
        if (primitive.equals(long.class)) {
            return boxing.equals(Long.class);
        }
        if (primitive.equals(char.class)) {
            return boxing.equals(Character.class);
        }
        if (primitive.equals(byte.class)) {
            return boxing.equals(Byte.class);
        }
        return false;
    }

    private static boolean isSubClassOf(Class<?> subCls, Class<?> superCls) {
        if (subCls == null || superCls == null) {
            return false;
        }
        if (superCls.equals(Object.class)) {
            return true;
        }
        if (superCls.isInterface() && ! subCls.isInterface()) {
            for (Class<?> interf : subCls.getInterfaces()) {
                if (interf.equals(superCls)) {
                    return true;
                }
            }
            return false;
        }
        Class<?> cls = subCls;
        for (; cls != null && ! cls.equals(superCls); cls = cls.getSuperclass());
        return cls != null;
    }

    public static Method getClassMethodWithNotAccurateTypedParams(Class<?> cls, String methodName, Object ...
            params) {
        if (cls == null || methodName == null) {
            return null;
        }

        Class<?>[] paramClasses = new Class<?>[params.length];
        int i = 0;
        for (Object param : params) {
            paramClasses[i  ] = param.getClass();
        }

        Method method = null;
        try {
            method = cls.getMethod(methodName, paramClasses);
        } catch (NoSuchMethodException e) {
            Method[] methods = cls.getMethods();

            List<Method> capableMethods = new ArrayList<Method>();
            for (Method candidateMethod : methods) {
                if (! candidateMethod.getName().equals(methodName)) {
                    continue;
                }
                if (! candidateMethod.isVarArgs() && candidateMethod.getParameterTypes().length != params.length) {
                    continue;
                }

                Class<?>[] methodParamClasses = candidateMethod.getParameterTypes();
                boolean found = true;
                for (int j = 0; j < methodParamClasses.length; j   ) {
                    Class<?> methodParamClass = methodParamClasses[j];
                    if(! TypeUtil.isInstanceOf(methodParamClass, params[j])) {
                        found = false;
                        break;
                    }
                }

                if (found) {
                    capableMethods.add(candidateMethod);
                }
            }

            if (capableMethods.size() == 1) {
                method = capableMethods.get(0);
            }
            else if (capableMethods.size() > 1) {
                for (int pi = 0; pi < params.length; pi   ) {
                    Class<?> bottom = Object.class;
                    int mindex = 0;
                    int bottomCount = 0;
                    for (int mi = 0; mi < capableMethods.size(); mi   ) {
                        Method m = capableMethods.get(mi);
                        Class<?> pclass = m.getParameterTypes()[pi];
                        if (pclass.equals(bottom) || isMatchedBoxingType(pclass, bottom)) {
                            bottomCount   ;
                            continue;
                        }
                        if (isSubClassOf(pclass, bottom)) {
                            bottom = pclass;
                            mindex = mi;
                            bottomCount = 1;
                        }
                    }
                    if (bottomCount < capableMethods.size() && bottomCount > 0) {
                        method = capableMethods.get(mindex);
                        break;
                    }
                }
            }
        }
        return method;
    }

    public static boolean isInstanceOf(Class<?> cls, Object instance) {
        if (cls == null) {
            return false;
        }

        if (instance == null) {
            return true;
        }

        if (cls.isPrimitive()) {
            Class<?> insType = instance.getClass();
            return isMatchedBoxingType(cls, insType);
        }
        else if (cls.isArray()) {
            Class<?> insType = instance.getClass();
            if (! insType.isArray()) {
                return false;
            }
            Class<?> cls0 = cls.getComponentType(), cls1 = insType.getComponentType();
            if (isMatchedBoxingType(cls0, cls1)) {
                return true;
            }
        }
        return cls.isInstance(instance);
    }
}

比如我们有4个方法:

public class Util {
    public static String refactorFrom(String message, int time) {
        return message   "("   time   ")";
    }

    @RefactorTestUtil.RefactorTest(
        classRef = "Util",
        methodName = "refactorFrom"
    )
    public static String refactorTo0(String message, int time) {
        return message   "("   time   ")";
    }

    @RefactorTestUtil.RefactorTest(
        classRef = "Util",
        methodName = "refactorFrom",
        paramClassIndex2ThisParams = {1, 0}
    )
    public static String refactorTo1(int time, String message) {
        return message   "("   time   ")";
    }

    @RefactorTestUtil.RefactorTest(
        classRef = "Util",
        methodName = "refactorFrom"
    )
    public static String refactorTo2(String message, int time) {
        return message   "["   time   "]";
    }

refactorTo0, refactorTo1, refactorTo2都是重构自refactorFrom。其中refactorTo1更换了参数类型的顺序,使用了paramClassIndex2ThisParams参数。而refactorTo2是一个会被报告错误的重构函数。我们做如下的测试:

public RefactorTestUtilTest {
    @Test
    public void testDecorateFunctionWithRefactorTest() {
        String message = "OK";
        int time = 3;

        assertEquals(message   "("   time   ")", RefactorUtil.
                decorateFunctionWithRefactorTest(Util.class, "refactorTo0", message, time);
        assertEquals(message   "("   time   ")", RefactorUtil.
                decorateFunctionWithRefactorTest(Util.class, "refactorTo1", time, message);
        assertEquals(message   "["   time   "]", RefactorUtil.
                decorateFunctionWithRefactorTest(Util.class, "refactorTo2", message, time);
    }
}

这样,通过java的annotations,我们实现了一种特定需求的decorator设计模式,但是由于语言特性与语法,无法实现python一样的透明使用。

澳门新濠3559 1

四、多元化百家争鸣

通过decorator实现refactor_test

我们想要通过装饰器实现这样的一个测试工具:我们重新实现了一个函数A,原函数是B。在调用函数A时,能够自动运行函数B,对二者的结果作比较,如果不相等,将当前的环境信息输出到日志中,以便追查。同时,不应现对函数的正常使用。
这里的函数,我们要求是幂等的无副作用的
下列全部的代码在这里。

overload存在的必要

4、装饰函数 -> 装饰类

(1)函数层面的装饰器很常见,以一个函数作为输入,返回一个新的函数; (2)类层面的装饰其实也类似,已一个类作为输入,返回一个新的类;

例如:给一个已有的类添加长度属性和getter、setter方法

def Length(cls):
    class NewClass(cls):
        @property
        def length(self):
            if hasattr(self, '__len__'):
                self._length = len(self)
            return self._length

        @length.setter
        def length(self, value):
             self._length = value
    return NewClass

@Length
class Tool(object):
    pass

t = Tool()
t.length = 10
print(t.length)  # 10

 

Decorator是一个经典的结构式设计模式,有着非常广泛的应用。在经典的Design Patterns:Elements of Reusable Object-Oriented Software中,它的用意被描述为:动态地为一个对象添加额外的责任与功能。对于扩展功能,装饰器提供了比子类化更加灵活的替代方案。
在许多编程语言中,比如Python,在语法上就提供了装饰器的支持,能够透明地使用装饰器。而Java则相比之下繁琐一些,通过Decorator接口的各类实现,针对被decorate的组件接口的实现来装饰。本文介绍一种基于annotation的decorator实现,虽然无法实现如python一般的透明使用装饰器,在某些情景下,也是一种灵活的实现方式。

不过上面的代码仅仅用来说明typing.overload的作用。

1、@property -> getter/setter方法

示例:给一个Student添加score属性的getter、setter方法

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

s = Student()
s.core = 60
print('s.score = ', s.score)
s.score = 999  # ValueError: score must between 0~100!

 

Annotation确实是个不错的特性,但是因此也需要引入更多的配套来保证整个环境的一致。因此就有了剩下的东西。可能后面还会有其他的东西。

3、类装饰器

装饰器不仅可以是函数,还可以是类,相比函数装饰器,类装饰器具有灵活度大、高内聚、封装性等优点。使用类装饰器主要依靠类的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法。

(1)示例一、被装饰函数不带参数

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()

 

(2)示例二、被装饰函数带参数

class Counter:
    def __init__(self, func):
        self.func = func
        self.count = 0  # 记录函数被调用的次数

    def __call__(self, *args, **kwargs):  
        self.count  = 1
        return self.func(*args, **kwargs)

@Counter
def today(name='devin'):
    print('Hello, %s! Today is 208-05-25' % name)  # 被装饰的函数带参数的情况

for i in range(10):
    today()
print(today.count)  # 10

 

(3)示例三、不依赖初始化函数,单独使用__call__函数实现(体现类装饰器灵活性大、高内聚、封装性高的特点) 实现当一些重要函数执行时,打印日志到一个文件中,同时发送一个通知给用户

class LogTool(object):
    def __init__(self, logfile='out.log'):
        self.logfile = logfile  # 指定日志记录文件

    def __call__(self, func):  # __call__作为装饰器函数
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            log_string = func.__name__   " was called"
            print(log_string)  # 输出日志
            with open(self.logfile, 'a') as fw:
                fw.write(log_string   'n')  # 保存日志
            self.notify()  # 发送通知
            return func(*args, **kwargs)
        return wrapper

    # 在类中实现通知功能的封装
    def notify(self):
        pass

@LogTool()  # 单独使用__call__函数实现时,别忘了添加括号,进行类的初始化
def bill_func():
    pass

 

进一步扩展,给LogTool创建子类,来添加email的功能:

class EmailTool(LogTool):
    """
    LogTool的子类,实现email通知功能,在函数调用时发送email给用户
    """
    def __init__(self, email='admin@myproject.com', *args, **kwargs):
        self.email = email
        super(EmailTool, self).__init__(*args, **kwargs)

    # 覆盖父类的通知功能,实现发送一封email到self.email
    def notify(self):
        pass

@EmailTool()
def bill_func():
    pass

 

澳门新濠3559 2

二、开天辟地

一个简单的装饰器

def logging_tool(func):
    def wrapper(*arg, **kwargs):
        logging.info('%s is running...' % func.__name__)
        func()  # 把today当作参数传递进来,执行func()就相当于执行today()
    return wrapper

def today():
    print('2018-05-25')

today = logging_tool(today)  # 因为装饰器logging_tool(today)返回函数对象wrapper,故这条语句相当于today=wrapper
today()  # 执行today()就相当于执行wrapper()

 

以上也是装饰器的原理!!!

澳门新濠3559 3

1、带参数的装饰器

装饰器的语法允许我们在调用时,提供其它参数,比如@decorator(condition)。为装饰器的编写和使用提供了更大的灵活性。比如,我们可以在装饰器中指定日志的等级,因为不同业务函数可能需要不同的日志级别。

def logging_tool(level):
    def decorator(func):
        def wrapper(*arg, **kwargs):
            if level == 'error':
                logging.error('%s is running...' % func.__name__)
            elif level == 'warn':
                logging.warn('%s is running...' % func.__name__)
            else:
                logging.info('%s is running...' % func.__name__)
            func()
        return wrapper
    return decorator

@logging_tool(level='warn')
def today(name='devin'):
    print('Hello, %s! Today is 208-05-25' % name)

today()

 

但这是运行时的错误,也就是代码在执行时才会发现问题。最好是我们能在写完代码时就发现是否存在问题。因此就有了类型检查工具,比如mypy这样的工具,还有很多IDE也集成了这样的检查工具。

六、最后

对于Python装饰器,除了以上列举的示例,还有很多很多神奇的用法,同时装饰器也只是Pythonic中的冰山一角,这里仅当抛砖引玉,更多hacker用法,少年,尽情愉快地探索吧......

python学习交流群:125240963
作者:devinzhang
澳门新濠3559,链接:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

先说第一种,对于固定数量参数的方法而言,同一个参数如果打算接受多种类型,可以这么用,比方说参数可以是:int, float, str:

2、@classmethod、@staticmethod

(1)@classmethod 类方法:定义备选构造器,第一个参数是类本身(参数名不限制,一般用cls) (2)@staticmethod 静态方法:跟类关系紧密的函数

简单原理示例:

class A(object):
    @classmethod
    def method(cls):
        pass

 

等价于

class A(object):
    def method(cls):
        pass
        method = classmethod(method)

 

澳门新濠3559 4

3、@functools.wraps

装饰器极大地复用了代码,但它有一个缺点:因为返回的是嵌套的函数对象wrapper,不是原函数,导致原函数的元信息丢失,比如函数的docstring、name、参数列表等信息。不过呢,办法总比困难多,我们可以通过@functools.wraps将原函数的元信息拷贝到装饰器里面的func函数中,使得装饰器里面的func和原函数有一样的元信息。

def timethis(func):
      """
      Decorator that reports the execution time.
      """
     @wraps(func)
     def wrapper(*args, **kwargs):
           start = time.time()
           result = func(*args, **kwargs)
           print(func.__name__, time.time() - start)
           return result
     return wrapper


@timethis
def countdown(n: int):
      """Counts down"""
      while n > 0:
           n -= 1


countdown(10000000)  # 1.3556335
print(countdown.__name__, ' doc: ', countdown.__doc__, ' annotations: ', countdown.__annotations__)

 

@functools.wraps让我们可以通过属性__wrapped__直接访问被装饰的函数,同时让被装饰函数正确暴露底层的参数签名信息

countdown.__wrapped__(1000)  # 访问被装饰的函数
print(inspect.signature(countdown))  # 输出被装饰函数的签名信息

 

澳门新濠3559 5

五、上古神器

问题来源于一个QQ群友的提问,顺着问题我看了下Typing中overload的使用。

三、Pythonic世界的初探

@语法糖 接触 Python 有一段时间的话,对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖,它放在函数开始定义的地方,这样就可以省略最后一步再次赋值的操作

def logging_tool(func):
    def wrapper(*arg, **kwargs):
        logging.info('%s is running...' % func.__name__)
        func()  # 把today当作参数传递进来,执行func()就相当于执行today()
    return wrapper

@logging_tool
def today():
    print('2018-05-25')

today()

 

有了 @ ,我们就可以省去today = logging_tool(today)这一句了,直接调用 today() 即可得到想要的结果。 不需要对today() 函数做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样。 如果我们有其他的类似函数,可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,提高程序可重复利用性,并增加程序的可读性。

装饰器在 Python 使用之所以如此方便,归因于Python函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。

装饰器本质上是一个Python函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外的功能,装饰器的返回值也是一个函数/类对象。 它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景,装饰器是解决这类问题的绝佳设计。 有了装饰器,我们就可以抽离出大量与函数功能本身无关的代码到装饰器中并继续重用。 简单来说:装饰器的作用就是让已经存在的对象添加额外的功能。

一、史前故事

先看一个简单例子,实际可能会复杂很多:

def today():
    print('2018-05-25')

 

现在有一个新的需求,希望可以记录下函数的执行日志,于是在代码中添加日志代码:

def today():
    print('2018-05-25')
    logging.info('today is running...')

 

如果函数 yesterday()、tomorrow() 也有类似的需求,怎么做?再写一个 logging 在yesterday函数里?这样就造成大量雷同的代码,为了减少重复写代码,我们可以这样做,重新定义一个新的函数:专门处理日志 ,日志处理完之后再执行真正的业务代码

def logging_tool(func):
    logging.info('%s is running...' % func.__name__)
    func()

def today():
    print('2018-05-25')

logging_tool(today)

 

这样做逻辑上是没问题的,功能是实现了,但是我们调用的时候不再是调用真正的业务逻辑today函数,而是换成了logging_tool函数,这就破坏了原有的代码结构,为了支持日志功能,原有代码需要大幅修改,那么有没有更好的方式的呢?当然有,答案就是装饰器。

如果用mypy检查上面的代码就会得到如下提示:

4、Easter egg

(1) 定义一个接受参数的包装器

@decorator(x, y, z)
def func(a, b):
      pass

 

等价于

func = decorator(x, y, z)(func)

 

即:decorator(x, y, z)的返回结果必须是一个可调用的对象,它接受一个函数作为参数并包装它。

(2)一个函数可以同时定义多个装饰器,比如:

@a
@b
@c
def f():
    pass

 

等价于

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

 

即:它的执行顺序是从里到外,最先调用最里层,最后调用最外层的装饰器。

有了Annotation的Python3显然是对Python大工程开发能力的增强,动态语言最大的弊端就是太灵活,当然这也是优点。不过对于有经验的开发来说,避免去修改函数的输入和输出是重要的设计思想。对于以往的参数,因为没有类型信息,所以我们需要在函数内部去做各种判断,来保证数据类型符合预期。

Python3 >= 3.5

另外谨慎声明:本篇文章仅仅是作者在看文档和部分代码是的一些想法,仅供参考。

这种方案更类似于静态语言中的interface的概念,定义一个通用的父类,这样的话,你可以传递子类型过去。

这么定义函数,可以达到静态类型的效果。如果你尝试使用foo(2) 传入一个int类型的话就会报错:

参考

这么用的作用是什么呢?文档中有一句话很重要:

需要提醒的是,Python3.5.1版本的overload是不对外使用的,如果你在这个版本下尝试上面的代码应该会报错: Overloading is only supported in library stubs。

“The @overload-decorated definitions are for the benefit of the type checker only, since they will be overwritten by the non-@overload-decorated definition, while the latter is used at runtime but should be ignored by a type checker. ”

overload翻译过来是“重载”的意思,Java中有这样的两个概念,重写(override)和重载(overload)。重写其实是在保证输入和输出不变的情况下重写实现逻辑。而重载则是允许修改输入和输出,即同一个方法名可以支持多种类型的输入和输出。

所以,看到这应该明白了,overload仅仅是给检查工具用的。但如果静态类型检查变成工程的一部分的话,这也会避免很多问题,在写代码时也会比心里有底。

再来看第二种的解决方案,也就是重载的方式。但是跟静态语言中还是很有差别。

一、使用typing.TypeVar

Python3标准库漫游之Typing.overload

上面介绍了annotation能够声明类型,这样在执行时能发现错误,也能够在静态检查阶段发现错误。但是如果函数的参数确实需要多种类型呢?不能因为静态类型的声明而导致动态易用性的损失吧。

翻译过来就是, 被overload装饰的函数仅仅是为了受益于类型检查工具,因为它们会被没有overload装饰的函数定义覆盖,尽管未被装饰的函数是用于运行时的,但是会被类型检查工具忽略。

因此有两种方案:

通过定义多个同名函数,上面的同名函数需要通过overload装饰器装饰。可以看到被装饰的函数的输入类型和输出类型都可以更改。但是,最后的实现方法一定要通用,也就是没有类型注解。

——段子来源:陈浩(coolshell)在<代码时间>的访谈。

最后讲一个段子:某创业公司司招了个比较水的程序员,在招不到人的情况下,为了保证项目进度,于是又招了项目经理。因为当程序员较水,搞不定服务器上的配置,于是又招了个运维工程师,...

澳门新濠3559 6

Python3中增加了Function Annotation的功能,翻译过来就是函数(方法)注解,具体用法就是:

二、使用typing.overload

总结

然后我们再来看overload。

编辑:编程 本文来源:希望可以记录下函数的执行日志,通过Decorator接

关键词: 澳门新濠3559