您的位置:首页 > 编程语言 > Python开发

Python深入04 闭包

2016-05-16 11:46 405 查看
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

闭包(closure)是函数式编程的重要的语法结构。函数式编程是一种编程范式 (而面向过程编程和面向对象编程也都是编程范式)。在面向过程编程中,我们见到过函数(function);在面向对象编程中,我们见过对象(object)。函数和对象的根本目的是以某种逻辑方式组织代码,并提高代码的可重复使用性(reusability)。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。

不同的语言实现闭包的方式不同。Python以函数对象为基础,为闭包这一语法结构提供支持的 (我们在特殊方法与多范式中,已经多次看到Python使用对象来实现一些特殊的语法)。Python一切皆对象,函数这一语法结构也是一个对象。在函数对象中,我们像使用一个普通对象一样使用函数对象,比如更改函数对象的名字,或者将函数对象作为参数进行传递。

函数对象的作用域

和其他对象一样,函数对象也有其存活的范围,也就是函数对象的作用域。函数对象是使用def语句定义的,函数对象的作用域与def所在的层级相同。比如下面代码,我们在line_conf函数的隶属范围内定义的函数line,就只能在line_conf的隶属范围内调用。

def line_conf():
def line(x):
return 2*x+1
print(line(5))   # within the scope

line_conf()
print(line(5))       # out of the scope


line函数定义了一条直线(y = 2x + 1)。可以看到,在line_conf()中可以调用line函数,而在作用域之外调用line将会有下面的错误:

NameError: name 'line' is not defined

说明这时已经在作用域之外。

同样,如果使用lambda定义函数,那么函数对象的作用域与lambda所在的层级相同。

闭包

函数是一个对象,所以可以作为某个函数的返回结果。

def line_conf():
def line(x):
return 2*x+1
return line       # return a function object

my_line = line_conf()
print(my_line(5))


上面的代码可以成功运行。line_conf的返回结果被赋给line对象。上面的代码将打印11。

如果line()的定义中引用了外部的变量,会发生什么呢?

def line_conf():
b = 15
def line(x):
return 2*x+b
return line       # return a function object

b = 5
my_line = line_conf()
print(my_line(5))


我们可以看到,line定义的隶属程序块中引用了高层级的变量b,但b信息存在于line的定义之外 (b的定义并不在line的隶属程序块中)。我们称b为line的环境变量。事实上,line作为line_conf的返回值时,line中已经包括b的取值(尽管b并不隶属于line)。

上面的代码将打印25,也就是说,line所参照的b值是函数对象定义时可供参考的b值,而不是使用时的b值。

一个函数和它的环境变量合在一起,就构成了一个闭包(closure)。在Python中,所谓的闭包是一个包含有环境变量取值的函数对象。环境变量取值被保存在函数对象的__closure__属性中。比如下面的代码:

def line_conf():
b = 15
def line(x):
return 2*x+b
return line       # return a function object

b = 5
my_line = line_conf()
print(my_line.__closure__)
print(my_line.__closure__[0].cell_contents)


__closure__里包含了一个元组(tuple)。这个元组中的每个元素是cell类型的对象。我们看到第一个cell包含的就是整数15,也就是我们创建闭包时的环境变量b的取值。

下面看一个闭包的实际例子:

def line_conf(a, b):
def line(x):
return ax + b
return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5), line2(5))


这个例子中,函数line与环境变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个环境变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。利用闭包,我们实际上创建了泛函。line函数定义一种广泛意义的函数。这个函数的一些方面已经确定(必须是直线),但另一些方面(比如a和b参数待定)。随后,我们根据line_conf传递来的参数,通过闭包的形式,将最终函数确定下来。

闭包与并行运算

闭包有效的减少了函数所需定义的参数数目。这对于并行运算来说有重要的意义。在并行运算的环境下,我们可以让每台电脑负责一个函数,然后将一台电脑的输出和下一台电脑的输入串联起来。最终,我们像流水线一样工作,从串联的电脑集群一端输入数据,从另一端输出数据。这样的情境最适合只有一个参数输入的函数。闭包就可以实现这一目的。

