Tuesday, May 5, 2009

Python| decorator 学习

Python decorator 学习 - Windows Live

November 09

Python decorator 学习

Python 程序一向简洁易读。不过也有例外,这两天 decorator 的语法就让我晕了一阵。不带参数时还好些,带参数的 decorator 定义要三层函数嵌套,网上的例子也比较少,自己想的时候是吃了点苦头。不过真正搞明白了也发现就那么回事。恩,记下此例以备忘:
 
目标描述:
写一个 decorator,使函数调用时能够打印被调用的次数和传给他的参数;且这两个功能由传给 decorator 的两个参数作为开关。
 
第一步:为目标函数func做第一层封装。
def newfunc(*kwds,**dic):
    #do something before the call of func
    if dbg_count:
        cnt[0] += 1
        print "Function %s() calling count= %s" % (func.__name__, cnt[0])
    if dbg_arg:
        print "Args:\t",kwds,dic
    val = func(*kwds,**dic)
    #do something after the call of func
    #log.write( str(val) + os.linesep )
    return val

*kwds 和 **dic 是要传给函数 func 的参数,我们在 newfunc 中把他们截获,做一点准备工作后再调用 func 。dbg_count 和 dbg_arg 是 decorator 接收的两个参数,分别作为是否打印对应信息的开关。cnt 是只有一个元素的 list , 用它的 0 号元素储存函数的调用次数,后面再具体分析。
 
第二步:为 newfunc 做一层封装,将他作为替代 func 的函数返回。
def loadf(func):
    cnt = [0]
    def newfunc(*kwds,**dic):
        #do something before the call of func
        if dbg_count:
            cnt[0] += 1
            print "Function %s() calling count= %s" % (func.__name__, cnt[0])
        if dbg_arg:
            print "Args:\t",kwds,dic
        val = func(*kwds,**dic)
        #do something after the call of func
        #log.write( str(val) + os.linesep )
        return val
    return newfunc

与第一步相比只增加了三行,完成两件事:(1)返回刚刚定义的 newfunc 函数,作为 func 的替代;(2)初始化函数调用计数器 cnt 。第一件事比较好理解,返回 newfunc 函数后,如果不考虑那两个控制参数,loadf 已经可以当成一个不含参数的 decorator 来使用了。关于第二件事,这里实际上 cnt[0] 是作为函数 newfunc 的一个静态变量来使用的。作为 c/c++ 程序员,当遇到给函数添加调用计数器时自然想到用静态变量来储存,但在 Python 中没有明确的 静态变量 声明语法,该如何保存上次函数调用后的一些值呢——嵌套函数,这是 Python 中的方法 (或许有人会说yield 或 Generator,那个虽然我也试过,但用作静态变量时很不自然,暂不讨论)。外层函数中定义的 list或dict 变量,在内层函数中一直有效且多次调用内层函数时不会重复初始化,完全就是静态变量的翻版嘛...这里还有个疑问,既然只用保存一个量,为何不用一个 int 型的 cnt ,而要麻烦的动用 list 呢。嘛,懒得写了,试过你就知道了。
 
第三步:最后一层封装,用来接收那两个控制参数。下面是最终版

def dbg_config(dbg_count=True, dbg_arg=True):
    def loadf(func):
        cnt = [0]
        def newfunc(*kwds,**dic):
            #do something before the call of real function
            if dbg_count:
                cnt[0] += 1
                print "Function %s() calling count= %s" % (func.__name__, cnt[0])
            if dbg_arg:
                print "Args:\t",kwds,dic
            val = func(*kwds,**dic)
            #do something after the call of real function
            #log.write( str(val) + os.linesep )
            return val
        return newfunc
    return loadf

做的事情很简单,接收参数,最后返回 loadf 。来分析一下如何起作用的:
@dbg_config(True,True)
def func(a,b):pass
 
调用 func(1,2) 时,展开后就是
dbg_config(True,True)(func)(1,2)
由于 dbg_config(True,True) 返回的是 loadf ,于是上面继续展开,得到
loadf(func)(1,2)
loadf(func) 又返回 newfunc ,变成
newfunc(1,2)
就把对 func 的调用,通过两层封装,变成了对 newfunc 的调用。newfunc 做点补充工作,最后在内部调用了 func
 
上面,由于 decorator 要接收参数,所以得三层封装。若不带参数时只用两层即可。
 
最后看下使用情况:
>>> @dbg_config(dbg_arg=False)
... def foo():pass
...
>>> foo()
Function foo() calling count= 1
>>> foo()
Function foo() calling count= 2
>>> foo()
Function foo() calling count= 3
通过将 dbg_arg 赋值为 False,关闭打印参数的功能
 
>>> @dbg_config()
... def add(a,b,c):
...     return a+b+c
...
>>> print add(1,2,3)
Function add() calling count= 1
Args: (1, 2, 3) {}
6

>>> print add(3,c=2,b=5)
Function add() calling count= 2
Args: (3,) {'c': 2, 'b': 5}
10
两个功能都打开(默认),打印了调用次数,也打印了参数,最后输出运行结果。
 
应该都比较清楚了。写 decorator 的时候有个 functools 模块好像有工具可以用;另外 yield 从 2.4 版开始可以作为表达式而不是一个语句来使用,灵活性大增,可以搞些复杂的用法了,以后有空再来分析吧。

Python|学习decorator的使用

[Python学习]decorator的使用 - limodou的学习记录 - DonewsBlog

在我以前介绍 Python 2.4 特性的Blog中已经介绍过了decorator了,不过,那时是照猫画虎,现在再仔细描述一下它的使用。

关于decorator的详细介绍在 Python 2.4中的What's new中已经有介绍,大家可以看一下。

如何调用decorator

基本上调用decorator有两种形式

第一种:

@A
def f ():
    ...

这种形式是decorator不带参数的写法。最终 Python 会处理为:

f = A(f)

还可以扩展成:

