类是建立实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;html
方法就是与实例绑定的函数,和普通函数不一样,方法能够直接访问实例的数据;python
经过在实例上调用方法,咱们就直接操做了对象内部的数据,但无需知道方法内部的实现细节。编程
和静态语言不一样,Python容许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不一样实例,但拥有的变量名称均可能不一样:网络
>>> bart = Student('Bart Simpson', 59)
>>> lisa = Student('Lisa Simpson', 87)
>>> bart.age = 8
>>> bart.age
8
>>> lisa.age
Traceback (most recent call last):
File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age'
对象中使用双下划线__开头设置私有变量,外部只能够经过_类名__变量访问,可是不建议这样作。函数
须要注意的是,在Python中,变量名相似__xxx__
的,也就是以双下划线开头,而且以双下划线结尾的,是特殊变量,特殊变量是能够直接访问的,不是private变量,因此,不能用__name__
、__score__
这样的变量名。ui
有些时候,你会看到以一个下划线开头的实例变量名,好比_name
,这样的实例变量外部是能够访问的,可是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我能够被访问,可是,请把我视为私有变量,不要随意访问”。spa
双下划线开头的实例变量是否是必定不能从外部访问呢?其实也不是。不能直接访问__name
是由于Python解释器对外把__name
变量改为了_Student__name
,因此,仍然能够经过_Student__name
来访问__name
变量:设计
>>> bart._Student__name 'Bart Simpson'
可是强烈建议你不要这么干,由于不一样版本的Python解释器可能会把__name
改为不一样的变量名。code
总的来讲就是,Python自己没有任何机制阻止你干坏事,一切全靠自觉。xml
最后注意下面的这种错误写法:
>>> bart = Student('Bart Simpson', 98) >>> bart.get_name() 'Bart Simpson' >>> bart.__name = 'New Name' # 设置__name变量! >>> bart.__name 'New Name'
表面上看,外部代码“成功”地设置了__name
变量,但实际上这个__name
变量和class内部的__name
变量不是一个变量!内部的__name
变量已经被Python解释器自动改为了_Student__name
,而外部代码给bart
新增了一个__name
变量。不信试试:
>>> bart.get_name() # get_name()内部返回self.__name 'Bart Simpson'
在OOP程序设计中,当咱们定义一个class的时候,能够从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
继承能够把父类的全部功能都直接拿过来,这样就没必要重零作起,子类只须要新增本身特有的方法,也能够把父类不适合的方法覆盖重写。
动态语言的鸭子类型特色决定了继承不像静态语言那样是必须的。
经过内置的一系列函数,咱们能够对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,咱们才会去获取对象信息。若是能够直接写:
sum = obj.x + obj.y
就不要写:
sum = getattr(obj, 'x') + getattr(obj, 'y')
一个正确的用法的例子以下:
def readImage(fp): if hasattr(fp, 'read'): return readData(fp) return None
假设咱们但愿从文件流fp中读取图像,咱们首先要判断该fp对象是否存在read方法,若是存在,则该对象是一个流,若是不存在,则没法读取。hasattr()
就派上了用场。
请注意,在Python这类动态语言中,根据鸭子类型,有read()
方法,不表明该fp对象就是一个文件流,它也多是网络流,也多是内存中的一个字节流,但只要read()
方法返回的是有效的图像数据,就不影响读取图像的功能。
因为Python是动态语言,根据类建立的实例能够任意绑定属性。
编写程序的时候,千万不要把实例属性和类属性使用相同的名字,由于相同名称的实例属性将屏蔽掉类属性,可是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
数据封装、继承和多态只是面向对象程序设计中最基础的3个概念。在Python中,面向对象还有不少高级特性,容许咱们写出很是强大的功能。
咱们会讨论多重继承、定制类、元类等概念。
可是,若是咱们想要限制实例的属性怎么办?好比,只容许对Student实例添加name
和age
属性。
为了达到限制的目的,Python容许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object): __slots__ = ('name', 'age') # 用tuple定义容许绑定的属性名称
而后,咱们试试:
>>> s = Student() # 建立新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # 绑定属性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
因为'score'
没有被放到__slots__
中,因此不能绑定score
属性,试图绑定score
将获得AttributeError
的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起做用,对继承的子类是不起做用的:
>>> class GraduateStudent(Student): ... pass ... >>> g = GraduateStudent() >>> g.score = 9999
除非在子类中也定义__slots__
,这样,子类实例容许定义的属性就是自身的__slots__
加上父类的__slots__
。
@property
普遍应用在类的定义中,可让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减小了出错的可能性。
class Student(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property def age(self): return 2015 - self._birth
经过多重继承,一个子类就能够同时得到多个父类的全部功能。
pass # 大类: class Mammal(Animal): pass class Bird(Animal): pass # 各类动物: class Dog(Mammal): pass class Bat(Mammal): pass class Parrot(Bird): pass class Ostrich(Bird): pass
在设计类的继承关系时,一般,主线都是单一继承下来的,例如,Ostrich
继承自Bird
。可是,若是须要“混入”额外的功能,经过多重继承就能够实现,好比,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计一般称之为MixIn。
为了更好地看出继承关系,咱们把Runnable
和Flyable
改成RunnableMixIn
和FlyableMixIn
。相似的,你还能够定义出肉食动物CarnivorousMixIn
和植食动物HerbivoresMixIn
,让某个动物同时拥有好几个MixIn:
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn): pass
Python的class容许定义许多定制方法,可让咱们很是方便地生成特定的类。
本节介绍的是最经常使用的几个定制方法,还有不少可定制的方法,请参考Python的官方文档。
Enum
能够把一组相关常量定义在一个class中,且class不可变,并且成员能够直接比较。
@unique
装饰器能够帮助咱们检查保证没有重复值。
访问这些枚举类型能够有若干种方法:
>>> day1 = Weekday.Mon >>> print(day1) Weekday.Mon >>> print(Weekday.Tue) Weekday.Tue >>> print(Weekday['Tue']) Weekday.Tue >>> print(Weekday.Tue.value) 2 >>> print(day1 == Weekday.Mon) True >>> print(day1 == Weekday.Tue) False >>> print(Weekday(1)) Weekday.Mon >>> print(day1 == Weekday(1)) True >>> Weekday(7) Traceback (most recent call last): ... ValueError: 7 is not a valid Weekday >>> for name, member in Weekday.__members__.items(): ... print(name, '=>', member) ... Sun => Weekday.Sun Mon => Weekday.Mon Tue => Weekday.Tue Wed => Weekday.Wed Thu => Weekday.Thu Fri => Weekday.Fri Sat => Weekday.Sat
可见,既能够用成员名称引用枚举常量,又能够直接根据value的值得到枚举常量。
metaclass是Python中很是具备魔术性的对象,它能够改变类建立时的行为。这种强大的功能使用起来务必当心。