并行运算正称为一个热点。这也是函数式编程又热起来的一个重要原因。函数式编程早在1950年代就已经存在,但应用并不广泛。然而,我们上面描述的流水线式的工作并行集群过程,正适合函数式编程。由于函数式编程这一天然优势,越来越多的语言也开始加入对函数式编程范式的支持。

欢迎继续阅读“Python快速教程

好久没有学习Python了,应为工作的需要,再次拾起python,唤起记忆。

当函数的参数不确定时,可以使用*args 和**kwargs,*args 没有key值,**kwargs有key值。

还是直接来代码吧,废话少说

[python]
view plain
copy
print?

def fun_var_args(farg, *args):
print "arg:", farg
for value in args:
print "another arg:", value

fun_var_args(1, "two", 3) # *args可以当作可容纳多个变量组成的list

result:

[python]
view plain
copy
print?

arg: 1
another arg: two
another arg: 3

**kwargs:

[python]
view plain
copy
print?

def fun_var_kwargs(farg, **kwargs):
print "arg:", farg
for key in kwargs:
print "another keyword arg: %s: %s" % (key, kwargs[key])

fun_var_kwargs(farg=1, myarg2="two", myarg3=3) # myarg2和myarg3被视为key, 感觉**kwargs可以当作容纳多个key和value的dictionary

result:

[python]
view plain
copy
print?

arg: 1
another keyword arg: myarg2: two
another keyword arg: myarg3: 3

也可以用下面的形式:

[python]
view plain
copy
print?

def fun_var_args_call(arg1, arg2, arg3):
print "arg1:", arg1
print "arg2:", arg2
print "arg3:", arg3

args = ["two", 3] #list

fun_var_args_call(1, *args)

result:

[python]
view plain
copy
print?

arg1: 1
arg2: two
arg3: 3

[python]
view plain
copy
print?

def fun_var_args_call(arg1, arg2, arg3):
print "arg1:", arg1
print "arg2:", arg2
print "arg3:", arg3

kwargs = {"arg3": 3, "arg2": "two"} # dictionary

fun_var_args_call(1, **kwargs)

result:

[python]
view plain
copy
print?

arg1: 1
arg2:"two"
arg3:3

Hi 朋友们。由于我最近都比较忙,所以已经很长一段时间没有写博客了。在这篇文章中我将和大家分享一些真正有用的技巧和窍门,这些技巧和窍门你们之前可能并不知道。所以不浪费时间了,让我们直接来看看这些内容吧:

枚举

之前我们这样操作:

Python

i = 0
for item in iterable:
print i, item
i += 1

1
2
3
4

i=
0

for itemin
iterable:
printi,item

i+=
1

现在我们这样操作:

Python

for i, item in enumerate(iterable):
print i, item

1
2

fori,item
inenumerate(iterable):

printi,item

enumerate函数还可以接收第二个参数。就像下面这样:

Python

>>> list(enumerate('abc'))
[(0, 'a'), (1, 'b'), (2, 'c')]

>>> list(enumerate('abc', 1))
[(1, 'a'), (2, 'b'), (3, 'c')]

1
2
3
4
5

>>>list(enumerate('abc'))

[(0,'a'),(1,'b'),(2,'c')]

>>> list(enumerate('abc',1))
[(1,'a'),(2,'b'),(3,'c')]

字典/集合 解析

你也许知道如何进行列表解析,但是可能不知道字典/集合解析。它们简单易用且高效。就像下面这个例子:

Python

my_dict = {i: i * i for i in xrange(100)}
my_set = {i * 15 for i in xrange(100)}

# There is only a difference of ':' in both

# 两者的区别在于字典推导中有冒号

1
2
3
4
5
6

my_dict=
{i:i
*i
fori
inxrange(100)}

my_set ={i*
15for
iin
xrange(100)}

# There is only a difference of ':' in both

# 两者的区别在于字典推导中有冒号

强制浮点除法