@A
@B
@C
def f ():
    ...

最终 Python 会处理为:

f = A(B(C(f)))

注:文档上写的是@A @B @C的形式,但实际上是不行的,要写成多行。而且执行顺序是按函数调用顺序来的,先最下面的C,然后是B,然后是A。因此,如果decorator有顺序话,一定要注意:先要执行的放在最下面,最后执行的放在最上面。(应该不存在这种倒序的关系)

第二种:

@A(args)
def f ():
    ...

这种形式是decorator带参数的写法。那么 Python 会处理为:

def f(): ...
_deco = A(args)
f = _deco(f)

可以看出, Python 会先执行A(args)得到一个decorator函数,然后再按与第一种一样的方式进行处理。

decorator函数的定义

每一个decorator都对应有相应的函数,它要对后面的函数进行处理,要么返回原来的函数对象,要么返回一个新的函数对象。请注意,decorator只用来处理函数和类方法。

第一种:

针对于第一种调用形式

def A(func):
    #处理func
    #如func.attr='decorated'
    return func
@A
def f(args):pass

上面是对func处理后,仍返回原函数对象。这个decorator函数的参数为要处理的函数。如果要返回一个新的函数,可以为:

def A(func):
    def new_func(args):
        #做一些额外的工作
        return func(args) #调用原函数继续进行处理
    return new_func
@A
def f(args):pass

要注意 new_func的定义形式要与待处理的函数相同,因此还可以写得通用一些,如:

def A(func):
    def new_func(*args, **argkw):
        #做一些额外的工作
        return func(*args, **argkw) #调用原函数继续进行处理
    return new_func
@A
def f(args):pass

可以看出,在A中定义了新的函数,然后A返回这个新的函数。在新函数中,先处理一些事情,比如对参数进行检查,或做一些其它的工作,然后再调原始的函数进行处理。这种模式可以看成,在调用函数前,通过使用decorator技术,可以在调用函数之前进行了一些处理。如果你想在调用函数之后进行一些处理,或者再进一步,在调用函数之后,根据函数的返回值进行一些处理可以写成这样:

def A(func):
    def new_func(*args, **argkw):
        result = func(*args, **argkw) #调用原函数继续进行处理
        if result:
            #做一些额外的工作
            return new_result
        else:
            return result
    return new_func
@A
def f(args):pass

第二种:

针对第二种调用形式

在文档上说,如果你的decorator在调用时使用了参数,那么你的decorator函数只会使用这些参数进行调用,因此你需要返回一个新的decorator函数,这样就与第一种形式一致了。

def A(arg):
    def _A(func):
        def new_func(args):
            #做一些额外的工作
            return func(args)
        return new_func
    return _A
@A(arg)
def f(args):pass

可以看出A(arg)返回了一个新的 decorator _A。

decorator的应用场景

不过我也一直在想,到底decorator的魔力是什么?适合在哪些场合呢?是否我需要使用它呢?

decorator的魔力就是它可以对所修饰的函数进行加工。那么这种加工是在不改变原来函数代码的情况下进行的。有点象我知道那么一点点的AOP(面向方面编程)的想法。

它适合的场合我能想到的列举出下:

  1. 象文档中所说,最初是为了使调用staticmethod和classmethod这样的方法更方便
  2. 在某些函数执行前做一些工作,如web开发中,许多函数在调用前需要先检查一下用户是否已经登录,然后才能调用
  3. 在某此函数执行后做一些工作,如调用完毕后,根据返回状态写日志
  4. 做参数检查

可能还有许多,你可以自由发挥想象

那么我需要用它吗?

我想那要看你了。不过,我想在某些情况下,使用decorator可以增加程序的灵活性,减少耦合度。比如前面所说的用户登录检查。的确可以写一个通用的登录检查函数,然后在每个函数中进行调用。但这样会造成函数不够灵活,而且增加了与其它函数之间的结合程度。如果用户登录检查功能有所修改,比如返回值的判断发生了变化,有可能每个用到它的函数都要修改。而使用decorator不会造成这一问题。同时使用decorator的语法也使得代码简单,清晰(一但你熟悉它的语法的话)。当然你不使用它是可以的。不过,这种函数之间相互结合的方式,更符合搭积木的要求,它可以把函数功能进一步分解,使得功能足够简单和单一。然后再通过decorator的机制灵活的把相关的函数串成一个串,这么一想,还真是不错。比如下面:

@A
@B
def account(args):pass

假设这是一个记帐处理函数,account只管记帐。但一个真正的记帐还有一些判断和处理,比如:B检查帐户状态,A记日志。这样的效果其实是先检查B、通过在A中的处理可以先执行account,然后再进行记日志的处理。象搭积木一样很方便,改起来也容易。甚至可以把account也写成decorator,而下面执行的函数是一个空函数。然后再通过配置文件等方法,将decorator的组合保存起来,就基本实现功能的组装化。是不是非常理想。

Python 带给人的创造力真是无穷啊!

Python| tips: 什么是*args和**kwargs?

Python tips: 什么是*args和**kwargs? - 天生我材必有用,千金散尽还复来 - JavaEye技术网站

http://www.cnblogs.com/fengmk2/archive/2008/04/21/1163766.html

Python tips: 什么是*args和**kwargs?

先来看个例子:

def foo(*args, **kwargs):     print 'args = ', args     print 'kwargs = ', kwargs     print '---------------------------------------'  if __name__ == '__main__':     foo(1,2,3,4)     foo(a=1,b=2,c=3)     foo(1,2,3,4, a=1,b=2,c=3)     foo('a', 1, None, a=1, b='2', c=3)
输出结果如下:

args =  (1, 2, 3, 4)
kwargs =  {}
---------------------------------------
args =  ()
kwargs =  {'a': 1, 'c': 3, 'b': 2}
---------------------------------------
args =  (1, 2, 3, 4)
kwargs =  {'a': 1, 'c': 3, 'b': 2}
---------------------------------------
args =  ('a', 1, None)
kwargs =  {'a': 1, 'c': 3, 'b': '2'}
---------------------------------------

