python中Decorators的用法

探索LangChain Decorators:提升你的Prompt编写效率 LangChain Decorators通过提供更为简洁和Pythonic的代码编写方式,极大简化了LangChain的开发流程。查看LangChain Decorators的GitHub仓库获取更多示例和支持。探索和中的实际示例。深入理解Python的装饰器和异步编程,以更好地利用LangChain Decorators的功能。 阅读详情
最近看别人写的一个源码,发现有很多地方用到了@符号。有的还传入了参数。觉得这个好像很强大。所以就在网上找了一些相关的资料。
也有一个专门讲python的网页也不错:
http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001386820062641f3bcc60a4b164f8d91df476445697b9e000
一、Decorators入门

我预计它会成为Python最重要的几个特性之一。而问题是我见过的所有介绍decorators的文章都很容易让人感到困惑。所以我打算在这里给以纠正以正视听。

(本系列文章将录入开源类书籍Python3之模式和用法)

 

Decorators vs. Decorator模式

首先,你得明白使用 “decorator”一词是十分谨慎的决定,因为它可能会让人联想到

Design Patterns(设计模式)一书中的Decorator模式。从某种角度看可能别的词也适用于这个特性,但“decorator”仍是不二之选。

实际上你可以使用Python decorators来实现Decorator模式,但它的威力远不止于此。我想,最和Python decorators接近的就是宏(macros)了。

 

宏的由来

宏的历史并不短,不过多数人大概都是使用过C的预处理宏。C中宏的问题是:

(1) 它们属于另外一个语言(不是C);

(2) 其行为有时候很古怪。并且经常和其余C的行为有不一致的情况。

Java和C#都加入了annotations机制,其允许用户为各程序变量进行标注。它们的问题是:

(1) 为了实现一些东西,你有时不得不忍受方方面面的“磨练”

(2)annotation的特性因为这些语言天生的规范条款(Martin Fowler文绉绉地称之为“导向”)而受到局限。

除此之外,很多C++程序员(也包括我)也领教过C++模版的生成能力,并多少使用过一些这个和宏相似的特性。

很多其他语言都有过宏。虽然对其知之不多,但我敢肯定,Python decorators与Lisp宏在能力上不分伯仲。

 

为什么使用宏

我想,从保守一点的角度说,宏在编程语言中的作用是提供了一种修改程序变量的方法。这也是Python中的decorators的工作--它们修改函数、若对class decorators则是整个类。这也是为何它们常常为metaclasses(元类)提供一个更简单的选择。

多数语言的自修正方法的主要失败之处是它们限制太多了,并且需要另外一种语言(我并不是说Java的annotations为了造就一个与众不同的annotation而使人饱经磨练,它就算包含了“另外一种语言”)。

Python被Fowler归入了“授权(enabling)”语言的行列,所以你如果想实现修正,为何还去创建一种与之不同或限制更多的语言?为何不使用Python本身呢?这恰恰是Python decorators的使命。

 

Decorators的用途

Decorators允许在函数和类中嵌入或修改代码。这听起来和Java的Aspect-Oriented Programming(AOP,面向方面编程)挺像的,对吧?除此之外前者其实更简单,也更加强大。例如,假设你想在一个函数的入口和出口处做点手脚(比如做一些安全、跟踪和锁等--一切AOP标准变量)。使用decorators实现代码如下:

 

@entryExit

def func1():

    print "inside func1()"

 

@entryExit

def func2():

    print "inside func2()"

 

‘@’表明了是decorator程序。

 

函数Decorators

一个函数decorators用于函数定义,它位于在函数定义之前的一行。例如:

 

@myDecorator

def aFunction():

    print "inside aFunction"

 

当编译器经过这段代码时,aFunction()被编译然后将结果函数对象传递给myDecorator代码,后者创建一个类函数对象并取代原来的aFunction()。

myDecorator代码长什么样呢?大多数介绍性的例子都将其作为函数给出,但我发现对于decoration机制使用类而非函数来理解decorators会更容易些,而且也更强大。

Decorator所返回的对象唯一的约束是它可以作为函数使用--也就意味着它必须是可调用的。因此,作为decorators使用的任何一个类必须实现__call__。

decorator该做什么呢?它什么都能做,只是你常常期望在某些地方能够使用原来的函数代码。其实没有这个必要:

 

class myDecorator(object):

 

    def __init__(self, f):

        print "inside myDecorator.__init__()"

        f() # Prove that function definition has completed

 

    def __call__(self):

        print "inside myDecorator.__call__()"

 

@myDecorator

def aFunction():

    print "inside aFunction()"

 

print "Finished decorating aFunction()"

 

aFunction()

 

当运行这段代码时,会看见:

 

inside myDecorator.__init__()

inside aFunction()

Finished decorating aFunction()

inside myDecorator.__call__()

 

注意,myDecorator的构造器(constructor)在函数的decoration处执行。由于我们可以在__init__()里面调用f(),它意味着在调用decorator前就完成了f()的创建。另外需注意,decorator构造器接收到decorated的函数对象。你将在构造器中得到函数对象,之后在__call__()方法中进行使用(当使用类时,decoration和调用是两个泾渭分明的阶段,这也是我为何说它更简单、更强大的原因)。