如果我们除以一个整数,即使结果是一个浮点数,Python 2(校注,这里我添上了版本号)依旧会给我们一个整数。为了规避这个问题,我们需要这样做:

Python

result = 1.0/2

1

result=
1.0/2

但是现在有一种别的方法可以解决这个问题,甚至在之前我都没有意识到有这种方法存在。你可以进行如下操作:

Python

from __future__ import division
result = 1/2
# print(result)
# 0.5

1
2
3
4

from__future__
importdivision

result =1/2
# print(result)

# 0.5

瞧,现在你不需要在数据上附件“.0” 来获得准确答案了。需要注意的是这个窍门只适用于Python 2。在Python 3 中就不需要进行import 操作了,因为它已经默认进行import了。

简单服务器

你是否想要快速方便的共享某个目录下的文件呢?你可以这么做:

Python

# Python2
python -m SimpleHTTPServer

# Python 3
python3 -m http.server

1
2
3
4
5

# Python2

python -mSimpleHTTPServer

# Python 3
python3-mhttp.server

这样会为启动一个服务器。

对Python表达式求值

我们都知道eval函数,但是我们知道literal_eval函数么?也许很多人都不知道吧。可以用这种操作:

Python

import ast
my_list = ast.literal_eval(expr)

1
2

importast

my_list =ast.literal_eval(expr)

来代替以下这种操作:

Python

expr = "[1, 2, 3]"
my_list = eval(expr)

1
2

expr=
"[1, 2, 3]"

my_list =eval(expr)

我相信对于大多数人来说这种形式是第一次看见,但是实际上这个在Python中已经存在很长时间了。

脚本分析

你可以很容易的通过运行以下代码进行脚本分析:

Python

python -m cProfile my_script.py

1

python-mcProfile
my_script.py

对象自检

在Python 中你可以通过dir() 函数来检查对象。正如下面这个例子:

Python

>>> foo = [1, 2, 3, 4]
>>> dir(foo)
['__add__', '__class__', '__contains__',
'__delattr__', '__delitem__', '__delslice__', ... ,
'extend', 'index', 'insert', 'pop', 'remove',
'reverse', 'sort']

1
2
3
4
5
6

>>>foo
=[1,2,3,4]

>>> dir(foo)
['__add__','__class__','__contains__',

'__delattr__','__delitem__','__delslice__',...,

'extend','index','insert','pop','remove',

'reverse','sort']

调试脚本

你可以很方便的通过pdb模块在你的脚本中设置断点。正如下面这个例子:

Python

import pdb
pdb.set_trace()

1
2

importpdb

pdb.set_trace()

你可以在脚本的任何地方加入pdb.set_trace(),该函数会在那个位置设置一个断点。超级方便。你应该多阅读pdb 函数的相关内容,因为在它里面还有很多鲜为人知的功能。

if 结构简化

如果你需要检查几个数值你可以用以下方法:

Python

if n in [1,4,5,6]:

1

ifn
in[1,4,5,6]:

来替代下面这个方式:

Python

if n==1 or n==4 or n==5 or n==6:

1

ifn==1or
n==4or
n==5or
n==6:

字符串/数列 逆序

你可以用以下方法快速逆序排列数列:

Python

>>> a = [1,2,3,4]
>>> a[::-1]
[4, 3, 2, 1]

# This creates a new reversed list.
# If you want to reverse a list in place you can do:

a.reverse()

1
2
3
4
5
6
7
8

>>>a
=[1,2,3,4]

>>> a[::-1]
[4,3,2,1]

# This creates a new reversed list.

# If you want to reverse a list in place you can do:

a.reverse()

这总方式也同样适用于字符串的逆序:

Python

>>> foo = "yasoob"
>>> foo[::-1]
'boosay'

1
2
3

>>>foo
="yasoob"

>>> foo[::-1]
'boosay'

优美地打印

你可以通过以下方式对字典和数列进行优美地打印:

Python

from pprint import pprint
pprint(my_dict)

1
2

frompprint
importpprint

pprint(my_dict)