可以看到,这两个是python中的可变参数。*args表示任何多个无名参数,它是一个tuple;**kwargs表示关键字参数,它是一个dict。并且同时使用*args和**kwargs时,必须*args参数列要在**kwargs前,像foo(a=1, b='2', c=3, a', 1, None, )这样调用的话,会提示语法错误“SyntaxError: non-keyword arg after keyword arg”。

 

呵呵,知道*args和**kwargs是什么了吧。还有一个很漂亮的用法,就是创建字典:

    def kw_dict(**kwargs):         return kwargs     print kw_dict(a=1,b=2,c=3) == {'a':1, 'b':2, 'c':3}

其实python中就带有dict类,使用dict(a=1,b=2,c=3)即可创建一个字典了。

 

“人生苦短,我用python。”

Python|学习python decorator模块

学习python decorator模块 - hfeeqi - JavaEye技术网站

2007-04-15

学习python decorator模块

关键字: python decorator metaclass
本文简介
decorator模块是 Michele Simionato 为简化python的decorator的使用难度而开发的,使用它,您可以更加容易的使用decorator机制写出可读性、可维护性更好的代码。
本文大部分翻译自下面这篇文档: www.phyast.pitt.edu/~micheles/python/documentation.html , 之中或会加入自己的理解或注释

decorator 模块


作者: Michele Simionato
E-mail: michele.simionato@gmail.com
版本: 2.0.1
下载: http://www.phyast.pitt.edu/~micheles/python/decorator-2.0.1.zip
网络安装: easy_install decorator
License: Python license

简介

Python 2.4 的 decorators 是一个展现语法糖的有趣的例子: 原理上, 它们的引入没有改变任何东西,因为它们没有提供任何新的在原语言里面不存在的功能; 实践中, 它们的引入能够显著的改善我们的Python代码结构. 我相信这种改变是出于好意的, 基于以下理由,decorators是一些非常好的想法:
但是,到现在为止(译者注: 到Python 2.5 里面已经有所改善),正确的定制一个decorators需要一些经验, 它比我们想像的要难于使用。例如,典型的decorators的实现涉及到嵌套(nested)函数,而我们都知道:flat 比 nested 好。

decorator的目的是为普通开发人员简化dccorators的使用,并且通过一些有用的例子来推广decorators的使用,例如:memoize,tracing,redirecting_stdout, locked等等。

模块的核心是一个叫做decorator的decorator工厂函数。所有在这里讨论的简单的decorators解决方案都是基于decorator模块的。通过执行如下命令后产生的 _main.py 文件中包含了这些所有的这些代码:
> python doctester.py documentation.txt
同时,该命令还会运行所有的作为测试用例存在的例子。

定义

从技术上来讲, 任何可以被调用的、带一个参数的Python对象都可以被当作是一个decorator。 然而,这个定义因为过于宽泛而显得并无真正得用处。为方便起见,我们可以把decorator分为两类:
  • 保留签名的(signature-preserving)decorators,例如:将一个函数A作为可调用对象(decorators)的输入并返回一个函数B,函数A和B的签名是相同的;
  • 改变签名的(signature-changing)decorators, 例如:将一个函数A作为可调用对象(decorators)的输入并返回一个函数B,函数A和B的签名是不相同的,或者该decorator的返回值就不是一个可调用对象。

Signature-changing decorators有它们的用处,例如:内部类 staticmethod classmethod 就属于这一类,因为它们接受函数作为输入并返回一个descriptor对象,这个对象不是函数,也不是可调用对象。

但是,signature-preserving decorators 则更加通用,更加容易理解,尤其是这一类decorators能够被组合起来使用,而其他decorators通常是不能够组合使用的(如staticmethodclassmethod)。

从零开始写一个signature-preserving decorator 不是那么浅显的,尤其是当你想实现一个正确的能够接受任意签名的decorator时,一个简单的例子将阐明这个问题。

问题的描述

假设你想跟踪一个函数的执行:这是一个常见的使用decorator的例子,很多地方你都能看到类似的代码

python 代码
  1. try:  
  2.     from functools import update_wrapper  
  3. except ImportError# using Python version < 2.5  
  4.     def decorator_trace(f):  
  5.         def newf(*args, **kw):  
  6.            print "calling %s with args %s, %s" % (f.__name__, args, kw)  
  7.            return f(*args, **kw)  
  8.         newf.__name__ = f.__name__  
  9.         newf.__dict__.update(f.__dict__)  
  10.         newf.__doc__ = f.__doc__  
  11.         newf.__module__ = f.__module__  
  12.         return newf  
  13. else# using Python 2.5+  
  14.     def decorator_trace(f):  
  15.         def newf(*args, **kw):  
  16.             print "calling %s with args %s, %s" % (f.__name__, args, kw)  
  17.             return f(*args, **kw)  
  18.         return update_wrapper(newf, f)  

(译者注:上面的代码中虽然有修改结果对象的自省信息(或元信息),但是修改得不全面,如其签名信息就没有修改为与被修饰对象保持一致)
上面代码是想实现一个能够接受一般签名的decorator,不幸的是,该实现并没有定义为一个signature-preserving的decorator,因为大体上说 decorator_trace 返回了一个与被修饰函数不同的签名。
思考下面的例子:

>>> @decorator_trace
... def f1(x):
... pass

这里原先的函数只接受一个参数,而修饰后的函数却接受任意的参数和关键字参数。
>>> from inspect import getargspec
>>> print getargspec(f1)
([], 'args', 'kw', None)

这就意味这自省工具如:pydoc将会给出关于函数f1的错误的签名信息,这是一个美丽的错误:pydoc将告诉你该函数能够接受一个通用的签名:*args, **kw,但是当你使用超过一个的参数去调用该函数时,你将会得到如下错误:
>>> f1(0, 1)
Traceback (most recent call last):
...
TypeError: f1() takes exactly 1 argument (2 given)


解决方案

这个方案提供了一个通用的 decorators  工厂,它对应用程序员隐藏了实现 signature-preserving  的 decorator 的复杂性。该工厂允许在不使用嵌套函数或嵌套类的情况下定义decorators, 下面是一个告诉你怎样定义 decorator_trace 的简单例子。
首先,导入decorator模块:
>>> from decorator import decorator
然后定义一个辅助函数f,该函数具有如下signature:(f, *args, **kw),该函数只是简单的做如下调用f(args, kw):
python 代码
 
  1. def trace(f, *args, **kw):  
  2.     print "calling %s with args %s, %s" % (f.func_name, args, kw)  
  3.     return f(*args, **kw)  

decorator 模块能够把帮助函数转变成一个 signature-preserving 对象,例如:一个接受一个函数作为输入的可调用对象,并返回一个被修饰过的函数,该函数具有与原函数一致的签名,这样,你就能够这样写:
>>> @decorator(trace)
... def f1(x):
... pass
很容易就验证函数 f1 正常工作了
>>> f1(0)
calling f1 with args (0,), {}
并且它具有正确的签名:
>>> print getargspec(f1)
(['x'], None, None, None)
同样的,该decorator对象对具有其他签名的函数也是有效的:
>>> @decorator(trace)
... def f(x, y=1, z=2, *args, **kw):
... pass
>>> f(0, 3)
calling f with args (0, 3, 2), {}
>>> print getargspec(f)

甚至包括下面这些具有奇特签名的函数也能够正常工作:
>>> @decorator(trace)
... def exotic_signature((x, y)=(1,2)): return x+y

>>> print getargspec(exotic_signature)
([['x', 'y']], None, None, ((1, 2),))
>>> exotic_signature()
calling exotic_signature with args ((1, 2),), {}
3

(['x', 'y', 'z'], 'args', 'kw', (1, 2))

decorator is a Decorator


工厂函数decorator本身就可以被当作是一个 signature-changing 的decorator, 就和 classmethod 和 staticmethod 一样,不同的是这两个内部类返回的是一般的不可调用的对象,而decorator返回的是 signature-preserving 的decorators, 例如带单参数的函数。这样,你能够这样写:
>>> @decorator
... def tracing(f, *args, **kw):
... print "calling %s with args %s, %s" % (f.func_name, args, kw)
... return f(*args, **kw)
 这中惯用法实际上是把 tracing 重定义为一个 decorator , 我们能够很容易的检测到tracing的签名被更改了:
>>> print getargspec(tracing)
(['f'], None, None, None)
这样,tracing能够被当作一个decorator使用,下面的代码能够工作:
>>> @tracing
... def func(): pass
>>> func()
calling func with args (), {}
BTW,你还能够对 lambda 函数应用该 decorator:
>>> tracing(lambda : None)()
calling <lambda> with args (), {}</lambda>
下面开始讨论decorators的用法。

缓存化(memoize)


这里讨论的decorator实现了memoize模式,它可以把函数调用结果存储在一个字典对象中,下次使用相同参数调用该函数时,就可以直接从该字典对象里面获取结果而无需重新计算。
memoize 代码
 
  1. from decorator import *  
  2.   
  3. def getattr_(obj, name, default_thunk):  
  4.     "Similar to .setdefault in dictionaries."  
  5.     try:  
  6.         return getattr(obj, name)  
  7.     except AttributeError:  
  8.         default = default_thunk()  
  9.         setattr(obj, name, default)  
  10.         return default  
  11.   
  12. @decorator  
  13. def memoize(func, *args):  
  14.     dic = getattr_(func, "memoize_dic", dict)  
  15.     # memoize_dic is created at the first call  
  16.     if args in dic:  
  17.         return dic[args]  
  18.     else:  
  19.         result = func(*args)  
  20.         dic[args] = result  
  21.         return result  
下面时使用测试:
>>> @memoize
... def heavy_computation():
... time.sleep(2)
... return "done"
>>> print heavy_computation() # the first time it will take 2 seconds
done
>>> print heavy_computation() # the second time it will be instantaneous
done
作为练习, 您可以尝试不借助于decorator工厂来正确的实现memoize。
注意:这个memoize实现只有当函数没有关键字参数时才能够正常工作,因为实际上不可能正确的缓存具有可变参数的函数。您可以放弃这个需求,允许有关键字参数存在,但是,当有关键字参数被传入时,其结果是不能够被缓存的。具体例子请参照http://www.python.org/moin/PythonDecoratorLibrary

锁(locked)


不想再翻这一小节了!这一小节本来已经翻译了,但是由于系统故障导致被丢失了,而且因为Python2.5中已经实现了with语句,因此这一小节的内容也就显得不是那么重要了。
代码附上:
locked的实现
 
  1. import threading  
  2.   
  3. @decorator  
  4. def locked(func, *args, **kw):  
  5.     lock = getattr_(func, "lock", threading.Lock)  
  6.     lock.acquire()  
  7.     try:  
  8.         result = func(*args, **kw)  
  9.     finally:  
  10.         lock.release()  
  11.     return result  

锁的使用
 
  1. import time  
  2.   
  3. datalist = [] # for simplicity the written data are stored into a list.  
  4.   
  5. @locked  
  6. def write(data):  
  7.     "Writing to a sigle-access resource"  
  8.     time.sleep(1)  
  9.     datalist.append(data)  

延时化和线程化(delayed and threaded)

Thursday, April 30, 2009

Linux|screen使用方法命令和解释

[求助]真心求screen使用方法! [论坛存档] - LinuxSir.Org

以下是命令和解释。
运行:
screen

Ctrl-a S 新建水平分割窗口
Ctrl-a Tab 切换窗口
Ctrl-a :screen bash 新建 screen 终端,并运行 bash
Ctrl-a :quit 退出 screen,将关闭所有 screen 终端,结束其中所有任务



screen 常用命令,

Ctrl-a c 新建 bash screen 终端
Ctrl-a " 列出
Ctrl-a A 重命名
Ctrl-a n 在当前窗口中切换到下一个 screen 终端
Ctrl-a p 在当前窗口中切换到上一个 screen 终端

Ctrl-a d 断开所有 screen 终端,返回 screen 执行前状态,但 screen 内所有终端的任务都在执行
screen -ls 列出当前用户的所有 screen 实例,包括联接和断开的
screen -R <pid> 重新联接到已断开的 screen 实例,如果有多个已断开的 screen 实例,则用 <pid> 区分

Ctrl-a S 新建水平分割窗口
Ctrl-a Tab 切换窗口
Ctrl-a X 关闭当前窗口
Ctrl-a + 扩大当前窗口,默认增加3行
Ctrl-a - 缩小当前窗口,默认减小3行

Ctrl-a :screen <command> 新建 screen 终端,并运行命令<command>
Ctrl-a :resize <height> 改变当前窗口高度为<height>
Ctrl-a :quit 退出 screen,将关闭所有 screen 终端,结束其中所有任务

Ctrl-a <Esc> 进入选择模式
<PageUp> 或 Ctrl-u 光标上移一页
<PageDown> 或 Ctrl-d 光标下移一页
<Left> 或 h 光标左移一格
<Down> 或 j 光标下移一行
<Up> 或 k 光标上移一行
<Right> 或 l 光标右移一格
<Space> 选择开始,选择结束
<Esc> 退出选择模式

Ctrl-a ] 粘贴选择的内容

