Status: | 草稿|校对|正式;HuangYi;完成度60%; |
---|
类与实例
通常大家说的是"类与对象",不过 python 中万物皆"对象",对象这个词语的含义太宽泛。 实际上我们这里要讨论的只是用户自定义的类和这些类的实例而已,类及其实例都只是对象这个大家族中的一员。
定义类
>>> class Klass(object): ... ' docstring... ' ... def __init__(self, a): ... self.attr_a = a ... >>> Klass.__doc__ ' docstring... ' >>> obj = Klass(1) >>> obj.attr_a 1
关键字 class 定义一个类, Klass 是类的名字,括号中 object 是它的基类, python 中所有的 class 都是从 object 继承而来 (虽然在目前的 python 版本中还存在一类所谓的 old-style class ,他们是不从 object 继承而来的,不过这只是为了保持向后兼容性并且很快就要在 python3.0 中彻底退出历史舞台,所以我们这里直接忽略它了)。
和许多语言不同的是,python 中构造函数叫做 __init__ ,第一个参数传递的就是将要初始化的实例对象本身,类似许多语言中的 this 。
和函数一样,class 也可以定义文档字符串,同样是通过 __doc__ 访问。
创建实例对象并不需要学习新的语法,创建实例和函数调用一摸一样。传给 Klass 的参数最终传给构造函数 __init__ ,而返回值便是新创建的实例对象。
属性
在构造函数中的 self.attr_a = a 便给 self 这个对象增加了一个名为 attr_a 的属性,其绑定的对象就是传入的参数 a 所绑定的对象。
对象的 __dict__ 属性可以让你以字典的方式查看对象的所有属性::
>>> obj.__dict__ {'attr_a': 1}
不光在构造函数中,其实在任何时候你都可以给对象增加属性,你只需要给不存在的属性绑定对象即可,python 会自动创建不存在的属性::
>>> obj.attr_b = 1 >>> obj.attr_b 1 >>> obj.__dict__ {'attr_b': 1, 'attr_a': 1}
讲到属性,需要明确的一个概念就是不光实例对象有属性,类也是对象,类自然也有属性!:
>>> class Klass1(object): ... ' docstrign... ' ... class_attr1 = 'hello' ... def __init__(self, a): ... self.attr_a = a ... >>> Klass1.class_attr1 'hello'
我们也可以使用 __dict__ 来查看类对象的所有属性:
>>> from pprint import pprint >>> pprint(dict(Klass1.__dict__)) {'__dict__': <attribute '__dict__' of 'Klass1' objects>, '__doc__': ' docstrign... ', '__init__': <function __init__ at 0x00FBAEF0>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Klass1' objects>, 'class_attr1': 'hello'}
class 的属性比较多,为了方便查看我们使用了 pprint 模块中的 pprint 方法,其功能是以更可读的方式输出一些复杂数据结构, 具体用法可以查看 python 自带手册。
当访问实例对象的属性时,如果属性不存在,将自动查找其对应的类对象的属性::
>>> obj1 = Klass1(1) >>> obj1.class_attr1 'hello'
类属性是和类对象的属性,同一个类对象的所有实例都共享同一份类属性::
>>> obj2 = Klass1(2) >>> Klass1.class_attr1 = 'changed' >>> obj1.class_attr1 'changed' >>> obj2.class_attr1 'changed'
方法
>>> class Klass(object): ... def __init__(self, name): ... self.name = name ... def greet_to(self, name): ... print self.name, 'say hello to', name ... >>> obj = Klass('HuangYi') >>> obj.greet_to('you') HuangYi say hello to you >>> pprint(dict(Klass.__dict__)) {'__dict__': <attribute '__dict__' of 'Klass' objects>, '__doc__': None, '__init__': <function __init__ at 0x010122B0>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Klass' objects>, 'greet_to': <function greet_to at 0x010122F0>}
方法其实也是属性!准确地说还是属于类对象的属性。在 Klass 中我们定义了两个方法: 构造函数 __init__ 和普通方法 greet_to 。
方法本质上说其实是对函数的一层简单包装,这层包装的作用就是将调用方法的这个对象当做第一个参数传进去, 所以在定义方法的时候千万别忘了声明第一个参数,按照约定,这个参数我们叫它 self 。:
>>> obj.greet_to <bound method Klass.greet_to of <__main__.Klass object at 0x0101C550>> >>> Klass.__dict__['greet_to'] <function greet_to at 0x010122F0>
直接通过实例对象 obj 访问属性 greet_to 时得到的其实是对这个函数包装后产生的"方法"。 直接通过 Klass.__dict__ 取 greet_to 属性实际绑定的对象时,你得到的才是这个还没有经过包装的"纯净"的函数!
>>> func = Klass.__dict__['greet_to'] >>> func(obj, 'you') HuangYi say hello to you
如果你要问这个包装是何时以及如何产生的,这其实又涉及到 python 中另一个相对高级的概念: Descriptors。 如果有兴趣可以查看 Raymond 写的精彩教程(http://users.rcn.com/python/download/Descriptor.htm),本书将不做更多介绍。
不过当我们认识到方法与函数的这一层关系后,我们已经可以利用它来实现一些有意思的功能了,比如在运行时给类添加方法::
>>> class Klass(object): ... pass ... >>> obj = Klass() >>> def print_id(obj): ... print id(obj) ... >>> Klass.print_id = print_id # 等价于 Klass.__dict__['print_id'] = print_id >>> obj.print_id() # 等价于 print_id(obj) 16892848