这种方式对于字典打印更加高效。此外,如果你想要漂亮的将文件中的json文档打印出来,你可以用以下这种方式:

Python

cat file.json | python -m json.tools

1

catfile.json|
python-mjson.tools

三元运算

三元运算是if-else 语句的快捷操作,也被称为条件运算。这里有几个例子可以供你参考,它们可以让你的代码更加紧凑,更加美观。

Python

[on_true] if [expression] else [on_false]
x, y = 50, 25
small = x if x < y else y

1
2
3

[on_true]if
[expression]else
[on_false]

x,y
=50,25
small=
xif
x<
yelse
y

这就是今天所有的内容。希望你们能喜欢这篇文章,并且从这篇文章能学到一两个技巧供以后使用。我们下篇文章再见吧。更多内容敬请关注我们的Facebook和Twitter!

如果你有任何意见或建议?你可以在下面评论或者给我发邮件,我的邮件地址是yasoob.khld(at)gmail.com

深刻理解Python中的元类(metaclass)

2012/06/11 ·
基础知识 ·
14 评论 · Python

分享到:7

本文由
伯乐在线 - bigship 翻译。未经许可,禁止转载!

英文出处:stackoverflow。欢迎加入翻译组

译注:这是一篇在Stack overflow上很热的帖子。提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解。他知道这肯定和自省有关,但仍然觉得不太明白,希望大家可以给出一些实际的例子和代码片段以帮助理解,以及在什么情况下需要进行元编程。于是e-satis同学给出了神一般的回复,该回复获得了985点的赞同点数,更有人评论说这段回复应该加入到Python的官方文档中去。而e-satis同学本人在Stack
Overflow中的声望积分也高达64271分。以下就是这篇精彩的回复(提示:非常长)





类也是对象

在理解元类之前,你需要先掌握Python中的类。Python中类的概念借鉴于Smalltalk,这显得有些奇特。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

Python

>>> class ObjectCreator(object):
… pass

>>> my_object = ObjectCreator()
>>> print my_object
<__main__.ObjectCreator object at 0x8974f2c>

1
2
3
4
5
6

>>>
class ObjectCreator(object):

… pass


>>> my_object
= ObjectCreator()
>>>
print my_object

<__main__.ObjectCreator
object at
0x8974f2c>

但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。下面的代码段:

Python

>>> class ObjectCreator(object):
… pass


1
2
3

>>>
class ObjectCreator(object):

… pass


将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

1) 你可以将它赋值给一个变量

2) 你可以拷贝它

3) 你可以为它增加属性

4) 你可以将它作为函数参数进行传递

下面是示例:

Python

>>> print ObjectCreator # 你可以打印一个类,因为它其实也是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
… print o

>>> echo(ObjectCreator) # 你可以将类做为参数传给函数
<class '__main__.ObjectCreator'>
>>> print hasattr(ObjectCreator, 'new_attribute')
Fasle
>>> ObjectCreator.new_attribute = 'foo' # 你可以为类增加属性
>>> print hasattr(ObjectCreator, 'new_attribute')
True
>>> print ObjectCreator.new_attribute
foo
>>> ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

>>>
print ObjectCreator
# 你可以打印一个类,因为它其实也是一个对象

<class
'__main__.ObjectCreator'>
>>>
def echo(o):

… print
o


>>> echo(ObjectCreator)
# 你可以将类做为参数传给函数
<class
'__main__.ObjectCreator'>

>>> print
hasattr(ObjectCreator,
'new_attribute')
Fasle

>>> ObjectCreator.new_attribute
= 'foo'
# 你可以为类增加属性
>>>
print hasattr(ObjectCreator,
'new_attribute')

True
>>>
print ObjectCreator.new_attribute

foo
>>>
ObjectCreatorMirror
= ObjectCreator
# 你可以将类赋值给一个变量

>>> print
ObjectCreatorMirror()
<__main__.ObjectCreator
object at
0x8997b4c>

动态地创建类

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。

Python

