类和对象

类和函数一样都是Python中的对象。当一个类定义完成之后,Python将创建一个“类对象”并将其赋值给一个同名变量。类是type类型的对象(是不是有点拗口?)。

类对象是可调用的(callable,实现了 __call__方法),并且调用它能够创建类的对象。你可以将类当做其他对象那么处理。例如,你能够给它们的属性赋值,你能够将它们赋值给一个变量,你可以在任何可调用对象能够用的地方使用它们,比如在一个map中。事实上当你在使用map(str, [1,2,3])的时候,是将一个整数类型的list转换为字符串类型的list,因为str是一个类。可以看看下面的代码:

>>> class C(object):

... def __init__(self, s):

... print s

...

>>> myclass = C

>>> type(C)

>>> type(myclass)

>>> myclass(2)

2

<__main__.C object at 0x10e2bea50>

>>> map(myclass, [1,2,3])

1

2

3

[<__main__.C object at 0x10e2be9d0>, <__main__.C object at 0x10e2bead0>, <__main__.C object at 0x10e2beb10>]

>>> map(C, [1,2,3])

1

2

3

[<__main__.C object at 0x10e2be950>, <__main__.C object at 0x10e2beb50>, <__main__.C object at 0x10e2beb90>]

>>> C.test_attribute = True

>>> myclass.test_attribute

True

正因如此,Python中的“class”关键字不像其他语言(例如C++)那样必须出现在代码main scope中。在Python中,它能够在一个函数中嵌套出现,举个例子,我们能够这样在函数运行的过程中动态的创建类。看代码:

>>> def make_class(class_name):

... class C(object):

... def print_class_name(self):

... print class_name

... C.__name__ = class_name

... return C

...

>>> C1, C2 = map(make_class, ["C1", "C2"])

>>> c1, c2 = C1(), C2()

>>> c1.print_class_name()

C1

>>> c2.print_class_name()

C2

>>> type(c1)

>>> type(c2)

>>> c1.print_class_name.__closure__

(,)

请注意,在这里通过make_class创建的两个类是不同的对象,因此通过它们创建的对象就不属于同一个类型。正如我们在装饰器中做的那样,我们在类被创建之后手动设置了类名。同样也请注意所创建类的print_class_name方法在一个closure cell中捕捉到了类的closure和class_name。如果你对closure的概念还不是很清楚,那么最好去看看前篇,复习一下closures和decorators相关的内容。

Metaclasses

如果类是能够制造对象的对象,那制造类的对象又该叫做什么呢(相信我,这并不是一个先有鸡还是先有蛋的问题)?答案是元类(Metaclasses)。大部分常见的基础元类都是type。当输入一个参数时,type将简单的返回输入对象的类型,这就不涉及元类。然而当输入三个参数时,type将扮演元类的角色,基于输入参数创建一个类并返回。输入参数相当简单:类名,父类及其参数的字典。后面两者可以为空,来看一个例子:

>>> MyClass = type("MyClass", (object,), {"my_attribute": 0})

>>> type(MyClass)

>>> o = MyClass()

>>> o.my_attribute

0

特别注意第二个参数是一个tuple(语法看起来很奇怪,以逗号结尾)。如果你需要在类中安排一个方法,那么创建一个函数并且将其以属性的方式传递作为第三个参数,像这样:

>>> def myclass_init(self, my_attr):

... self.my_attribute = my_attr

...

>>> MyClass = type("MyClass", (object,), {"my_attribute": 0, "__init__": myclass_init})

>>> o = MyClass("Test")

>>> o.my_attribute

"Test"

>>> o.__init__

>

我们可以通过一个可调用对象(函数或是类)来自定义元类,这个对象需要三个输入参数并返回一个对象。这样一个元类在一个类上实现只要定义了它的__metaclass__属性。第一个例子,让我们做一些有趣的事情看看我们能够用元类做些什么:

>>> def mymetaclass(name, parents, attributes):

... return "Hello"

...

>>> class C(object):

... __metaclass__ = mymetaclass

...

>>> print C

Hello

>>> type(C)