Linux 技巧:使用 screen 管理你的远程会话

linux 技巧:使用 screen 管理你的远程会话

linux 技巧:使用 screen 管理你的远程会话

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

将打印机的版面设置成横向打印模式

打印本页

将此页作为电子邮件发送

将此页作为电子邮件发送


级别: 中级

田 强 (tianq@cn.ibm.com), 软件工程师, IBM中国软件开发中心

2007 年 7 月 31 日

你是不是经常需要远程登录到Linux服务器?你是不是经常为一些长时间运行的任务头疼?还在用 nohup 吗?那么来看看 screen 吧,它会给你一个惊喜!

你是不是经常需要 SSH 或者 telent 远程登录到 Linux 服务器?你是不是经常为一些长时间运行的任务而头疼,比如系统备份、ftp 传输等等。通常情况下我们都是为每一个这样的任务开一个远程终端窗口,因为他们执行的时间太长了。必须等待它执行完毕,在此期间可不能关掉窗口或者断开连接,否则这个任务就会被杀掉,一切半途而废了。

元凶:SIGHUP 信号

让我们来看看为什么关掉窗口/断开连接会使得正在运行的程序死掉。

在Linux/Unix中,有这样几个概念:

  • 进程组(process group):一个或多个进程的集合,每一个进程组有唯一一个进程组ID,即进程组长进程的ID。
  • 会话期(session):一个或多个进程组的集合,有唯一一个会话期首进程(session leader)。会话期ID为首进程的ID。
  • 会话期可以有一个单独的控制终端(controlling terminal)。与控制终端连接的会话期首进程叫做控制进程(controlling process)。当前与终端交互的进程称为前台进程组。其余进程组称为后台进程组。