>>> def choose_class(name):
… if name == 'foo':
… class Foo(object):
… pass
… return Foo # 返回的是类,不是类的实例
… else:
… class Bar(object):
… pass
… return Bar

>>> MyClass = choose_class('foo')
>>> print MyClass # 函数返回的是类,不是类的实例
<class '__main__'.Foo>
>>> print MyClass() # 你可以通过这个类创建类实例,也就是对象
<__main__.Foo object at 0x89c6d4c>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

>>>
def choose_class(name):

… if
name ==
'foo':

class Foo(object):

… pass

return Foo
# 返回的是类,不是类的实例

… else:

class Bar(object):

… pass

return Bar


>>>
MyClass =
choose_class('foo')

>>> print
MyClass # 函数返回的是类,不是类的实例
<class
'__main__'.Foo>

>>> print
MyClass() # 你可以通过这个类创建类实例,也就是对象
<__main__.Foo
object at
0x89c6d4c>

但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:

Python

>>> print type(1)
<type 'int'>
>>> print type("1")
<type 'str'>
>>> print type(ObjectCreator)
<type 'type'>
>>> print type(ObjectCreator())
<class '__main__.ObjectCreator'>

1
2
3
4
5
6
7
8

>>>
print type(1)

<type
'int'>
>>>
print type("1")

<type
'str'>
>>>
print type(ObjectCreator)

<type
'type'>
>>>
print type(ObjectCreator())

<class
'__main__.ObjectCreator'>

这里,type有一种完全不同的能力,它也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type可以像这样工作:

Python

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

1

type(类名,
父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

Python

>>> class MyShinyClass(object):
… pass

1
2

>>>
class MyShinyClass(object):

… pass

可以手动像这样创建:

Python

>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类对象
>>> print MyShinyClass
<class '__main__.MyShinyClass'>
>>> print MyShinyClass() # 创建一个该类的实例
<__main__.MyShinyClass object at 0x8997cec>

1
2
3
4
5

>>>
MyShinyClass =
type('MyShinyClass',
(),
{}) # 返回一个类对象

>>> print
MyShinyClass
<class
'__main__.MyShinyClass'>

>>> print
MyShinyClass() # 创建一个该类的实例
<__main__.MyShinyClass
object at
0x8997cec>

你会发现我们使用“MyShinyClass”作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。

type 接受一个字典来为类定义属性,因此

Python

>>> class Foo(object):
… bar = True

1
2

>>>
class Foo(object):

… bar
= True

可以翻译为:

Python

>>> Foo = type('Foo', (), {'bar':True})

1

>>>
Foo =
type('Foo',
(),
{'bar':True})

并且可以将Foo当成一个普通的类一样使用:

Python

>>> print Foo
<class '__main__.Foo'>
>>> print Foo.bar
True
>>> f = Foo()
>>> print f
<__main__.Foo object at 0x8a9b84c>
>>> print f.bar
True

1
2
3
4
5
6
7
8
9

>>>
print Foo

<class
'__main__.Foo'>
>>>
print Foo.bar

True
>>>
f =
Foo()

>>> print
f
<__main__.Foo
object at
0x8a9b84c>

>>> print
f.bar
True

当然,你可以向这个类继承,所以,如下的代码:

Python

>>> class FooChild(Foo):
… pass

1
2

>>>
class FooChild(Foo):

… pass

就可以写成:

Python

>>> FooChild = type('FooChild', (Foo,),{})
>>> print FooChild
<class '__main__.FooChild'>
>>> print FooChild.bar # bar属性是由Foo继承而来
True

1
2
3
4
5

>>>
FooChild =
type('FooChild',
(Foo,),{})

>>> print
FooChild
<class
'__main__.FooChild'>

>>> print
FooChild.bar
# bar属性是由Foo继承而来
True

最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。

Python

>>> def echo_bar(self):
… print self.bar

>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

1
2
3
4
5
6
7
8
9
10
11

>>>
def echo_bar(self):

… print
self.bar


>>> FooChild
= type('FooChild',
(Foo,),
{'echo_bar':
echo_bar})
>>>
hasattr(Foo,
'echo_bar')

False
>>>
hasattr(FooChild,
'echo_bar')

True
>>>
my_foo =
FooChild()

>>> my_foo.echo_bar()
True

你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。

到底什么是元类(终于到主题了)

元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。好吧,元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解 为:

Python

MyClass = MetaClass()
MyObject = MyClass()

1
2

MyClass
= MetaClass()

MyObject =
MyClass()

你已经看到了type可以让你像这样做:

Python

MyClass = type('MyClass', (), {})

1

MyClass
= type('MyClass',
(),
{})

这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查__class__属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来。

Python

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>>foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

1
2
3
4
5
6
7
8
9
10
11
12
13

>>>
age =
35

>>> age.__class__
<type
'int'>

>>> name
= 'bob'
>>>
name.__class__

<type
'str'>
>>>
def foo():
pass

>>>foo.__class__
<type
'function'>

>>> class
Bar(object):
pass
>>>
b =
Bar()

>>> b.__class__
<class
'__main__.Bar'>

现在,对于任何一个__class__的__class__属性又是什么呢?

Python

>>> a.__class__.__class__
<type 'type'>
>>> age.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

1
2
3
4
5
6
7
8

>>>
a.__class__.__class__

<type
'type'>
>>>
age.__class__.__class__

<type
'type'>
>>>
foo.__class__.__class__

<type
'type'>
>>>
b.__class__.__class__

<type
'type'>

因此,元类就是创建类这种对象的东西。如果你喜欢的话,可以把元类称为“类工厂”(不要和工厂类搞混了:D) type就是Python的内建元类,当然了,你也可以创建自己的元类。

__metaclass__属性

你可以在写一个类的时候为其添加__metaclass__属性。

Python

class Foo(object):
__metaclass__ = something…
[…]

1
2
3

class
Foo(object):

__metaclass__
= something…
[…]

如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下class Foo(object),但是类对象Foo还没有在内存中创建。Python会在类的定义中寻找__metaclass__属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的type来创建这个类。把下面这段话反复读几次。当你写如下代码时 :

Python

class Foo(Bar):
pass

1
2

class
Foo(Bar):

pass

Python做了如下的操作:

Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧跟我的思路)。如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东东都可以。

