Python interview - Decorator

对于decorator修饰器,拜读完几篇英文 中文的博客,有一定的理解,本文是一个理解的过程和总结,会有翻译段落和例子来自参考的博客。


http://www.pythoncentral.io/python-decorators-overview/

http://www.cnblogs.com/SeasonLee/archive/2010/04/24/1719444.html


有兴趣的可以去看看原文。


从一个美国的python讲师的理解性讲解开始。

1. 首先理解python中的functions

看过python code的基本都应该知道,python的functions在创建的时候都是使用def关键词,然后跟随function name,optional有一个或者几个的参数。能够用return返回值。

def test():
    return 1

print test()
# <span style="font-family: Arial, Helvetica, sans-serif;">1</span>

python的结构是很严谨的,在缩进的地方必须有缩进,比如loop。我们可以直接调用方法,方法名+()。


2. 我们需要理解python中的scope

a_string = 'This is a global variable'

def foo():
    a = 0
    print locals()

print globals()

foo()

然后我们来看结果:


{'foo': <function foo at 0x0000000001FA7358>, '__builtins__': <module '__builtin__' (built-in)>, '__file__': 'C:/Users/Jiateng/PycharmProjects/practice/2014MS.py', 'a_string': 'This is a global variable', '__name__': '__main__', '__package__': None, '__doc__': None}
{'a': 0}

很明显的,在foo方法中的a是一个local的参数,而a_string是一个global的参数,其他的一些python定义好的方法和值的pair。

在python中,可以说functions有自己的namespace。global()方法返回一个python现在状态下所存在的所有的global的variable的一个字典。locals()方法返回当前方法中的local variable,比如我在foo方法中定义了a这个变量。

P.S. 当然,global在python中也很特殊,当你要对global的参数进行操作更改的时候,你需要使用Global关键词才可以,如果不更新,那么没有关键词也是可以的。


3. variable的生命周期

一个简单的例子

def foo():
    x = 1

foo()
print x

这里的x在foo方法中定义,但是调用了foo方法并不意味着在foo调用结束后,x还依旧被定义完全并且存在。x=1在foo方法中,随着foo被调用而被创建,当foo方法结束的时候,相关的都被释放或者说是不存在。


4. 方法的变量和参数

python中我们可以往方法中传入变量,然后传入的就变成了这个方法的local variables。

def foo(x):
    print locals()

foo(1)

# result

{'x' : 1}

python传递参数的方法有很多种,会在另一文章中说明,简单来说可以归类以下四种:

Fun1(a,b,c), pass the parameters
Fun2(a=1,b=2,c=3), set the default value to the parameters 
Fun3(*args), pass any number of parameters you want, but in order like tuple
Fun4(**kargs), pass any number of parameters you want, like a dictionary with key-value pair. 

对于参数的default value,可以说是一种python的overload的方式,简单的例子:


def foo(x, y=0):
    print x - y

foo(3, 1)
foo(3)
foo(y=1, x=3)
foo()

# result
2
3
2
TypeError: foo() takes at least 1 argument(0 given)

我们可以看到几种调用的方式,因为y有默认值,所以可以调用的时候不给y赋值。同时,可以不按照顺序。但是对于没有默认值的变量,必须传入参数才能正常调用方法。


5. 嵌套方法

python中也存在方法的嵌套。意味着我们可以在方法中声明方法,如此一来所有的scope和生命周期都依旧是一样的规则。

def outer():
    x = 1
    def inner():
        print x
    inner()


outer()

#result

1


在inner方法中,想要输出一个local的x,但是在inner中找不到,然后就会找嵌套起来的方法的外面一层。x是outer方法的一个local variable,调用inner方法的时候会访问到outer中的x。

需要注意的是,在outer方法中,inner也就是一个遵循python variable lookup rules的参数名。所以python会先在outer方法中查看,然后找到local variable - inner。然后再是调用inner,找不到x,最后在outer中找到x。


6. 方法也是对象

在python中,方法也是对象,也有属性,就和其他所有的一样。

print issubclass(int, object)

def foo():
    pass

print foo.__class__

print issubclass(foo.__class__, object)

第一行我们能知道,所有的objects都继承了一个基本的baseclass - object。

同样的,foo方法是function类别的,但是是否是一个继承了object的对象呢,是的!


对于function来说,function可以传入到function中,作为一个参数,function也可以返回一个function,当作一个返回值。

def add(x, y):
    return x + y

def sub(x, y):
    return x - y

def apply(func, x, y):
    return func(x, y)

print apply(add, 2, 1)
# 3
print apply(sub, 2, 1)
# 1

add 和sub是两个普通的python function,计算得到的两个参数,并返回。apply中,func作为一个参数被传入,并且返回了这个func并调用。
def outer():
    def inner():
        print 'inside inner.'
    return inner

foo = outer()
print foo
print foo()

# result

<function inner at 0x0000000001FB73C8>
inside inner.
根据上面的例子,我们可以看到function也可以直接被当做对象,只传递一个方法名。然后我们可以用()括号来调用这个方法。


7. closure 闭包问题

微微改一下上一个内容的代码

def outer():
    x=1
    def inner():
        print x
    return inner

foo = outer()
print foo.func_closure

# result

(<cell at 0x0000000001E46C78: int object at 0x0000000001E26478>,)

根据python's scoping rules,我们知道x是方法outer的local variable,当inner方法要使用x的时候,会先在inner方法中寻找本地的x,没找到,会查找外围方法的范围,并在outer方法中得到x。

但是,根据之前说的,只有当outer方法在运行的时候,x才是有效的,否则就是未定义的x。我们不能在outer运行完毕,调用inner的时候访问这个在outer中定义的x。可能会造成runtime error或者其他问题。