当decorated之后再调用aFunction(),它的行为就完全不一样了:不再用原来的代码而开始调用myDecorator.__call__()方法。原因是decoration过程是将decoration结果取代原先的函数--在我们的例子中,myDecorator对象取代了aFunction。实际上,加入decorators之前,为了达到同样效果你需要编写十分晦涩的代码:

 

def foo(): pass

foo = staticmethod(foo)

 

有了‘@’这个decoration操作符的加入,你可以这样做:

 

@staticmethod

def foo(): pass

 

这也是有人为何反对decorators的原因。因为‘@’实在有点像一块语法糖(syntax sugar,意指指那些没有给计算机语言添加新功能,而只是对人类来说更“甜蜜“的语法。语法糖往往给程序员提供了更实用的编码方式,有益于更好的编码风格,更易读。不过其并没有给语言添加什么新东西。--译者注):通过另外一个函数传递函数对象,然后将结果赋给原来的函数。

我认为,decorator之所以产生如此大影响,就是因为其带着一点点语法糖的味道改变了人们思考程序设计的方式。事实上,当成一种语言概念对其进行形式化,这种方法使“将代码应用到其他代码之中”(比如宏)的思想出现在主流思想之中。

 

更多用处

现在,让我们回过头来实现第一个例子。这里我们实现一些更常规的东西,并在decorated函数中实际使用这些代码:

 

class entryExit(object):

 

    def __init__(self, f):

        self.f = f

 

    def __call__(self):

        print "Entering", self.f.__name__

        self.f()

        print "Exited", self.f.__name__

 

@entryExit

def func1():

    print "inside func1()"

 

@entryExit

def func2():

    print "inside func2()"

 

func1()

func2()

 

输出是:

 

Entering func1

inside func1()

Exited func1

Entering func2

inside func2()

Exited func2

 

现在可以看见,在整个调用期间decorated函数拥有了“Entering”和“Exited”跟踪语句。

构造器保存着自变量,即函数对象。在调用中,我们使用函数的__name__属性来显示函数名称,然后调用函数本身。

 

作为Decorators使用函数

对于一个decorator结果,其唯一的约束是必须是可调用的。这样它就可以完全取代decorated函数。在上面的例子中,我用包含一个__call__()方法的类的一个对象替代原有的函数。但函数对象仍是可调用的。因此我们可以使用函数(而非类)重写上一个例子,即:

 

def entryExit(f):

    def new_f():

        print "Entering", f.__name__

        f()

        print "Exited", f.__name__

    return new_f

 

@entryExit

def func1():

    print "inside func1()"

 

@entryExit

def func2():

    print "inside func2()"

 

func1()

func2()

print func1.__name__

 

new_f()在entryExit()里定义,所以在调用entryExit()时它就创建并返回了。注意,new_f()是一个闭包(closure),因为它获得的是f的实际值。

只要定义过new_f(),它就从entryExit()返回,这样decorator机制就可以将结果作为decorated函数进行赋值了。

‘print func1.__name__’一行的输出是new_f,因为new_f函数在decoration期间已取代原函数。如果还有疑问,可以在函数返回前改变decorator函数的名字:

 

def entryExit(f):

    def new_f():

        print "Entering", f.__name__

        f()

        print "Exited", f.__name__

    new_f.__name__ = f.__name__

    return new_f

 

动态获得有关函数的信息、对函数的修改能力,都是Python的强大之处。

 

更多例子

既然你有一定基础了,在这里可以看到更多一些decorators的例子。注意使用类作为decorators的例子数量远多于使用函数的例子。

在本文中我有意避开含有自变量的decorated函数,在下一篇文章中我将重点讨论它。

(本文是Python 3 Patterns & Idioms(Python3之模式和用法)一书的章节节选第二部分,点击这里阅读第一部分)

 

回顾:不含参数的decorators

在前文中,我介绍了如何使用不含参数的decorators,并使用类来实现。因为我发现这样做更容易接受。

如果创建了一个无参decorator,被decorated的函数被传至构造器,每次调用decorated函数时就会调用__call__()方法:

 

class decoratorWithoutArguments(object):

 

    def __init__(self, f):

        """

        If there are no decorator arguments, the function

        to be decorated is passed to the constructor.

        """

        print "Inside __init__()"

        self.f = f

 

    def __call__(self, *args):

        """

        The __call__ method is not called until the

        decorated function is called.

        """

        print "Inside __call__()"

        self.f(*args)

        print "After self.f(*args)"

 

@decoratorWithoutArguments

def sayHello(a1, a2, a3, a4):

    print 'sayHello arguments:', a1, a2, a3, a4

 

print "After decoration"

 

print "Preparing to call sayHello()"

sayHello("say", "hello", "argument", "list")

print "After first sayHello() call"

sayHello("a", "different", "set of", "arguments")

print "After second sayHello() call"

 

decorated函数的所有参数会被传至__call__()。输出结果是:

 

Inside __init__()

After decoration

Preparing to call sayHello()

Inside __call__()

sayHello arguments: say hello argument list

After self.f(*args)

After first sayHello() call

Inside __call__()

sayHello arguments: a different set of arguments

After self.f(*args)

After second sayHello() call

 

注意,__init__()是唯一一个被调用执行decoration的方法,每次调用decorated的sayHello()时就会调用__call__()。

 