自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类(我知道,某些名字里带有‘class’的东西并不需要是一个class,画画图理解下,这很有帮助)。所以,我们这里就先以一个简单的函数作为例子开始。

Python

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
# 选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))

1
2
3
4
5

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入

def upper_attr(future_class_name,
future_class_parents,
future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''

# 选择所有不以'__'开头的属性
attrs
= ((name,
value)
for name,
value in
future_class_attr.items()
if not
name.startswith('__'))

Python

# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)

# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # 这会作用到这个模块中的所有类

class Foo(object):
# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar = 'bip'

1
2
3
4
5
6
7
8
9
10
11

# 将它们转为大写形式

uppercase_attr
= dict((name.upper(),
value)
for name,
value in
attrs)

# 通过'type'来做类对象的创建
return
type(future_class_name,
future_class_parents,
uppercase_attr)

__metaclass__
= upper_attr # 这会作用到这个模块中的所有类

class
Foo(object):

# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar
= 'bip'

Python

print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True

f = Foo()
print f.BAR
# 输出:'bip'

1
2
3
4
5
6
7
8

print
hasattr(Foo,
'bar')

# 输出: False
print
hasattr(Foo,
'BAR')

# 输出:True

f =
Foo()
print
f.BAR

# 输出:'bip'

现在让我们再做一次,这一次用一个真正的class来当做元类。

Python

# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法
# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建
# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情
# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type(future_class_name, future_class_parents, uppercase_attr)

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 请记住,'type'实际上是一个类,就像'str'和'int'一样

# 所以,你可以从type继承
class
UpperAttrMetaClass(type):

# __new__ 是在__init__之前被调用的特殊方法
# __new__是用来创建对象并返回之的方法

# 而__init__只是用来将传入的参数初始化给对象
# 你很少用到__new__,除非你希望能够控制对象的创建

# 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
# 如果你希望的话,你也可以在__init__中做些事情

# 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
def
__new__(upperattr_metaclass,
future_class_name,
future_class_parents,
future_class_attr):

attrs
= ((name,
value)
for name,
value in
future_class_attr.items()
if not
name.startswith('__'))
uppercase_attr
= dict((name.upper(),
value)
for name,
value in
attrs)

return
type(future_class_name,
future_class_parents,
uppercase_attr)

但是,这种方式其实不是OOP。我们直接调用了type,而且我们没有改写父类的__new__方法。现在让我们这样去处理:

Python

class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)