根据POSIX.1定义:

  • 挂断信号(SIGHUP)默认的动作是终止程序。
  • 当终端接口检测到网络连接断开,将挂断信号发送给控制进程(会话期首进程)。
  • 如果会话期首进程终止,则该信号发送到该会话期前台进程组。
  • 一个进程退出导致一个孤儿进程组中产生时,如果任意一个孤儿进程组进程处于STOP状态,发送SIGHUP和SIGCONT信号到该进程组中所有进程。

因此当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会导致该会话期内其他进程退出。

我们来看一个例子。打开两个SSH终端窗口,在其中一个运行top命令。

 [root@tivf09 root]# top

在另一个终端窗口,找到top的进程ID为5180,其父进程ID为5128,即登录shell。

 [root@tivf09 root]# ps -ef|grep top root      5180  5128  0 01:03 pts/0    00:00:02 top root      5857  3672  0 01:12 pts/2    00:00:00 grep top

使用pstree命令可以更清楚地看到这个关系:

 [root@tivf09 root]# pstree -H 5180|grep top |-sshd-+-sshd---bash---top             

使用ps-xj命令可以看到,登录shell(PID 5128)和top在同一个会话期,shell为会话期首进程,所在进程组PGID为5128,top所在进程组PGID为5180,为前台进程组。

 [root@tivf09 root]# ps -xj|grep 5128  5126  5128  5128  5128 pts/0     5180 S        0   0:00 -bash  5128  5180  5180  5128 pts/0     5180 S        0   0:50 top  3672 18095 18094  3672 pts/2    18094 S        0   0:00 grep 5128

关闭第一个SSH窗口,在另一个窗口中可以看到top也被杀掉了。

 [root@tivf09 root]# ps -ef|grep 5128 root     18699  3672  0 04:35 pts/2    00:00:00 grep 5128

如果我们可以忽略SIGHUP信号,关掉窗口应该就不会影响程序的运行了。nohup命令可以达到这个目的,如果程序的标准输出/标准错误是终端,nohup默认将其重定向到nohup.out文件。值得注意的是nohup命令只是使得程序忽略SIGHUP信号,还需要使用标记&把它放在后台运行。

 nohup <command> [argument…] &

虽然nohup很容易使用,但还是比较“简陋”的,对于简单的命令能够应付过来,对于复杂的需要人机交互的任务就麻烦了。