含有参数的decorators

现在让我们来修改上面的代码,看看向decorator加入参数后结果是什么。

 

class decoratorWithArguments(object):

 

    def __init__(self, arg1, arg2, arg3):

        """

        If there are decorator arguments, the function

        to be decorated is not passed to the constructor!

        """

        print "Inside __init__()"

        self.arg1 = arg1

        self.arg2 = arg2

        self.arg3 = arg3

 

    def __call__(self, f):

        """

        If there are decorator arguments, __call__() is only called

        once, as part of the decoration process! You can only give

        it a single argument, which is the function object.

        """

        print "Inside __call__()"

        def wrapped_f(*args):

            print "Inside wrapped_f()"

            print "Decorator arguments:", self.arg1, self.arg2, self.arg3

            f(*args)

            print "After f(*args)"

        return wrapped_f

 

@decoratorWithArguments("hello", "world", 42)

def sayHello(a1, a2, a3, a4):

    print 'sayHello arguments:', a1, a2, a3, a4

 

print "After decoration"

 

print "Preparing to call sayHello()"

sayHello("say", "hello", "argument", "list")

print "after first sayHello() call"

sayHello("a", "different", "set of", "arguments")

print "after second sayHello() call"

 

从输出结果可以看到,加入参数使程序执行发生了很大变化。

 

Inside __init__()

Inside __call__()

After decoration

Preparing to call sayHello()

Inside wrapped_f()

Decorator arguments: hello world 42

sayHello arguments: say hello argument list

After f(*args)

after first sayHello() call

Inside wrapped_f()

Decorator arguments: hello world 42

sayHello arguments: a different set of arguments

After f(*args)

after second sayHello() call

 

现在decoration方法调用构造器,然后就马上调用__call__(),后者只能包含一个参数(函数对象)且返回替代原有函数的decorated函数对象。注意当前decoration期间__call__()仅被调用一次,此后从__call__()返回的decorated函数就可以在实际调用中使用了。

虽然这种机制有一定合理性—构造器在这里可获取decorator参数,但__call__()对象不能再作为decorated函数使用了。因此你必须使用__call__()执行decoration—可能第一次遇到这种与无参情况截然不同的方式你会比较吃惊,何况还必须编写和无参decorator完成不同的代码。

 

含decorator参数的decorator函数

最后,让我们看一个更复杂一点的decorator函数实现,它需要你处理所有细节:

 

def decoratorFunctionWithArguments(arg1, arg2, arg3):

    def wrap(f):

        print "Inside wrap()"

        def wrapped_f(*args):

            print "Inside wrapped_f()"

            print "Decorator arguments:", arg1, arg2, arg3

            f(*args)

            print "After f(*args)"

        return wrapped_f

    return wrap

 

@decoratorFunctionWithArguments("hello", "world", 42)

def sayHello(a1, a2, a3, a4):

    print 'sayHello arguments:', a1, a2, a3, a4

 

print "After decoration"

 

print "Preparing to call sayHello()"

sayHello("say", "hello", "argument", "list")

print "after first sayHello() call"

sayHello("a", "different", "set of", "arguments")

print "after second sayHello() call"

 

输出结果为:

 

Inside wrap()

After decoration

Preparing to call sayHello()

Inside wrapped_f()

Decorator arguments: hello world 42

sayHello arguments: say hello argument list

After f(*args)

after first sayHello() call

Inside wrapped_f()

Decorator arguments: hello world 42

sayHello arguments: a different set of arguments

After f(*args)

after second sayHello() call

 

decorator函数的返回值必须是一个封装待decorated函数的函数。也就是说,Python会保存返回函数然后在decoration期间调用,并传递待decorated函数。这也是为何有三层函数的原因:里面那个函数才是被替换的。

由于闭包,wrapped_f()有权访问decorator参数arg1, arg2 和arg3,而无需像在class版本中那样显式存储它们。然而,我也是在这里发现了“显胜于隐(explicit is better than implicit)”。即使该函数版本看起来要更加简洁紧凑,但我发现还是类版本容易理解,当然也就容易修改和维护。

 

下一节内容

在下一节中我会给出decorators的一些实例—基于Python开发的build system—然后在最后一节讨论类decorators。


我使用make已有很多年了。我只使用ant的原因是它可以创建速度更快的java build。但这两个构建系统都是以“问题是简单的”这个出发点考虑问题的,所以此后不久就发现真正需要的是一个程序设计语言来解决构建问题。可为时已晚,作为代价你需要付出大量艰辛来搞定问题。

在语言之上实现构建系统已有一些成果。Rake是一个十分成功的基于Ruby的领域特定语言(domain specific language, DSL)。还有不少项目也是用Python完成的,

多年来我一直想能有一个Python上的系统,其作用类似于一个薄薄的“胶合板”。这样可以得到一些依赖性上的支持,即使都是Python上的。因为如此一来你就不必在Python和Python以外的语言之间来回奔波,减少了思维上的分心。

最终发现decorators是解决该问题的最佳选择。我这里设计的仅是一个雏形,但很容易添加新特性,而且我已经开始将它作为The Python Book的构建系统了,也需要增加更多特性。更重要的是,我知道我能够做任何想做而make和ant做不到的事(没错,你可以扩展ant,但常常是得不偿失)。

