Python——面向对象
1. Properties
处于安全考虑,通常在对实例属性进行读写时要进行一定的预判和处理,比如参数的判断、净化等,这些方法通常对应于getXXX、setXXX这样的函数,Python提供了一种 “property” 机制,便于实现这样的操作。
首先,我们来看一个只读的 property:
>>> class Rectangle(object): ... def __init__(self, width, height): ... self.width = width ... self.height = height ... @property ... def area(self): ... return self.width * self.height ...
property 通过装饰器 @property 声明,@property 所修饰的函数将成为实例的一个属性:
>>> r = Rectangle(10, 10) >>> print r.area 100
上面的例子中,只读性质 area 是动态生成的,但是该例并没有完全展示 property 的优势。对于那些可写的属性,往往需要经过额外的验证,才允许对属性的值进行某些修改,这里我们不妨假定,我们只允许长方形实例的宽和高都是整数:
class Rectangle(object): __slots__ = '_width', '_height' @property def width(self): return self._width @width.setter def width(self, value): # 矩形的宽必须是整数 if not isinstance(value, int): raise ValueError, 'Width must be an integer.' self._width = value @property def height(self): return self._height @height.setter def height(self, value): # 矩形的高必须是整数 if not isinstance(value, int): raise ValueError, 'Height must be an integer.' self._height = value @property def area(self): # 矩形的面积 return self._height * self._width if __name__ == "__main__": r = Rectangle() r.width = 100 r.height = 100.1 # 程序运行到这会终止,将抛出异常 print r.areaself._width
这个例子比较全面地展示了Python中 property 机制,@property 装饰的方法在使用时完全和属性相同,但是property将公共的安全接口暴露给用户,避免了直接对属性进行操作时带来的安全隐患。property 机制可以在读写实例的属性时进行额外操作。
对于可读写的 property,首先使用 @property 定义只读的部分 x,此时Python会自动生成一个 @x.setter 用于创建 x 的可写部分。比如该例子中的性质 width,当设置了 width 的只读部分后,自动生成一个 @width.setter装饰器,用来修饰 width 的可写部分。
这个例子使用 Python 的 property 机制创建了一个宽高只能是整数的长方形类 Rectangle,规定 Rectangle 的实例具有整数类型的可读写属性 width 和 height,同时还有只读性质 area。
上面的例子中,class body 中的 __slots__ 属性又有什么作用和含义呢?
2. __slots__
Python支持动态地为实例、类型绑定属性,这样绑定的属性都会存放在对象的私有属性 __dict__ 中,例如:
>>> class E(object): ... pass ... >>> e = E() >>> e.__dict__ {}
可以看到此时 e 还没有动态绑定的属性,接下来:
>>> e.name = 'slots_example' >>> e.__dict__ {'name': 'slots_example'}
通过为 e 动态绑定属性 name,可以看到已经将该属性对应的键值对添加到 e.__dict__ 属性中。
类属性 __slots__ 就是用来限制我们能够给实例动态绑定的属性数量和名称的,以上面的类型 E 和 实例 e 为例,如果 E 中定义了类属性 __slots__,那么 e 将不再具有 __dict__ 属性。同时,只能给 e 动态绑定那些名称出现在 __slots__ 中的属性。
__slots__一定得是类属性,类型上通常是一个元组,里面的每个元素都是字符串。
例:
>>> class Rectangle(object): ... __slots__ = 'width', 'height' # 将 __slots__ 设置为类属性 ... >>> r = Rectangle() >>> r.name = 'test' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Rectangle' object has no attribute 'name' >>> r.__dict__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Rectangle' object has no attribute '__dict__'
可见此时我们 1. 无法为实例 r 动态绑定除了__slots__中列出的属性,2.实例 r 没有 __dict__属性,这是 __slots__ 属性最主要的功能。当某个类型可能会被实例化很多很多次时,如果每个实例都有一些动态绑定的属性,那么存储上的额外开销是巨大的,此时可以通过 __slots__ 属性,限制这些实例中能够动态添加的额外属性。
3. 闭包(closure)
事实上,闭包并不能算作 Python 中的面向对象机制,应当看做是一种面向对象之外的手段。
Python 支持函数式编程,可以在一个函数中定义新的函数,例如装饰器就是这样定义的。此时内部额外定义的函数被称为嵌套函数(nested function),外部的函数被称为外函数(outer function),嵌套函数可以访问外函数的局部变量和参数,这种来自外函数的变量和参数称为自由变量(free variable)。
闭包是指嵌套函数和它可以访问的自由变量。
例如:
>>> def f(a,b): ... def g(): ... return a + b ... return g ... >>> f(2,3) <function g at 0x0000000002072358> >>> f(2,3)() 5
嵌套函数 g() 可以访问 外函数 f() 的局部变量和参数,g() 和 f() 提供的自由变量共同构成了一个闭包。
闭包在 Python 中是一个包含自由变量的函数对象,自由变量信息被保存在这个函数对象的 __closure__ 属性中。__closure__属性是一个元组,其中的每一个元素都是一个Python cell对象。
还是上面的例子,假设我们将 f(2, 3) 返回的函数对象绑定到一个引用:
>>> g1 = f(2, 3) >>> g1 <function g at 0x00000000020727B8> >>> g1.__closure__ (<cell at 0x000000000219BD98: int object at 0x0000000001F56290>, <cell at 0x000000000219BCA8: int object at 0x0000000001F56278>)
如何从这样一个 cell 对象中查看具体的自由变量的信息呢?访问cell对象的cell_contents属性即可。
>>> g1.__closure__[0].cell_contents 2
闭包可以创建一些构造器,进一步创建一些需要在初始化阶段就已经动态确定某些环境信息的可调用对象,这与面向对编程中的类-->实例的模式是不同的。