请注意以上的代码,C只是简单地将一个变量引用指向了字符串“Hello”。当然了,没人会在实际中写这样的代码,这只是为了演示元类的用法而举的一个简单例子。接下来我们来做一些更有用的操作。在本系列的第二部分我们曾看到如何使用装饰器类来记录目标类每个方法的输出,现在我们来做同样的事情,不过这一次我们使用元类。我们借用之前的装饰器定义:

def log_everything_metaclass(class_name, parents, attributes):

print "Creating class", class_name

myattributes = {}

for name, attr in attributes.items():

myattributes[name] = attr

if hasattr(attr, "__call__"):

myattributes[name] = logged("%b %d %Y - %H:%M:%S",

class_name + ".")(attr)

return type(class_name, parents, myattributes)

class C(object):

__metaclass__ = log_everything_metaclass

def __init__(self, x):

self.x = x

def print_x(self):

print self.x

# Usage:

print "Starting object creation"

c = C("Test")

c.print_x()

# Output:

Creating class C

Starting object creation

- Running "C.__init__" on Aug 05 2013 - 13:50:58

- Finished "C.__init__", execution time = 0.000s

- Running "C.print_x" on Aug 05 2013 - 13:50:58

Test

- Finished "C.print_x", execution time = 0.000s

如你所见,类装饰器与元类有着很多共同点。事实上,任何能够用类装饰器完成的功能都能够用元类来实现。类装饰器有着很简单的语法结构易于阅读,所以提倡使用。但就元类而言,它能够做的更多,因为它在类被创建之前就运行了,而类装饰器则是在类创建之后才运行的。记住这点,让我们来同时运行一下两者,请注意运行的先后顺序:

def my_metaclass(class_name, parents, attributes):

print "In metaclass, creating the class."

return type(class_name, parents, attributes)

def my_class_decorator(class_):

print "In decorator, chance to modify the class."

return class_

@my_class_decorator

class C(object):

__metaclass__ = my_metaclass

def __init__(self):

print "Creating object."

c = C()

# Output:

In metaclass, creating the class.

In decorator, chance to modify the class.

Creating object.

元类的一个实际用例

让我们来考虑一个更有用的实例。假设我们正在构思一个类集合来处理MP3音乐文件中使用到的ID3v2标签Wikipedia。简而言之,标签由帧(frames)组成,而每帧通过一个四字符的识别码(identifier)进行标记。举个例子,TOPE标识了原作者帧,TOAL标识了原专辑名称等。如果我们希望为每个帧类型写一个单独的类,并且允许ID3v2标签库用户自定义他们自己的帧类。那么我们可以使用元类来实现一个类工厂模式,具体实现方式可以这样:

frametype_class_dict = {}

class ID3v2FrameClassFactory(object):

def __new__(cls, class_name, parents, attributes):

print "Creating class", class_name

# Here we could add some helper methods or attributes to c

c = type(class_name, parents, attributes)

if attributes["frame_identifier"]:

frametype_class_dict[attributes["frame_identifier"]] = c

return c

@staticmethod

def get_class_from_frame_identifier(frame_identifier):

return frametype_class_dict.get(frame_identifier)

class ID3v2Frame(object):

frame_identifier = None

__metaclass__ = ID3v2FrameClassFactory

pass

class ID3v2TitleFrame(ID3v2Frame):

__metaclass__ = ID3v2FrameClassFactory

frame_identifier = "TIT2"

class ID3v2CommentFrame(ID3v2Frame):

__metaclass__ = ID3v2FrameClassFactory

frame_identifier = "COMM"

title_class = ID3v2FrameClassFactory.get_class_from_frame_identifier("TIT2")

comment_class = ID3v2FrameClassFactory.get_class_from_frame_identifier("COMM")

print title_class

print comment_class

# Output:

Creating class ID3v2Frame

Creating class ID3v2TitleFrame

Creating class ID3v2CommentFrame

当然了,以上的代码同样可以用类装饰器来完成,以下是对应代码:

frametype_class_dict = {}

class ID3v2FrameClass(object):

def __init__(self, frame_id):

self.frame_id = frame_id

def __call__(self, cls):

print "Decorating class", cls.__name__

# Here we could add some helper methods or attributes to c

if self.frame_id:

frametype_class_dict[self.frame_id] = cls

return cls

@staticmethod

def get_class_from_frame_identifier(frame_identifier):

return frametype_class_dict.get(frame_identifier)

@ID3v2FrameClass(None)

class ID3v2Frame(object):

pass

@ID3v2FrameClass("TIT2")

class ID3v2TitleFrame(ID3v2Frame):

pass

@ID3v2FrameClass("COMM")

class ID3v2CommentFrame(ID3v2Frame):

pass

title_class = ID3v2FrameClass.get_class_from_frame_identifier("TIT2")

comment_class = ID3v2FrameClass.get_class_from_frame_identifier("COMM")

print title_class

print comment_class

Decorating class ID3v2Frame

Decorating class ID3v2TitleFrame

Decorating class ID3v2CommentFrame

如你所见,我们可以直接给装饰器传递参数,而元类却不能。给元类传递参数必须通过属性。正因如此,这里装饰器的解决方案更为清晰,同时也更容易维护。然而,同时也需要注意当装饰器被调用的时候,类已经建立完毕,这意味着此时就不能够修改其属性了。例如,一旦类建立完成,你就不能够修改__doc__。来看实际例子:

>>> def mydecorator(cls):

... cls.__doc__ = "Test!"

... return cls

...

>>> @mydecorator

... class C(object):

... """Docstring to be replaced with Test!"""

... pass

...

Traceback (most recent call last):

File "", line 2, in

File "", line 2, in mydecorator

AttributeError: attribute "__doc__" of "type" objects is not writable

>>> def mymetaclass(cls, parents, attrs):

... attrs["__doc__"] = "Test!"

... return type(cls, parents, attrs)

...

>>> class D(object):

... """Docstring to be replaced with Test!"""

... __metaclass__ = mymetaclass

...

>>> D.__doc__

"Test!"

通过type生成元类

正如我们所说,最基本的元类就是type并且类通常都是type类型。那么问题很自然来了,type类型本身是一种什么类型呢?答案也是type。这也就是说type就是它自身的元类。虽然听起来有点诡异,但这在Python解释器层面而言是可行的。

type自身就是一个类,并且我们可以从它继承出新类。这些生成的类也能作为元类,并且使用它们的类可以得到跟使用type一样的类型。来看以下的例子:

>>> class meta(type):

... def __new__(cls, class_name, parents, attributes):

... print "meta.__new__"

... return super(meta, cls).__new__(cls, class_name, parents, attributes)

... def __call__(self, *args, **kwargs):

... print "meta.__call__"

... return super(meta, self).__call__(*args, **kwargs)

...

>>> class C(object):

... __metaclass__ = meta

...

meta.__new__

>>> c = C()

meta.__call__

>>> type(C)

请注意当类创建对象时,元类的__call__函数就被调用,进而调用type.__call__创建对象。在下一节,我们将把上面的内容融合在一起。

要点集合

假定一个类C自己的元类为my_metaclass并被装饰器my_class_decorator装饰。并且,假定my_metaclass本身就是一个类,从type生成。让我们将上面提到的内容融合到一起做一个总结来显示C类以及它的对象都是怎么被创建的。首先,让我们来看看代码:

class my_metaclass(type):

def __new__(cls, class_name, parents, attributes):

print "- my_metaclass.__new__ - Creating class instance of type", cls

return super(my_metaclass, cls).__new__(cls,

class_name,

parents,

attributes)

def __init__(self, class_name, parents, attributes):

print "- my_metaclass.__init__ - Initializing the class instance", self

super(my_metaclass, self).__init__(self)

def __call__(self, *args, **kwargs):

print "- my_metaclass.__call__ - Creating object of type ", self

return super(my_metaclass, self).__call__(*args, **kwargs)

def my_class_decorator(cls):

print "- my_class_decorator - Chance to modify the class", cls

return cls

@my_class_decorator

class C(object):

__metaclass__ = my_metaclass

def __new__(cls):

print "- C.__new__ - Creating object."

return super(C, cls).__new__(cls)

def __init__(self):

print "- C.__init__ - Initializing object."

c = C()

print "Object c =", c

现在,你可以花几分钟时间测试一下你的理解,并且猜一猜打印输出的顺序。