尽管书中其余部分有一个Creative Commons Attribution-Share Alike license(Creative Commons相同方式共享署名许可),可该程序仅有一个Creative Commons Attribution许可,因为我想要人们能够在任何环境下使用它。很显然,如果你做了任何回馈于项目的改进方面的贡献,那就再好不过了。不过这不是使用和修改代码的前提条件。

语法

该构建系统提供的最重要和最便捷的特性就是依赖(dependencies)。你告诉它什么依赖于什么,如何更新这些依赖,这就叫做一个规则(rule)。因此decorator也被称为rule。decorator第一个参数是目标对象(target,需要更新的变量),其余参数都属于依赖(dependencies)。如果目标对象相对于依赖过时,函数就要对它进行更新。

下面是一个反映基本语法的简单例子:

 

@rule("file1.txt")

def file1():

    "File doesn't exist; run rule"

    file("file1.txt", 'w')

 

规则名称是file1,因为这也是函数名称。在这里例子里,目标对象是"file1.txt",不含依赖,因此规则仅检查file1.txt是否存在,如果不存在则运行起更新作用的函数。

注意docstring的使用:它由构建系统获取,当你输入build help,则以命令行方式显示规则描述。

@rule decorators仅对其绑定的函数产生影响,因此你可以很容易地在同一构建文件中将普通代码和规则混合使用。这是一个更新文件时间戳的函数,如果文件不存在则创建一份:

 

def touchOrCreate(f): # Ordinary function

    "Bring file up to date; creates it if it doesn't exist"

    if os.path.exists(f):

        os.utime(f, None)

    else:

        file(f, 'w')

 

更典型的规则是将目标文件与一个或多个依赖文件进行关联:

 

@rule("target1.txt","dependency1.txt","dependency2.txt","dependency3.txt")

def target1():

    "Brings target1.txt up to date with its dependencies"

    touchOrCreate("target1.txt")

 

构建系统也允许加入多个目标,方法是将这些目标放入一个列表中:

 

@rule(["target1.txt", "target2.txt"], "dependency1.txt", "dependency2.txt")

def multipleBoth():

    "Multiple targets and dependencies"

    [touchOrCreate(f) for f in ["target1.txt", "target2.txt"]]

 

如果目标对象和依赖都不存在,规则通常这样运行:

 

@rule()

def clean():

    "Remove all created files"

    [os.remove(f) for f in allFiles if os.path.exists(f)]

 

这个例子中出现了alFiles数组,稍后作以介绍。

你也可以编写依赖于其他规则的规则:

 

@rule(None, target1, target2)

def target3():

    "Always brings target1 and target2 up to date"

    print target3

 

由于None是目标对象,这里不存在比较,但在检查target1和target2的规则过程中,它们将被更新。这在编写“所有(all)”规则时十分有用,下面例子中将涉及到。

构建器代码

通过使用decorators和一些恰当的设计模式,代码变得十分简洁。需要注意的是,代码__main__创建了一个名为“build.by”的示例文件(包含了你上面看到的例子)。当你第一次运行构建时,它创建了一个build.bat文件(在windows中)或build命令文件(在Unix/Linux/Cygwin中)。完整的解释说明附在代码后面:

 

# builder.py

import sys, os, stat

"""

Adds build rules atop Python, to replace make, etc.

by Bruce Eckel

License: Creative Commons with Attribution.

"""

 

def reportError(msg):

    print >> sys.stderr, "Error:", msg

    sys.exit(1)

 

class Dependency(object):

    "Created by the decorator to represent a single dependency relation"

 

    changed = True

    unchanged = False

 

    @staticmethod

    def show(flag):

        if flag: return "Updated"

        return "Unchanged"

 

    def __init__(self, target, dependency):

        self.target = target

        self.dependency = dependency

 

    def __str__(self):

        return "target: %s, dependency: %s" % (self.target, self.dependency)

 

    @staticmethod

    def create(target, dependency): # Simple Factory

        if target == None:

            return NoTarget(dependency)

        if type(target) == str: # String means file name

            if dependency == None:

                return FileToNone(target, None)

            if type(dependency) == str:

                return FileToFile(target, dependency)

            if type(dependency) == Dependency:

                return FileToDependency(target, dependency)

        reportError("No match found in create() for target: %s, dependency: %s"

            % (target,  dependency))

 

    def updated(self):

        """

        Call to determine whether this is up to date.

        Returns 'changed' if it had to update itself.

        """

        assert False, "Must override Dependency.updated() in derived class"

 

class NoTarget(Dependency): # Always call updated() on dependency

    def __init__(self, dependency):

        Dependency.__init__(self, None, dependency)

    def updated(self):

        if not self.dependency:

            return Dependency.changed # (None, None) -> always run rule

        return self.dependency.updated() # Must be a Dependency or subclass

 

class FileToNone(Dependency): # Run rule if file doesn't exist

    def updated(self):

        if not os.path.exists(self.target):

            return Dependency.changed

        return Dependency.unchanged

 

class FileToFile(Dependency): # Compare file datestamps

    def updated(self):

        if not os.path.exists(self.dependency):

            reportError("%s does not exist" % self.dependency)

        if not os.path.exists(self.target):

            return Dependency.changed # If it doesn't exist it needs to be made

        if os.path.getmtime(self.dependency) > os.path.getmtime(self.target):

            return Dependency.changed

        return Dependency.unchanged

 