# 复用type.__new__方法
# 这就是基本的OOP编程,没什么魔法
return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

1
2
3
4
5
6
7
8

class
UpperAttrMetaclass(type):

def
__new__(upperattr_metaclass,
future_class_name,
future_class_parents,
future_class_attr):
attrs
= ((name,
value)
for name,
value in
future_class_attr.items()
if not
name.startswith('__'))

uppercase_attr
= dict((name.upper(),
value)
for name,
value in
attrs)

# 复用type.__new__方法
# 这就是基本的OOP编程,没什么魔法

return
type.__new__(upperattr_metaclass,
future_class_name,
future_class_parents,
uppercase_attr)

你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像self一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:

Python

class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type.__new__(cls, name, bases, uppercase_attr)

1
2
3
4
5

class
UpperAttrMetaclass(type):

def
__new__(cls,
name,
bases,
dct):
attrs
= ((name,
value)
for name,
value in
dct.items()
if not
name.startswith('__')

uppercase_attr =
dict((name.upper(),
value)
for name,
value in
attrs)
return
type.__new__(cls,
name,
bases,
uppercase_attr)

如果使用super方法的话,我们还可以使它变得更清晰一些,这会缓解继承(是的,你可以拥有元类,从元类继承,从type继承)

Python

class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

1
2
3
4
5

class
UpperAttrMetaclass(type):

def
__new__(cls,
name,
bases,
dct):
attrs
= ((name,
value)
for name,
value in
dct.items()
if not
name.startswith('__'))

uppercase_attr
= dict((name.upper(),
value)
for name,
value in
attrs)
return
super(UpperAttrMetaclass,
cls).__new__(cls,
name,
bases,
uppercase_attr)

就是这样,除此之外,关于元类真的没有别的可说的了。使用到元类的代码比较复杂,这背后的原因倒并不是因为元类本身,而是因为你通常会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。确实,用元类来搞些“黑暗魔法”是特别有用的,因而会搞出些复杂的东西来。但就元类本身而言,它们其实是很简单的:
1) 拦截类的创建
2) 修改类
3) 返回修改之后的类

为什么要用metaclass类而不是函数?

由于__metaclass__可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:

1) 意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。

2) 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。

3) 你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。

4) 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。

5) 哇哦,这东西的名字是metaclass,肯定非善类,我要小心!

究竟为什么要使用元类?

现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

元类的主要用途是创建API。一个典型的例子是Django ORM。它允许你像这样定义:

Python

class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()

1
2
3

class
Person(models.Model):

name
= models.CharField(max_length=30)
age
= models.IntegerField()

但是如果你像这样做的话:

Python

guy = Person(name='bob', age='35')
print guy.age

1
2

guy =
Person(name='bob',
age='35')

print guy.age

这并不会返回一个IntegerField对象,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有可能的,因为models.Model定义了__metaclass__, 并且使用了一些魔法能够将你刚刚定义的简单的Person类转变成对数据库的一个复杂hook。Django框架将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。

结语

首先,你知道了类其实是能够创建出类实例的对象。好吧,事实上,类本身也是实例,当然,它们是元类的实例。

Python

>>>class Foo(object): pass
>>> id(Foo)
142630324

1
2
3

>>>class
Foo(object):
pass

>>> id(Foo)
142630324

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

1) Monkey patching

2) class decorators

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类 :D
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python 闭包