Python学习笔记–5.面向对象编程
MrZigZag @ Peking University
2015-07-17
1.定义
通过class关键字定义类,父类(object)。
class MyClass(object):
    pass
没有合适的继承类时可以使用object类,这是所有的类都会继承的类。
自由绑定,Python是动态语言,可以在运行过程中实时地给实例instance绑定新的属性,同时还可以绑定新的方法。
>>> ins1 = MyClass()
>>> ins1.name = 'Test'
def set_age(self, age):
    self.age = age
from types inport MethodType
s = Student()
s.set_age = MethodType(set_age, s, Student)
方法,类的初始化方法是__init__(self, args),任何方法第一个参数都是self,但是使用时不需要传进去。
2.访问限制
属性名称加上__表示private,__prop会被解释器解释成_Classname__prop,要访问__prop可以写__Classname__prop。
双下划线开头双下划线结尾的属性属于特殊变量,可以访问,一般不能使用这个命名方法。
单下划线开头的变量可以访问,但是一般被视为private变量。
3.继承和多态
继承之后,子类的方法会覆盖父类的方法。
多态保证了只需要知道父类,并不需要确切的子类就可以放心的使用父类中的方法。因而调用方只管调用不管细节。符合“开闭原则”。对扩展开放,对修改封闭。
class Animal(object):
    def run(self):
        print 'Animal is running!'
class Dog(Animal):
    def run(self):
        print 'Dog is runnning!'
def run_twice(animal):
    animal.run()
    animal.run()
>>> run_twice(Animal())
Animal is running!
Animal is running!
>>> run_twice(Dog())
Dog is running!
Dog is running!
根据多态的性质,任何时候最好都要使用继承,如果没有合适的父类则直接继承object。
4.获取对象信息
拿到一个对象的引用时,需要确定对象的类型和包含的方法。可以使用多种方法。
使用type(),基本类型都可以使用type()函数判断。
>>> type(1)
<type 'int'>
>>> type('hello')
<type 'str'>
>>> type(None)
<type 'NoneType'>
>>> type(abs)
<type 'builtin_function or method'>
>>> type(Animal())
<type '__main__.Animal'>
type()函数返回type类型,Python把每种type类型都定义好了常量,放在types模块中。
>>> import types
>>> type('abc') == types.StringType
True
>>> type(u'abc') == types.UnicodeType
True
>>> type([]) == types.ListType
True
>>> type(int) = types.TypeType
不同类型都有对应的type,所有类型本身的类型就是一种TypeType。
使用isinstance(),判断class的类型使用type()并不是很方便,直接使用isinstance()函数。
>>> a = Ainimal()
>>> d = Dog()
>>> isinstance(d, Dog)
True
>>> isinstance(d, Animal)
True
可见isinstance()对继承关系也是有用的。当然,isinstance()还可以判断变量是某些类型中之一。
>>> isinstance('a', str)
True
>>> isinstance('a', (str, unicode))
True
使用dir()可以获得一个对象的所有属性和方法。返回一个包含字符串的list。
同时配合getattr()、setattr()、hassttr()可以直接操作一个对象的状态。
>>> hasattr(obj, 'x')
True
>>> hasattr(obj, 'y')
False
>>> setattr(obj, 'y', 10)
>>> getattr(obj, 'y')
10
>>> obj.y
10
>>> getattr(obj, 'z', 404)
404
一般正确的用法:
def readImage(fp):
    if hasattr(fp, 'read'):
        return readData(fp)
    return None
5.slots
基于Python的动态语言特性,创建class实例之后我们还可以在运行过程中给该实例绑定新的属性和方法,但是新绑定的属性和方法对其他实例是不起作用的。为了给所有实例都绑定方法,可以直接给class绑定新的方法。
def set_score(self, score):
    self.score = score;
Student.set_score = Method_Type(set_score, None, Student)
使用slots可以限制class的属性。Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性。__slots__仅仅对当前class有效,对继承的子类是不起作用的。
class Student(object):
    __slots__ = ('name', 'age') # Use tuple to describe __slots__
如果在子类中定义__slots__,那么子类允许定义的属性就是自身的__slots__加上父类的__slots__。
6.@property
绑定属性时,为了方便起见,有时候会将属性直接暴露出去,但是这样就没法检查参数,导致属性可以随便改。因此,我们需要限制属性的范围。
class Student(object):
    def get_score(self):
        return self._score
    def set_score(self, val):
        if not isinstance(val, int):
            raise ValueError('score must be an integer')
        if val < 0 or val > 100:
            raise ValueError('score must between 0 - 100')
        self._score = val
但是这样做就没法将属性直接暴露出去方便读写了。因此我们可以用装饰器decorator给函数动态加上功能。Python内置的@property装饰器就是负责把一个方法变成属性调用。把一个getter方法变成属性只需加上@property,此时@property本身又创建了另一个装饰器@property.setter,负责把setter方法编程属性直接赋值。
class Student(object):
    @property
    def score(self):
        return self._score
    @property.setter
    def score(self, val):
        if not isinstance(val, int):
            raise ValueError('score must be an integer')
        if val < 0 or val > 100:
            raise ValueError('score must between 0-100')
        self._score = val
这样可以把score当做public的属性来用。
>>> s = Student()
>>> s.score = 100 # equals s.score(100)
>>> s.score # equals s.score()
100
当只使用@property而不使用@property.setter时相关的变量就是只读属性。