class FileToDependency(Dependency): # Update if dependency object has changed

    def updated(self):

        if self.dependency.updated():

            return Dependency.changed

        if not os.path.exists(self.target):

            return Dependency.changed # If it doesn't exist it needs to be made

        return Dependency.unchanged

 

class rule(object):

    """

    Decorator that turns a function into a build rule. First file or object in

    decorator arglist is the target, remainder are dependencies.

    """

    rules = []

    default = None

 

    class _Rule(object):

        """

        Command pattern. name, dependencies, ruleUpdater and description are

        all injected by class rule.

        """

 

        def updated(self):

            if Dependency.changed in [d.updated() for d in self.dependencies]:

                self.ruleUpdater()

                return Dependency.changed

            return Dependency.unchanged

 

        def __str__(self): return self.description

 

    def __init__(self, *decoratorArgs):

        """

        This constructor is called first when the decorated function is

        defined, and captures the arguments passed to the decorator itself.

        (Note Builder pattern)

        """

        self._rule = rule._Rule()

        decoratorArgs = list(decoratorArgs)

        if decoratorArgs:

            if len(decoratorArgs) == 1:

                decoratorArgs.append(None)

            target = decoratorArgs.pop(0)

            if type(target) != list:

                target = [target]

            self._rule.dependencies = [Dependency.create(targ, dep)

                for targ in target for dep in decoratorArgs]

        else: # No arguments

            self._rule.dependencies = [Dependency.create(None, None)]

 

    def __call__(self, func):

        """

        This is called right after the constructor, and is passed the function

        object being decorated. The returned _rule object replaces the original

        function.

        """

        if func.__name__ in [r.name for r in rule.rules]:

            reportError("@rule name %s must be unique" % func.__name__)

        self._rule.name = func.__name__

        self._rule.description = func.__doc__ or ""

        self._rule.ruleUpdater = func

        rule.rules.append(self._rule)

        return self._rule # This is substituted as the decorated function

 

    @staticmethod

    def update(x):

        if x == 0:

            if rule.default:

                return rule.default.updated()

            else:

                return rule.rules[0].updated()

        # Look up by name

        for r in rule.rules:

            if x == r.name:

                return r.updated()

        raise KeyError

 

    @staticmethod

    def main():

        """

        Produce command-line behavior

        """

        if len(sys.argv) == 1:

            print Dependency.show(rule.update(0))

        try:

            for arg in sys.argv[1:]:

                print Dependency.show(rule.update(arg))

        except KeyError:

            print "Available rules are:/n"

            for r in rule.rules:

                if r == rule.default:

                    newline = " (Default if no rule is specified)/n"

                else:

                    newline = "/n"

                print "%s:%s/t%s/n" % (r.name, newline, r)

            print "(Multiple targets will be updated in order)"

        # Create "build" commands for Windows and Unix:

        if not os.path.exists("build.bat"):

            file("build.bat", 'w').write("python build.py %1 %2 %3 %4 %5 %6 %7")

        if not os.path.exists("build"):

            # Unless you can detect cygwin independently of Windows

            file("build", 'w').write("python build.py $*")

            os.chmod("build", stat.S_IEXEC)

 

############### Test/Usage Examples ###############

 

if __name__ == "__main__":

    if not os.path.exists("build.py"):

        file("build.py", 'w').write('''/

# Use cases: both test code and usage examples

from builder import rule

import os

 

@rule("file1.txt")

def file1():

    "File doesn't exist; run rule"

    file("file1.txt", 'w')

 

def touchOrCreate(f): # Ordinary function

    "Bring file up to date; creates it if it doesn't exist"

    if os.path.exists(f):

        os.utime(f, None)

    else:

        file(f, 'w')

 

dependencies = ["dependency1.txt", "dependency2.txt",

                "dependency3.txt", "dependency4.txt"]

 

targets = ["file1.txt", "target1.txt", "target2.txt"]

 

allFiles = targets + dependencies

 

@rule(allFiles)

def multipleTargets():

    "Multiple files don't exist; run rule"

    [file(f, 'w') for f in allFiles if not os.path.exists(f)]

 

@rule(["target1.txt", "target2.txt"], "dependency1.txt", "dependency2.txt")

def multipleBoth():

    "Multiple targets and dependencies"

    [touchOrCreate(f) for f in ["target1.txt", "target2.txt"]]

 

@rule("target1.txt","dependency1.txt","dependency2.txt","dependency3.txt")

def target1():

    "Brings target1.txt up to date with its dependencies"

    touchOrCreate("target1.txt")

 

@rule()

def updateDependency():

    "Updates the timestamp on all dependency.* files"

    [touchOrCreate(f) for f in allFiles if f.startswith("dependency")]

 

@rule()

def clean():

    "Remove all created files"

    [os.remove(f) for f in allFiles if os.path.exists(f)]

 

@rule()

def cleanTargets():

    "Remove all target files"

    [os.remove(f) for f in targets if os.path.exists(f)]

 

@rule("target2.txt", "dependency2.txt", "dependency4.txt")

def target2():

    "Brings target2.txt up to date with its dependencies, or creates it"

    touchOrCreate("target2.txt")

 

@rule(None, target1, target2)

def target3():

    "Always brings target1 and target2 up to date"

    print target3

 

@rule(None, clean, file1, multipleTargets, multipleBoth, target1,

      updateDependency, target2, target3)

def all():

    "Brings everything up to date"

    print all

 

rule.default = all

rule.main() # Does the build, handles command-line arguments

''')

 