事实并不是如此,python中有function closures方法闭包。就是说,inner的方法有一个定义了的非全局scope,记录了在他们所被包括的nested的外围方法的namespace。可以用func_closure属性来查看。比如例子中我们知道有一个int对象,就是我们需要的x。

def outer(x):
    def inner():
        print x
    return inner

print outer(1)()
print outer(2)()

# result

1
2

outer(x)得到一个inner是一个方法的类型,用()去调用。对于闭包,我们能说inner方法记录了他们enclosing scope的能够被用到的对象。

就像面向对象,outer是inner的构造函数,带有x就像是私有的变量。同时可以参考sorted方法的使用。


8. Decorator 修饰器

修饰器就是一个可以调用的,把一个方法作为参数,然后返回一个替代的方法。我们看一个简单的例子。

def outer(some_func):
    def inner():
        print "before some_func"
        ret = some_func()
        return ret + 1
    return inner

def foo():
    return 1

decorated = outer(foo)

print decorated
print decorated()

# result

<function inner at 0x0000000001F97438>
before some_func
2
我们定义一个outer方法,传入一个参数some_func,内部方法inner,会打印一个string,然后调用some_func方法,并获得返回值,最后inner返回之前的返回值+1.

在执行的时候,我们给decorated赋予foo方法,但是有outer方法进行了修饰,当我们调用decorated的时候,会得到2而不是foo应该返回的1.

我们可以说decorated就是被修饰过的foo方法,就是foo加上了一些其他的功能。实际上,如果我们可以直接给foo方法重新赋值

foo = outer(foo)
然后在直接调用foo(),不会得到原始的foo方法给出的返回值,而是得到新的修饰过的版本的返回值。


下面这个例子,假设我们有一个定义并返回坐标的class。但是对于这个class并不能够对坐标进行加减,我们需要自己写add 和 sub的方法。

class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Coord:' + str(self.__dict__)


def add(a, b):
    return Coordinate(a.x + b.x, a.y + b.y)

def sub(a, b):
    return Coordinate(a.x - b.x, a.y - b.y)

one = Coordinate(100, 200)
two = Coordinate(300, 200)

print add(one, two)

# result

Coord:{'y': 400, 'x': 400}

假设一下,我们需要仅仅对正数进行操作,并且得到的返回值也要是正数,那该如何写这个方法。现在的方法调用如下:

one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)
print sub(one, two)
print add(one, three)

# result

Coord:{'y': 0, 'x': -200}
Coord:{'y': 100, 'x': 0}

如果按照假设的,那么sub(one, two)是{x: 0, y: 0}, 而sum(one, three)是{x: 100, y: 200}。 我们写decorate方法来完成这个操作。

class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Coord:' + str(self.__dict__)

def add(a, b):
     return Coordinate(a.x + b.x, a.y + b.y)

def sub(a, b):
     return Coordinate(a.x - b.x, a.y - b.y)

def wrapper(func):
     def checker(a, b):
         if a.x < 0 or a.y < 0:
             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
         if b.x < 0 or b.y < 0:
             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
         ret = func(a, b)
         if ret.x < 0 or ret.y < 0:
             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
         return ret
     return checker

one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)

add = wrapper(add)
sub = wrapper(sub)
print sub(one, two)
print add(one, three)

# result

Coord:{'y': 0, 'x': 0}
Coord:{'y': 200, 'x': 100}

这个修饰器wrapper的作用,就是返回一个 修改过之后的方法,此方法把add或者sub方法传入,然后进行checker操作,返回一个checker方法给add和sub,已经经过了修饰。从而不会再出现负数。


9. 用@符号来写decorator

就上面的方法,我们可以用@符号,加上decorate方法名来定义。更改后的方法为:

class Coordinate(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Coord:' + str(self.__dict__)

def wrapper(func):
     def checker(a, b):
         if a.x < 0 or a.y < 0:
             a = Coordinate(a.x if a.x > 0 else 0, a.y if a.y > 0 else 0)
         if b.x < 0 or b.y < 0:
             b = Coordinate(b.x if b.x > 0 else 0, b.y if b.y > 0 else 0)
         ret = func(a, b)
         if ret.x < 0 or ret.y < 0:
             ret = Coordinate(ret.x if ret.x > 0 else 0, ret.y if ret.y > 0 else 0)
         return ret
     return checker

@wrapper
def add(a, b):
     return Coordinate(a.x + b.x, a.y + b.y)
@wrapper
def sub(a, b):
     return Coordinate(a.x - b.x, a.y - b.y)

one = Coordinate(100, 200)
two = Coordinate(300, 200)
three = Coordinate(-100, -100)

print sub(one, two)
print add(one, three)

# result

Coord:{'y': 0, 'x': 0}
Coord:{'y': 200, 'x': 100}

我们可以不用再用赋值新的修饰的方法,而是直接调用add和sub就可以,因为他们因为有@wrapper修饰而已经是修饰了的方法。


10. *args 和 **kwargs

def logger(func):
    def inner(*args, **kwargs):
        print 'Arguments were: %s, %s' % (args, kwargs)
        return func(*args, **kwargs)
    return inner

@logger
def foo1(x, y=1):
    return x*y

@logger
def foo2(**kwargs):
    return 2

print foo1(5,4)
print foo1(1)
print foo2(z=2)

# result

Arguments were: (5, 4), {}
20
Arguments were: (1,), {}
1
Arguments were: (), {'z': 2}
2


对于*args,传入后是tuple的形式,**kwargs传入后是dictionary的形式。这样可以让我们随意的修饰任何的function,无论它的传入参数的识别是什么。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值