其实我们可以使用一个更为强大的实用程序screen。流行的Linux发行版(例如Red Hat Enterprise Linux 4)通常会自带screen实用程序,如果没有的话,可以从GNU screen的官方网站下载。

 [root@tivf06 ~]# rpm -qa|grep screen xscreensaver-4.18-5.rhel4.11 screen-4.0.2-5             

开始使用Screen

简单来说,Screen是一个可以在多个进程之间多路复用一个物理终端的窗口管理器。Screen中有会话的概念,用户可以在一个screen会话中创建多个screen窗口,在每一个screen窗口中就像操作一个真实的telnet/SSH连接窗口那样。在screen中创建一个新的窗口有这样几种方式:

1.直接在命令行键入screen命令

 [root@tivf06 ~]# screen

Screen将创建一个执行shell的全屏窗口。你可以执行任意shell程序,就像在ssh窗口中那样。在该窗口中键入exit退出该窗口,如果这是该screen会话的唯一窗口,该screen会话退出,否则screen自动切换到前一个窗口。

2.Screen命令后跟你要执行的程序。

 [root@tivf06 ~]# screen vi test.c

Screen创建一个执行vi test.c的单窗口会话,退出vi将退出该窗口/会话。

3.以上两种方式都创建新的screen会话。我们还可以在一个已有screen会话中创建新的窗口。在当前screen窗口中键入C-a c,即Ctrl键+a键,之后再按下c键,screen 在该会话内生成一个新的窗口并切换到该窗口。

screen还有更高级的功能。你可以不中断screen窗口中程序的运行而暂时断开(detach)screen会话,并在随后时间重新连接(attach)该会话,重新控制各窗口中运行的程序。例如,我们打开一个screen窗口编辑/tmp/abc文件:

 [root@tivf06 ~]# screen vi /tmp/abc

之后我们想暂时退出做点别的事情,比如出去散散步,那么在screen窗口键入C-a d,Screen会给出detached提示:


暂时中断会话
暂时中断会话

半个小时之后回来了,找到该screen会话:

 [root@tivf06 ~]# screen -ls There is a screen on:         16582.pts-1.tivf06      (Detached) 1 Socket in /tmp/screens/S-root.

重新连接会话:

 [root@tivf06 ~]# screen -r 16582

看看出现什么了,太棒了,一切都在。继续干吧。

你可能注意到给screen发送命令使用了特殊的键组合C-a。这是因为我们在键盘上键入的信息是直接发送给当前screen窗口,必须用其他方式向screen窗口管理器发出命令,默认情况下,screen接收以C-a开始的命令。这种命令形式在screen中叫做键绑定(key binding),C-a叫做命令字符(command character)。

可以通过C-a ?来查看所有的键绑定,常用的键绑定有:


C-a ?显示所有键绑定信息
C-a w显示所有窗口列表
C-a C-a切换到之前显示的窗口
C-a c创建一个新的运行shell的窗口并切换到该窗口
C-a n切换到下一个窗口
C-a p切换到前一个窗口(与C-a n相对)
C-a 0..9切换到窗口0..9
C-a a发送 C-a到当前窗口
C-a d暂时断开screen会话
C-a k杀掉当前窗口
C-a [进入拷贝/回滚模式

Screen常用选项

使用键绑定C-a ?命令可以看到, 默认的命令字符(Command key)为C-a,转义C-a(literal ^a)的字符为a:


Screen 常用选项
Screen 常用选项

因为screen把C-a看作是screen命令的开始,所以如果你想要screen窗口接收到C-a字符,就要输入C-a a。Screen也允许你使用-e选项设置自己的命令字符和转义字符,其格式为:

-exy x为命令字符,y为转义命令字符的字符

下面命令启动的screen会话指定了命令字符为C-t,转义C-t的字符为t,通过C-t ?命令可以看到该变化。

 [root@tivf18 root]# screen -e^tt


自定义命令字符和转义字符
自定义命令字符和转义字符

其他常用的命令选项有:


-c file使用配置文件file,而不使用默认的$HOME/.screenrc
-d|-D [pid.tty.host]不开启新的screen会话,而是断开其他正在运行的screen会话
-h num指定历史回滚缓冲区大小为num行
-list|-ls列出现有screen会话,格式为pid.tty.host
-d -m启动一个开始就处于断开模式的会话
-r sessionowner/ [pid.tty.host]重新连接一个断开的会话。多用户模式下连接到其他用户screen会话需要指定sessionowner,需要setuid-root权限
-S sessionname创建screen会话时为会话指定一个名字
-v显示screen版本信息
-wipe [match]同-list,但删掉那些无法连接的会话

下例显示当前有两个处于detached状态的screen会话,你可以使用screen -r <screen_pid>重新连接上:

 [root@tivf18 root]# screen –ls There are screens on:         8736.pts-1.tivf18       (Detached)         8462.pts-0.tivf18       (Detached) 2 Sockets in /root/.screen.  [root@tivf18 root]# screen –r 8736

如果由于某种原因其中一个会话死掉了(例如人为杀掉该会话),这时screen -list会显示该会话为dead状态。使用screen -wipe命令清除该会话:

 [root@tivf18 root]# kill -9 8462 [root@tivf18 root]# screen -ls   There are screens on:         8736.pts-1.tivf18       (Detached)         8462.pts-0.tivf18       (Dead ???) Remove dead screens with 'screen -wipe'. 2 Sockets in /root/.screen.  [root@tivf18 root]# screen -wipe There are screens on:         8736.pts-1.tivf18       (Detached)         8462.pts-0.tivf18       (Removed) 1 socket wiped out. 1 Socket in /root/.screen.  [root@tivf18 root]# screen -ls   There is a screen on:         8736.pts-1.tivf18       (Detached) 1 Socket in /root/.screen.  [root@tivf18 root]#

-d –m 选项是一对很有意思的搭档。他们启动一个开始就处于断开模式的会话。你可以在随后需要的时候连接上该会话。有时候这是一个很有用的功能,比如我们可以使用它调试后台程序。该选项一个更常用的搭配是:-dmS sessionname

启动一个初始状态断开的screen会话:

 [root@tivf06 tianq]# screen -dmS mygdb gdb execlp_test

连接该会话:

 [root@tivf06 tianq]# screen -r mygdb

管理你的远程会话

先来看看如何使用screen解决SIGHUP问题,比如现在我们要ftp传输一个大文件。如果按老的办法,SSH登录到系统,直接ftp命令开始传输,之后。。如果网络速度还可以,恭喜你,不用等太长时间了;如果网络不好,老老实实等着吧,只能传输完毕再断开SSH连接了。让我们使用screen来试试。

SSH登录到系统,在命令行键入screen。

 [root@tivf18 root]# screen

在screen shell窗口中输入ftp命令,登录,开始传输。不愿意等了?OK,在窗口中键入C-a d:


管理你的远程会话
管理你的远程会话

然后。。退出SSH登录?随你怎样,只要别杀掉screen会话。

是不是很方便?更进一步,其实我们可以利用screen这种功能来管理你的远程会话,保存你所有的工作内容。你是不是每次登录到系统都要开很多窗口,然后每天都要重复打开关闭这些窗口?让screen来帮你“保存”吧,你只需要打开一个ssh窗口,创建需要的screen窗口,退出的时候C-a d“保存”你的工作,下次登录后直接screen -r <screen_pid>就可以了。

最好能给每个窗口起一个名字,这样好记些。使用C-a A给窗口起名字。使用C-a w可以看到这些窗口名字,可能名字出现的位置不同。使用putty:


putty
putty

使用telnet:


telnet
telnet

更多Screen功能

Screen提供了丰富强大的定制功能。你可以在Screen的默认两级配置文件/etc/screenrc和$HOME/.screenrc中指定更多,例如设定screen选项,定制绑定键,设定screen会话自启动窗口,启用多用户模式,定制用户访问权限控制等等。如果你愿意的话,也可以自己指定screen配置文件。

以多用户功能为例,screen默认是以单用户模式运行的,你需要在配置文件中指定multiuser on 来打开多用户模式,通过acl*(acladd,acldel,aclchg...)命令,你可以灵活配置其他用户访问你的screen会话。更多配置文件内容请参考screen的man页。

参考资料

Tuesday, April 28, 2009

Django|reverse函数

python BlogSpot: django reverse函数

Wednesday

django reverse函数

在0.96版中新加入了django.core.urlresolvers.reverse
reverse()是干什么的?反解析url以直接访问其它视图方法

在django的mvc(mvt也可以)中,url和view是通过url.py和views.py实现分离的,具体view的访问是通过url.py中设置的对应方法来实现的。

有一个view最后实现页面跳转:

views.py
def redirect(request):
return HttpResponseRedirect("/vote/1/")

url.py
(r'^/vote/(?P\d+)/$','mysite.poll.vote')

这样做起来就有1个问题:如果未来某天我们的url发生了改变,我们将不得不同时修改这两个文件。这和DRY哲学思想是冲突的,
---------
那么有什么办法能让我们只用修改url.py呢,答案就是使用reverse().
reverse的用例如下:

views.py

from django.core.urlresolvers import reverse

def redirect(request):
return HttpResponseRedirect(reverse('mysite.polls.views.detail',args=(1,)))

很容易明白,第一个参数就直接添入要使用的view方法,第二个args里边顺序填入方法的参数,(extra_context也从这里传入)然后剩下的就全部交给django去完成拉。
...

Sunday, April 26, 2009

Django|Debugging Django

Debugging Django

Debugging Django

I gave a talk on Debugging Django applications at Monday's inaugural meeting of DJUGL, the London Django Users Group. I wanted to talk about something that wasn't particularly well documented elsewhere, so I pitched the talk as "Bug Driven Development"—what happens when Test Driven Development goes the way of this unfortunate pony.

The slides are up on SlideShare, but don't provide quite enough context so I'm going to cover the tips in full here.

Making the most of the error page

Django's default error page is great—it provides a detailed traceback with local variables, lets you expand out the lines of code around the problem, provides a plain text exception suitable for e-mailing to colleagues and even a one-click button to send details to http://dpaste.com/ so you can go and talk about the error on IRC. It also serves the same purpose as phpinfo()—it shows you your application's settings, the GET, POST and COOKIE data from the request and the all important META fields assembled from the HTTP environment (great for remembering how to miss-spell HTTP_REFERER).

Useful tip number one is that you can trigger the error page from any view just by adding the following line:

assert False

You can serve up an expression with the assertion as well; it will be displayed at the top of the error page:

assert False, request.GET

One particularly useful place to use this is when you are building a complex form. If you want to see the data that was submitted, drop an assert False in to the view that the form targets and use the error page to inspect the data.

Logging to the development server console

If you want to know what's going on inside a view, the quickest way is to drop in a print statement. The development server outputs any print statements directly to the terminal; it's the server-side alternative to a JavaScript alert().

If you want to be a bit more sophisticated with your logging, it's worth turning to Python's logging module (part of the standard library). You can configure it in your settings.py:

import logging logging.basicConfig(     level = logging.DEBUG,     format = '%(asctime)s %(levelname)s %(message)s', )

Then call it from any of your views:

def my_view(request):     import logging     logging.debug("A log message")     ...

Again, this will log things to the terminal where the development server is running. If you want to log things to a file you can do so by extending the basicConfig call:

logging.basicConfig(     level = logging.DEBUG,     format = '%(asctime)s %(levelname)s %(message)s',     filename = '/tmp/myapp.log',     filemode = 'w' )

You can then use tail -f /tmp/myapp.log to see log lines being appended to that file in real-time. This can be used in production as well as development.

The above just scratches the surface of Python's logging module; with a bit of digging around in the documentation you can use it to rotate log files, send log messages over the network and even POST log events to an HTTP server somewhere.

Often you find yourself dealing with an error that only occurs in certain circumstances—a function might be called from dozens of different places in your program but only runs in to trouble in a very specific case. You can use the traceback module to log the current stack, which will allow you to tell how a function was called when something went wrong:

import logging, traceback, pprint  def my_buggy_function(arg):     ...     if error_condition:         stack = pprint.pformat(traceback.extract_stack())         logging.debug('An error occurred: %s' % stack)

The tuple returned by traceback.extract_stack() includes line numbers, function names and paths to Python files so you can use it to reconstruct a good amount of information about your program.

Using the debugger

By far the most powerful weapon in my debugging toolkit is the Python debugger, pdb. Again, this ships with the standard library so there's nothing extra to install. pdb is a command line debugger (if you want a GUI options include PyEclipse and Komodo, but I haven't used either myself). There are a bunch of ways to activate pdb, but the most straight forward is to simply drop the following line directly in to a Django view function:

import pdb; pdb.set_trace()

If you try to load that page in your browser, the browser will hang—the page will appear to be loading extremely slowly. What's actually happened is the developer server has paused execution and thrown up the pdb interface—you can switch over to your console and start interacting directly with the server mid view.

Did I mention you should never, ever leave this on in production?

So, you've got a hung development server and a pdb prompt. What can you do with it? The answer is pretty much anything. I won't provide a full pdb tutorial here (this is a good introduction), but the commands I find most useful are the following:

list
Shows the lines of source code around your current point of execution. You can run it multiple times to increase the amount of source code displayed.
n
Execute the next line
s
Same as n, but steps in to any functions that are called. You can quickly get lost in a twisty maze of code with this command, but that's OK because...
r
Continues execution until the current function returns
u
Goes UP one level in the stack—so you can see the function that called the function you are currently in
d
Goes DOWN again
locals()
not a pdb command, but handy for seeing what's in your current scope

The pdb docs have a full list of commands.

The pdb prompt doubles up as a full Python interactive shell, so you can not only access variables but you can modify them, call functions and generally mess around with the internals of your application as much as you like, while it's running. It's kind of a poor man's imitation of being a Smalltalk developer.

Remember though, the whole time you're messing around in pdb your browser is still stuck there, waiting for the HTTP request to come back. If you hit "c" (for continue) your application will kick in again, the request will be served and your browser will breathe a sigh of relief.

Thankfully you don't have to use pdb in a way that freezes your development server; it also works great in the interactive shell. If you've got a buggy function, one way to explore it is to run it interactively, then use the following idiom:

>>> def function_that_raises_an_exception(): ...   assert False ...  >>> function_that_raises_an_exception() Traceback (most recent call last):   File "<stdin>", line 1, in <module>   File "<stdin>", line 2, in function_that_raises_an_exception AssertionError >>> import pdb; pdb.pm() > <stdin>(2)function_that_raises_an_exception() (Pdb)

pdb.pm() stands for post-mortem, and is probably my favourite feature of the debugger—it lets you jump back in to debug the most recently raised exception, even if you hadn't imported pdb at the time the exception was raised.

One last pdb tip: you can use it to debug Python command line scripts such as Django's custom ./manage.py commands. The trick is to run the script like this:

python -i manage.py buggy_command

The -i argument causes Python to drop in to the interactive prompt after executing the script. If the script raised an exception, you can then use pdb.pm() to debug it.

Handling errors in production

Django's default behaviour in production (that is, when the DEBUG setting is set to False) is to e-mail exception reports to anyone listed in the ADMINS section. You can also turn on e-mail reports on every 404 error with the SEND_BROKEN_LINK_EMAILS setting, which will send them to addresses in the MANAGERS setting. As far as I know these settings don't do anything else—they're a pretty ancient bit of Django.

On a high traffic site you probably don't want to be e-mailed on every server error. One neat alternative is David Cramer's django-db-log, which logs exceptions to a database table. It cleverly uses an MD5 hash of the traceback to aggregate many reports of the same error. More importantly though, it acts as a really straight forward example of how to use Django middleware's process_exception hook to roll your own error reporting. Take a look at the code to see how simple this is.

More useful middleware

In the talk I demoed a couple of other handy pieces of middleware. The first was the ProfilerMiddleware (one of several profiling tools on Django Snippets) which allows you to add ?prof to the end of any URL to see the output of Python's cProfile module run against that request. The second is one that I've just released: DebugFooter, which adds a footer showing exactly which templates were loaded from where (handy for debugging complex template paths) as well as every executed SQL query and how long each one took.

Abusing the test client

A final tip for exploring your application interactively is to learn to use Django's TestClient. Although designed for use in unit tests, this tool is equally useful for use at the interactive prompt. It allows you to simulate an in-process request against your application from within your Python code. Here's an example:

>>> from django.test.client import Client >>> c = Client() >>> response = c.get("/") # The homepage >>> response <django.http.HttpResponse object at 0x2300470> >>> print response Vary: Cookie Content-Type: text/html; charset=utf-8  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"     "http://www.w3.org/TR/html4/strict.dtd"> <html> ...

The response object you get back is the HttpResponse returned by the view, ready to be explored interactively.

There's another function from the unit testing tools that can help with interactively exploring an application: setup_test_environment(). This function monkey-patches in some additional hooks used by the unit tests, including one that intercepts template render calls and adds information on them to the request object. Here's an example:

>>> from django.test.utils import setup_test_environment >>> setup_test_environment() >>> from django.test.client import Client >>> c = Client() >>> response = c.get("/") >>> response.template [<django.template.Template object at 0x2723dd0>,  <django.template.Template object at 0x2723f30>,  <django.template.Template object at 0x273ee10>] >>> response.context [ list of Context objects ]

This allows you to explore not just the HTML returned by a view, but also the templates and contexts that were used to render it.

Your tips welcome

If you have any useful tips on debugging Django applications, please share them in the comments on this entry.