第一组类管理不同类型对象间的依赖。基类包含了一些通用代码,比如,当在派生类中没有显式重定义构造器则自动调用构造器(Python中一个节省代码的优秀特性)

Dependency的派生类管理依赖关系中的具体类型,并重定义updated()方法以根据依赖判断目标对象是否需要更新。这是一个Template Method(模版方法)设计模式的例子,updated()是模版方法,_Rule是上下文(context)。

如果你想创建一个新类型的依赖—即增加依赖和/或目标上的通配符—你就要定义新的Dependency子类。你会发现其余代码不需要更改,这是设计的一个独到之处(未来的变化相对独立)。

Dependency.create()是所谓的Simple Factory Method(简单工厂模式),因为它的所有工作是使创建所有Dependency子类型实现本地化。注意这里向前引用并不是问题,因为它就存在在一些语言里面。所以没必要使用GoF提供的更复杂的Factory Method完整实现(并不意味着完整版的Factory Method就没有用武到之地)。

注意,在FileToDependency中,我们可以声明self.dependency是Dependency的一个子类型,但在调用updated()时才会进行类型检查(有效性)。

一个rule Decorator

rule decorator使用了Builder(构造器)设计模式,其原因是一个规则的创建分为两步:构造器获取decorator参数,__call__()方法获取函数。

Builder产品是一个_Rule对象。像Dependency类一样,它包含一个updated()方法。每个_Rule对象包含一个dependencies列表和一个ruleUpdater()方法,后者在任何一个依赖过时时调用。_Rule还包含一个name(decorated函数名)和一个description(decorated函数的docstring)。(_Rule对象是一个Command(命令)模式的例子)。

_Rule的不寻常之处在于你在类中看不到任何初始化dependencies, ruleUpdater(), name和description的代码。它们是在Builder方法过程中使用Injection并由rule完成初始化的。其典型的替代方法是创建setter方法,但由于_Rule是内嵌到rule中,rule便“拥有了”_Rule,Injection也看起来更简单直接一些。

rule构造器首先创建了产品_Rule对象,然后处理decorator参数。它将decoratorArgs转换为一个list—因为我们需要对其对进行修改—并使decoratorArgs作为一个tuple出现。如果仅有一个参数,那就意味着用户仅指定了目标对象而没有依赖。因为Dependency.create()需要两个参数,我们将None加入到列表中。

目标对象通常是第一个参数,因此pop(0)将它弹出来,列表中剩下的就是依赖了。为了适应目标对象可能是一个列表的情况,单独的目标对象会被转换为列表。

现在任何可能的目标-依赖组合都调用了Dependency.create(),并且结果列表被注入到_Rule对象中。当出现不含参数的特殊情况时,就会创建一个None和None Dependency。

注意,rule构造器做的唯一事情是提取参数,它对具体的关联关系一无所知。这使得Dependency级别的内部得以存储专门知识。即使增加一个新的Dependency也会与该级别相互独立。

类似的思想也出现__call__()方法上,它用于获取decorated函数。我们将_Rule对象保存在一个称为rules的静态列表中。首先检查的是是否有重复规则名,然后获取并写入名字、文件字符串和函数本身。

注意,Builder“产品”、即_Rule对象作为rule.__call__()的结果返回,这意味着该对象—不含__call__()方法—替代了decorated函数。这是decorators比较少见的用法。正常情况下decorated函数被直接调用,但在这里decorated函数不会直接调用,只能通过_Rule对象调用。

运行系统

rule中的静态方法main()负责构建过程,并使用helper方法update()。如果不提供命令行参数,main()传递0至update(),如果后者完成设置则调用默认规则,否则调用定义好第一个规则。如果提供命令行参数,它将传递每一个参数到update()。

如果给定一个错误参数(比如help就是个典型例子),它将输出附加对应docstrings的所有规则。

最后,它将检查看build.bat和build命令文件是否存在,若不存在就创建它们。

第一次运行build.py时它将作为构建文件的起点运行。

改进

按照目前情况,这个系统仅完成了基本功能,仍有不完善之处。比如,在处理依赖方面它还没有make有的所有特性。另一方面,由于它是基于一个十分完善的程序设计语言构建的,你可以很容易地做想做的事。如果你发现自己一直在重复地写相同代码,则可以修改rule()来减少这种重复劳动。如果你拥有许可,请将类似的改进建议提交上来。




用LangChain Decorators优化你的AI开发体验 ✨ 使用""""""return...LangChain Decorators为开发者提供了一种优雅的方式来编写LangChain代码,使其更容易管理和扩展。LangChain官方文档LangChain Decorators GitHub仓库。 阅读详情

相关推荐

使用LangChain Decorators提升你的Prompt编写体验

LangChain Decorators提供了一种高效的方式编写和管理LLM prompts。通过学习和实践,开发者可以大幅提升开发效率。

akhfuiigabv的博客 1009

python基础-装饰器Decoration的简单使用