首先,让我们来看看Python的解释器是如何阅读这部分代码的,然后我们会对应输出来加深我们的理解。

1. Python首先看类声明,准备三个传递给元类的参数。这三个参数分别为类名(class_name),父类(parent)以及属性列表(attributs)。

2. Python会检查__metaclass__属性,如果设置了此属性,它将调用metaclass,传递三个参数,并且返回一个类。

3. 在这个例子中,metaclass自身就是一个类,所以调用它的过程类似创建一个新类。这就意味着my_metaclass.__new__将首先被调用,输入四个参数,这将新建一个metaclass类的实例。然后这个实例的my_metaclass.__init__将被调用调用结果是作为一个新的类对象返回。所以此时C将被设置成这个类对象。

4. 接下来Python将查看所有装饰了此类的装饰器。在这个例子中,只有一个装饰器。Python将调用这个装饰器,将从元类哪里得到的类传递给它作为参数。然后这个类将被装饰器返回的对象所替代。

5. 装饰器返回的类类型与元类设置的相同。

6. 当类被调用创建一个新的对象实例时,因为类的类型是metaclass,因此Python将会调用元类的__call__方法。在这个例子中,my_metaclass.__call__只是简单的调用了type.__call__,目的是创建一个传递给它的类的对象实例。

7. 下一步type.__call__通过C.__new__创建一个对象。

8. 最后type.__call__通过C.__new__返回的结果运行C.__init__。

9. 返回的对象已经准备完毕。

所以基于以上的分析,我们可以看到调用的顺序如下:my_metaclass.__new__首先被调用,然后是my_metaclass.__init__,然后是my_class_decorator。至此C类已经准备完毕(返回结果就是C)。当我们调用C来创建一个对象的时候,首先会调用my_metaclass.__call__(任何对象被创建的时候,Python都首先会去调用其类的__call__方法),然后C.__new__将会被type.__call__调用(my_metaclass.__call__简单调用了type.__call__),最后是C.__init__被调用。现在让我们来看看输出:

- my_metaclass.__new__ - Creating class instance of type

- my_metaclass.__init__ - Initializing the class instance

- my_class_decorator - Chance to modify the class

- my_metaclass.__call__ - Creating object of type

- C.__new__ - Creating object.

- C.__init__ - Initializing object.

Object c = <__main__.C object at 0x1043feb90>

关于元类多说几句

元类,一门强大而晦涩的技法。在GitHub上搜索__metaclass__得到的结果多半是指向”cookbook”或其他Python教学材料的链接。一些测试用例(诸如Jython中的一些测试用例),或是其他一些写有__metaclass__ = type的地方只是为了确保新类被正常使用了。坦白地说,这些用例都没有真正地使用元类。过滤了下结果,我只能找到两个地方真正使用了元类:ABCMeta和djangoplugins。

ABCMeta是一个允许注册抽象基类的元类。如果想了解多些请查看其官方文档,本文将不会讨论它。

对于djangoplugins而言,基本的思想是基于这篇文章article on a simple plugin framework for Python,使用元类是为了创建一个插件挂载系统。我并没有对其有深入的研究,不过我感觉这个功能可以使用装饰器来实现。如果你有相关的想法请在 本文后留言。

总结笔记

通过理解元类能够帮助我们更深入的理解Python中类和对象的行为,现实中使用它们的情况可能比文中的例子要复杂得多。大部分元类完成的功能都可以使用装饰器来实现。所以当你的第一直觉是使用元类来解决你的问题,那么请你停下来先想想这是否必要。如果不是非要使用元类,那么请三思而行。这会使你的代码更易懂,更易调试和维护。

