面向对象编程————Object Oriented Programming,简称OOP,是一种程序设计思想。OOP吧对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行,为了简化程序设计,面向过程吧函数继续切分成为子函数,即把大块的函数通过切割成小块函数降低系统的复杂度。
而面向对象的程序设计吧计算机程序视为一组对象的集合,而每个对象都可以接受其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象,自定义的对象数据类型就是面向对象中的类(Class)的概念。
类和实例 面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,而实例是根据类创建出来的一个个具体的‘对象’,每个对象都拥有相同的方法,但各自的数据可能不同。 以Student类为例,在Python中,定义类是通过class关键字
1 2 class Student (object ): pass
clas后米娜紧接着是类名,即’Student’,类名通过是大写开头的单词,紧接着是(object),表示类是从哪个类继承下来的,object是所有类都会继承的类。 定义了好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 >>> jack = Student()>>> jack<__main__.Student object at 0x10f5e3110 > >>> Student<class '__main__.Student' > >>> 通过一个特殊的`__init__`方法,在创建实例的时候,就可以对某些属性初始化 ```Python class Student (object ): def __init__ (self, name, score ): self .name = name self .score = score
注意__innit__方法的第一个参数永远是self,表示创建的实例本身,因此在__init__方法内部,就可以把各个属性绑定到self,因为self就指向创建的实例本身。
有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量穿进去:
1 2 3 4 5 6 >>> class Student (object ):... ... def __init__ (self,name ):... self .name = name... >>> jack = Student('jack' )
数据封装 面向对象编程的一个重要特点就是数据封装。 类本身就拥有数据,要访问数据,就没有必要从外面的函数去访问,就直接用类内部定义访问数据的函数,这样就把“数据”给封装起来了,这些封装数据的函数是和类本身关联起来的,外面称之为类的方法,要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,出了self不用传递,其他参数正常传入。
1 2 3 4 5 6 7 8 9 >>> class Student (object ):... def __init__ (self,name ):... self .name = name... def getName (self ):... return self .name... >>> jack = Student('jack' )>>> jack.getName()'jack'
访问限制 在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样就可以隐藏内部的复杂逻辑。 为了内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。 需要注意的是,在Python中变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以不能用__name__这样的变量名。 双下划线开头的实例变量外部不能直接访问是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Student (object ): def __init__ (self,name ): self .__name = name def print_name (self ): print '%s' % (self .__name) jack = Student('jack' ) jack.print_name() print jack._Student__name
但不同版本的Python解释器可能会把__name改成不同的变量名。
继承和多态 在OOP程序设计中,定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或者超类(Base class、Super class)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Animal (object ): def run (self ): print 'Animal is running...' class Dog (Animal ): def run (self ): print 'Dog is running...' class Cat (Animal ): pass dog = Dog() dog.run() cat = Cat() cat.run()
判断一个变量是否是某个类型可以用isinstance()判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 >>> a = list () >>> b = Animal() >>> c = Dog() >>> isinstance (a,list )True >>> isinstance (b,list )False >>> isinstance (b,Animal)True >>> isinstance (c,Animal)True >>> isinstance (c,Dog)True
获取对象信息 使用type() 判断对象类型,使用type()函数
1 2 3 4 5 6 7 8 >>> type (123 )<type 'int' > >>> type ('str' )<type 'str' > >>> type (None )<type 'NoneType' > >>> type (abs )<type 'builtin_function_or_method' >
Python把每种type类型都定义好了常量,放在types模块里,使用之前,需要先导入:
1 2 3 4 5 6 7 8 9 >>> import types>>> type ('abc' )==types.StringTypeTrue >>> type (u'abc' )==types.UnicodeTypeTrue >>> type ([])==types.ListTypeTrue >>> type (str )==types.TypeTypeTrue
使用isinstance() 对于class的继承关系,可以用isinstance()函数判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 >>> class Animal (object ):... pass ... >>> class Dog (Animal ):... pass ... >>> class Husky (Dog ):... pass ... >>> a = Animal()>>> d = Dog()>>> h = Husky()>>> isinstance (h,Husky)True >>> isinstance (h,Dog)True >>> isinstance (h,Animal)True >>> isinstance (d,Husky)False >>> isinstance (d,Dog)True >>> isinstance (d,Animal)True >>> isinstance (d,(Animal,Husky))True
使用dir() 获取一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list
1 2 >>> dir ('ABC' )['__add__' , '__class__' , '__contains__' , '__delattr__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__getitem__' , '__getnewargs__' , '__getslice__' , '__gt__' , '__hash__' , '__init__' , '__le__' , '__len__' , '__lt__' , '__mod__' , '__mul__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__rmod__' , '__rmul__' , '__setattr__' , '__sizeof__' , '__str__' , '__subclasshook__' , '_formatter_field_name_split' , '_formatter_parser' , 'capitalize' , 'center' , 'count' , 'decode' , 'encode' , 'endswith' , 'expandtabs' , 'find' , 'format' , 'index' , 'isalnum' , 'isalpha' , 'isdigit' , 'islower' , 'isspace' , 'istitle' , 'isupper' , 'join' , 'ljust' , 'lower' , 'lstrip' , 'partition' , 'replace' , 'rfind' , 'rindex' , 'rjust' , 'rpartition' , 'rsplit' , 'rstrip' , 'split' , 'splitlines' , 'startswith' , 'strip' , 'swapcase' , 'title' , 'translate' , 'upper' , 'zfill' ]
类似__xxx__的属性和方法在Python中都有特殊的用途,比如上例的__len__方法返回长度。调用len()函数获取对象长度时,实际回去调用对象的__len__方法。 利用getattr()、setattr()、hasattr()函数还可以直接操作一个对象的状态:
1 2 3 4 5 6 7 8 9 10 >>> obj = MyObject()>>> hasattr (obj,'x' )True >>> hasattr (obj,'y' )False >>> setattr (obj,'y' ,19 )>>> hasattr (obj,'y' )True >>> getattr (obj,'y' )19
可以传入一个default参数,如果属性不存在,就返回默认值:
1 2 >>> getattr (obj, 'z' , 404 ) 404
动态绑定属性和方法 正常情况下,当我们定义了一个class,差创建一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 >>> class Student (object ):... pass ... >>> s = Student()>>> s.name = 'Zoe' >>> print s.nameZoe >>> def set_age (self,age ): ... self .age = age... >>> from types import MethodType>>> s.set_age = MethodType(set_age,s,Student) >>> s.set_age(22 )>>> s.age22
但是其他的实例该方法是不起作用的
1 2 3 4 5 >>> s1 = Student()>>> s2.set_age(21 )Traceback (most recent call last): File "<stdin>" , line 1 , in <module> NameError: name 's2' is not defined
可以给class绑定方法:
Student.set_age = MethodType(set_age,None,Student) s1.set_age(21) s1.age 21
s.age 22
1 2 3 4 5 6 7 8 9 10 11 12 ## 使用__slots__ 如果想要限制class的属性怎么办?比如只允许Student实例添加`name`和`age`属性。 为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的`__slots__`变量,来限制该class能添加的属性: ```Python >>> s = Student() >>> s.name = 'Zoe' >>> s.age = 22 >>> s.score = 99 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的
@property 在绑定属性时,我们直接把属性暴露出来,这样还不安全 为了限制属性的使用范围,可以使用@property装饰器把一个方法变成属性调用:
1 2 3 4 5 6 7 8 9 10 11 >>> class Student (object ):... @property ... def score (self ):... return self ._score... @score.setter... def score (self,value ):... if not isinstance (value,int ):... raise ValueError('score must be an integer!' )... if value < 0 or value > 100 :... raise ValueError('score must between 0 ~ 100' )... self ._score = value
@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 >>> s = Student() >>> s.score = 69 >>> s.score 69 >>> s.score = 101 Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 10, in score ValueError: score must between 0 ~ 100 >>> s.score = '99' Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 8, in score ValueError: score must be an integer!
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2014 - self._birth
上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。
多重继承 Python支持同时继承多个父类
1 class SubClass (SpuClass1,SpuClass2,...)
Mixin 在设计类的继承关系时,通常主线是单一继承下来的。为了混入额外的功能,可以通过多继承实现,这种设计称为Mixing。 为了更好地看出继承关系,可以在添加功能的类后面在Mixin,比如class Dog(Mammal,RunnableMixin,CarnivorousMixin) Python子弟啊的很多库使用了Mixi,举个例子,Python自带的TCPServer和UDPServer这两个网络服务,而要同时多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixin和ThreadingMixin提供。
定制类 之前,我们知道了一些形如__xxx__的变量或方法的特殊作用,如:__slots__,__len__()
Str 类的说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 ValueError: score must be an integer! >>> class Student (object ):... def __init__ (self,name ):... self .name = name... >>> print Student('Jack' )<__main__.Student object at 0x10e50d910 > >>> class Student (object ):... def __init__ (self,name ):... self .name = name... def __str__ (self ):... return 'Student object (name:%s)' % self .name... >>> print Student('Jack' )Student object (name:Jack) >>> s<__main__.Student object at 0x10e50d790 > >>> class Student (object ):... def __str__ (self ):... return 'Student object (name:%s)' % self .name... def __init__ (self,name ):... self .name = name... __repr__ = __str__... >>> s = Student("Jakc" )>>> sStudent object (name:Jakc)
iter _如果一个类要呗用于for...in循环,就必须实现一个__iter__()方法,该方法返回一个迭代对象,然后Python的for循环就会不断调用该迭代对象的next()方法拿到循环的下一个值,值得遇到StopIteration错误时退出循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 >>> class Fib (object ):... def __init__ (self ):... self .a,self .b = 0 ,1 ... def __iter__ (self ):... return self ... def next (self ):... self .a,self .b = self .b,self .a + self .b... if self .a > 100000 :... raise StopIteration()... return self .a... >>> for n in Fib():... print n... 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 10946 17711 28657 46368 75025
getitem 要想像list那样按照下标取元素,需要实现__getitem__()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 >>> class Fib (object ):... def __getitem__ (self,n ):... a,b = 1 ,1 ... for x in range (n):... a,b = b,a+b... return a... >>> f = Fib()>>> f[0 ]1 >>> f[1 ]1 >>> f[10 ]89 >>> f[20 ]10946 >>> f[30 ]1346269
但是list有个神奇的切片方法:
1 2 >>> range (100 )[5 :10 ][5 , 6 , 7 , 8 , 9 ]
对于Fib却报错。原因是__getitem__()传入的参数可能是一个int,也可能是一个切片对象slice,所以要做判断:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Fib (object ): def __getitem__ (self, n ): if isinstance (n, int ): a, b = 1 , 1 for x in range (n): a, b = b, a + b return a if isinstance (n, slice ): start = n.start stop = n.stop a, b = 1 , 1 L = [] for x in range (stop + 1 ): if x >= start: L.append(a) a, b = b, a + b return L
getattr 通过__getattr__()方法我们可以返回一个不存在的属性
1 2 3 4 5 6 7 8 >>> class SubClass (object ):... def __getattr__ (self,name ):... if name=='age' :... return lambda :22 ... >>> s = SubClass()>>> s.age()22
call 设置一个函数,通过实例本身调用
1 2 3 4 5 6 7 8 9 >>> class Student (object ):... def __init__ (self,name ):... self .name = name... def __call__ (self ):... print ('My name is %s' % self .name)... >>> s = Student('zoe' )>>> s()My name is zoe
通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。
1 2 3 4 5 6 7 8 9 10 >>> callable (max )True >>> callable ([1 ,2 ,4 ])False >>> callable (None )False >>> callable ('str' )False >>> callable (Student('zoe' ))True