首先,先看一下,未使用装饰器时,我们想查看代码的执行时间如何操作? import time def is_prime(num): if num < 2: return False elif num == 2: return True else: for i in range(2,num+1): if num%i == 0: return True return

qq_36697196的博客 354

Python中的装饰器(Decorators)及其应用场景

在Python编程中,装饰器(Decorators)是一个强大且灵活的工具,它允许我们在不修改函数或类代码的情况下,为其添加额外的功能或修改其行为。装饰器本质上是一个接受函数作为参数的可调用对象(通常是函数或类),并返回一个新的函数对象。这个符号会将紧跟其后的函数对象作为参数传递给装饰器函数,并将装饰器函数的返回值(通常是一个新的函数对象)重新绑定到原函数的名称上。:装饰器可以用于实现函数的缓存机制,将函数的计算结果存储起来,以便在下次调用时直接使用缓存结果,提高代码的执行效率。一、装饰器的基本概念。

铭渊老黄 982

Python高级用法:装饰器(decorator)

Python装饰器的作用是使函数包装与方法包装(一个函数,接受函数并返回其增强函数)变得更容易阅读和理解。最初的使用场景是在方法定义的开头能够将其定义为类方法或静态方法。

艾醒的博客 931

stm32最小系统_完成一个最小FOC矢量控制系统所需的基本模块和功能配置

当我们读懂 FOC 矢量控制的基本原理之后,便迫不及待的想动手尝试,去实现一个矢量控制系统,让电机先转起来,有一个直观的感受。因此,我们需要设计实现一个矢量控制的最小系统,具备矢量控制的基本功能,满足电机矢量运行的基本条件。首先,看一下矢量控制的基本架构:矢量控制架构如上图所描述,矢量控制系统包括实时电流的采集、clarke 变换、park 变换、SVPWM 、实时角度的反馈和计算以及电流环和速...

weixin_39524048的博客 1784

Oracle EBS R12 AP模块发票导入接口实战:从SQL插入到API调用的完整流程

本文详细解析了Oracle EBS R12 AP模块发票导入接口的完整流程,从SQL插入到API调用的实战技巧。涵盖接口表设计、数据验证、双通道导入机制及错误诊断,特别分享官方文档未提及的高阶优化方案,助力企业实现财务数字化转型。

weixin_33728268的博客 476

【python】装饰器:decorator

python装饰器。

风高秋月白,雨霁晚霞红 3424

WiseFlow本地搭建实录---保姆教程

本文介绍了如何从0开始搭建wiseflow的个人情报官的方法,目前所有的API全部都是免费的,对机器没有要求,大家可以放心搭建

liuhui244的博客 1962

python——装饰器详解