python中classes和class的区别-Python中的Classes和Metaclasses详解相关推荐

  1. C语言中指针与数组的区别,C语言 指针与数组的详解及区别

    C语言 指针与数组的详解及对比 通俗理解数组指针和指针数组 数组指针: eg:int( *arr)[10]; 数组指针通俗理解就是这个数组作为指针,指向某一个变量. 指针数组: eg:int*arr[ ...

  2. Python eval 与 exec 函数的区别 - Python零基础入门教程

    目录 一.Python eval 与 exec 函数的区别 二.价值 10 个亿的智能机器人核心代码 三.猜你喜欢 基础 Python 学习路线推荐 : Python 学习目录 >> Py ...

  3. Python 进程 Process 与线程 threading 区别 - Python零基础入门教程

    目录 一.Python 线程 threading 创建 二.Python 进程 Process 创建 三.Python 进程 Process 和线程 threading 区别 四.Python 进程 ...

  4. [Python从零到壹] 九.网络爬虫之Selenium基础技术万字详解(定位元素、常用方法、键盘鼠标操作)

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  5. [Python从零到壹] 五.网络爬虫之BeautifulSoup基础语法万字详解

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  6. [Python图像识别] 五十.Keras构建AlexNet和CNN实现自定义数据集分类详解

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

  7. 作为SLAM中最常用的闭环检测方法,视觉词袋模型技术详解来了

    摘自:https://mp.weixin.qq.com/s/OZnnuA31tEaVt0vnDOy5hQ 作为SLAM中最常用的闭环检测方法,视觉词袋模型技术详解来了 原创 小翼 飞思实验室 今天 基 ...

  8. linux上传文件命令ftp put,Linux ftp 命令行中下载文件get与上传文件put的命令应用详解...

    介绍:从本地以用户anok登录的机器192.168.0.16上通过ftp远程登录到192.168.0.6的ftp服务器上,登录用户名是peo.以下为使用该连接做的实验. 查看远程ftp服务器上用户pe ...

  9. 中科大-计算机类考研真题(初试笔试真题详解+复试笔试机试真题详解+面试问题汇总分析)

    中科大-计算机类考研真题(初试笔试真题详解+复试笔试机试真题详解+面试问题汇总分析) 初试笔试真题2003年~2019年:答案+详解 复试面试问题总结:问题汇总+详解分析 复试面试经验总结:个人小结 ...

  10. linux get与put,科技常识:Linux ftp 命令行中下载文件get与上传文件put的命令应用详解...

    今天小编跟大家讲解下有关Linux ftp 命令行中下载文件get与上传文件put的命令应用详解 ,相信小伙伴们对这个话题应该也很关注吧,小编也收集到了有关Linux ftp 命令行中下载文件get与 ...

最新文章

  1. 英特尔 Arria 10 GX FPGA 正式商用,瞄准数据中心市场
  2. 编程珠玑第二章习题答案
  3. mysql insert 自增_MySQL自增列插入0值的解决方案
  4. java 反射 获取成员_java 反射获取成员
  5. Fedora 19 Mate环境安装Gnome3
  6. ubuntu安装labelme
  7. 2016-7-3 linux学习笔记
  8. 勤哲web配置教程_勤哲Excel服务器新手教程
  9. 科宇扫地机器人_扫地谁更精准更干净?新一代3D视讯+激光成像 PK 老式激光扫描...
  10. uniapp安卓app里拦截webview下载apk,显示下载进度
  11. matlab 检验异方差,stata中面板数据异方差的处理_stata面板异方差检验
  12. 后端理解ajax和axios
  13. javascript适合移动端的响应式瀑布流插件实例演示
  14. Python代码格式化工具autopep8安装及使用极简版
  15. SATA 3.2协议 Error handing机制
  16. 介孔二氧化硅纳米粒子应用在组织工程
  17. BH1750_数字16位串行输出型环境光传感器
  18. 不一样的 LaTeX 教程(第二话):LaTeX 多级标题设置必知必会
  19. 含泪赔了近200万,我终于明白不是什么人都能干电商的……
  20. Postman教程-Send Requests相关的基本操作

热门文章

  1. 【BZOJ】1711: [Usaco2007 Open]Dining吃饭
  2. 推荐:Webpack2入门到深入的中文文档
  3. [Java]JDBC操作MySQL数据库
  4. 需要用到的各种Jar包
  5. Fragment的startActivityForResult详细解决方案
  6. Cocos2d入门--3--向量的应用
  7. 高频交易都有哪些著名的算法
  8. [系统开发] Postfix 邮件管理系统
  9. javascript自定义cookie
  10. python操作yaml的方法详解