文章目录一、装饰器概念1、装饰器2、闭包二、如何创建装饰器三、python内置装饰器四、装饰器类型五、装饰器的用途 一、装饰器概念 1、装饰器 装饰器:一种返回值也是一个函数的函数,即装饰器。 装饰器的目的:装饰器是用于拓展原函数的一种函数,可以在不改变原来函数名或类名的情况下,给原函数增加新的功能。 一般情况下,我们需要改变原函数,直接改原函数的代码即可,但是实际情况中,并不是所有的函数代码(...

qq_45827239的博客 902

【MagicDrive环境配置】新手配俩星期版

1.创建一个新的环境conda create -n newdrive python==3.82.激活该环境conda activate newdrive3.下载MagicDrive源码 git clone --recursive https://github.com/cure-lab/MagicDrive.git,如果出现时间超时八成是网的问题,直接自己下载解压就好3.我的cuda版本是11.7,但11.7导致后续安装有冲突,因此进入。

m0_46261292的博客 2203

2026华为OD面试题029:勇攀数字高峰

在给定的数字地形图里寻找登山路径,数字代表当前位置的海拔高度。要求从最低海拔出发,不断攀登,最终到达最高山峰。地图保证最低海拔和最高山峰都只有一个。 登山规则: - 路径上的海拔必须严格递增 - 只能向上下左右 4 个方向移动 - 路径必须从最低海拔开始,到最高海拔结束 - 每个地点只能走一次 - 每一步的攀登高度差必须大于 0,且不超过指定值 `K` 输出满足条件的登山路径的数量。

琅嬛福地 120

一篇文章理解Python 装饰器(Decorator)

Python 装饰器(Decorator)可以在不改变函数代码和引用方式的情况下给函数增加新的功能。 提起Python 装饰器,大家必定感觉特别神秘。其实装饰器本质上就是一个函数(func_A),它接受被装饰的函数(func_B)作为参数,并在其内部嵌套了一个函数(func_C),func_C 对被装饰的函数B进行封装(通常是在func B的前后增加一些功能),然后返回这个包装过的函数fun...

katrinawj的博客 443

【HALCON 25.11 + C#】 01:2026年工业机器视觉实战详解(从入门到精通)

2026年机器视觉市场规模将突破230亿美元,HALCON 25.11的革命性更新与C#的工业集成优势形成黄金技术栈。本文作为付费专栏开篇序章,聚焦“为什么现在必须学25.11+C#”的核心问题,通过真实工业案例、五项核心技术突破、联合编程架构优势及30篇系统学习路线,全方位论证其投资价值。读者将掌握持续学习、深度OCR加速等关键技能,获得可直接上产线的工业级代码资产与架构设计思维,解决柔性生产、边缘部署等核心痛点,成为具备技术溢价的机器视觉工程师。

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 1225

Python - Decorators I: Introduction to Python Decorators

分享一个大牛的人工智能教程。零基础!通俗易懂!风趣幽默!希望你也加入到人工智能的队伍中来!请点击http://www.captainbed.net I predict that in time it will be seen as one of the more powerful features in the language. The problem is that all the intr...

了解➔熟悉➔掌握➔精通 3万+

【论文汇总】Diffusion Models视频生成/视频编辑/可控视频生成/跨模态视频生成

本文总结了Diffusion Models视频生成领域相关的工作,目前共收录142篇,持续更新中。

沉迷单车的追风少年 1673

25杰瑞机械工程师面试经验机械面试问题总结技巧面试全攻略

作为一个对机械工程充满热情的应届生,我有幸参加了杰瑞集团的机械工程师面试。面试过程既充满挑战,也不乏乐趣。以下是我面试的详细经历,包括面试流程、面试题目及参考答案。 #### 面试流程 1. **HR初面**:主要是对个人背景、教育经历和工作经验的初步了解。 2. **技术面**:由技术部门的主管进行,主要考察专业知识和项目经验。 3. **部门经理面**:更深入地了解我的技术能力和团队合作精神。 4. **总监终面**:最后一轮面试,总监会对候选人的整体素质进行评估。

m0_72216164的博客 2794

Python装饰器decorators和pytest夹具fixture详解和使用

本文介绍了Python装饰器与pytest夹具(fixture)的区别与联系。装饰器是一种高级功能,用于动态修改函数或类的行为,常用于日志、性能分析等场景。文章详细讲解了装饰器的基本语法、参数处理及内置装饰器的使用。而fixture是pytest框架中用于测试用例预置环境的机制,本质上是一个封装好的装饰器。文中重点分析了fixture的五个参数:scope(作用域)、autouse(自动执行)、params(参数)、ids(参数别名)和name(fixture别名),并通过代码示例展示了各参数的用法,帮助读

小轩的名古屋 1226

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

decorators 参数_decorators.xml的用法 (转)

spring,hibernate框架做的,对了,还有jQuery。 我用jquery做异步请求到后台,生成json数据返回前台生成下拉输入框,请求到后台以后,成功生成了json数据并根据struts的映射文件返回 到一个jsp页面,这个页面的作用只是生成文本数据,但是js方面一直没有反应。。。从网上查到jquery.ajax有一个参数error,它可以告诉 我这中间出了什么问题,error代码加上...

weixin_40006963的博客 152

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

Decorators-Primer on Python Decorators 装饰器入门

Decorators-Primer on Python Decorators 装饰器入门

labmem012的博客 1187

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

python decorators

Decorator is a very expressive language feature in python. It helps developers to write cleaner, modular code that is easier to extend and maintain. It also helps implement AOP and decorator pattern in python. Decorator syntax To declare a decorator funct

just do IT 697

Python Decorators入门 (一)

Decorators I: Introduction to Python DecoratorsOctober 18, 2008  我预计它会成为Python最重要的几个特性之一。而问题是我见过的所有介绍decorators的文章都很容易让人感到困惑。所以我打算在这里给以纠正以正视听。(本系列文章将录入开源类书籍Python3之模式和用法) Decorators vs.

Bruce Eckel 博客中文版 2万+

python decorators_python decorators

python decorators装饰器基础Decorator 本质@ 本质是语法糖- Syntactic Sugar使用@decorator 来修饰某个函数 func 时:@decoratordef func():pass其解释器会解释成:func = decorator(func)注意这条语句会被执行多重装饰器@decorator_one@decorator_twodef func():pas...

weixin_39732316的博客 211

Python decorators

Python代码里经常看到一个符号@摆在函数的上面一行,这叫decorator,但它到底是什么,表示什么意思,有什么好处呢?decorator是用来修饰函数的,等于将原函数(比如foo)经过了一定加工,达成的效果是foo = our_decorator(foo)这个样子,可以简写成@our_decorator在foo上面。 举个简单的例子:首先我们定义一个函数 def foo(x): print("Hi, foo has been called with " + str(x)) 调用此函数 f

Sherryleen的博客 292

python-Decorators

Decorators详解

qq_45602911的博客 139

python decorators_Python Decorators

DecoratorsNoteThis chapter is a work in progress; it’s probably better if you don’t begin making changes until I’ve finished the original version, which is being posted as a series on my weblog.This a...

weixin_39653717的博客 183

Python装饰器Decorators

这样做逻辑上是没问题的,功能是实现了,但是我们调用的时候不再是调用真正的业务逻辑 foo 函数,而是换成了 use_logging 函数,这就破坏了原有的代码结构, 现在我们不得不每次都要把原来的那个 foo 函数作为参数传递给 use_logging 函数,那么有没有更好的方式的呢?当然有,答案就是装饰器。foo() 函数不需要做任何修改,只需在定义的地方加上装饰器,调用的时候还是和以前一样,如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。

喜欢打篮球的普通人 651
上一篇: 想设计一门全新的编程语言
下一篇: iOS推送
woodcol
博客等级 码龄19年 288粉丝 429原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值