面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象做为程序的基本单元,一个对象包含了数据和操做数据的函数。java
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数经过切割成小块函数来下降系统的复杂度。python
而面向对象的程序设计把计算机程序视为一组对象的集合,而每一个对象均可以接收其余对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。编程
在Python中,全部数据类型均可以视为对象,固然也能够自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。json
数据封装、继承和多态是面向对象的三大特色。服务器
在Python中,类经过 class 关键字定义。以 Person 为例,定义一个Person类以下:网络
class Person(object): pass
按照 Python 的编程习惯,类名以大写字母开头,紧接着是(object),表示该类是从哪一个类继承下来的。类的继承将在后面的章节讲解,如今咱们只须要简单地从object类继承。多线程
有了Person类的定义,就能够建立出具体的xiaoming、xiaohong等实例。建立实例使用 类名+(),相似函数调用的形式建立:ssh
xiaoming = Person()
xiaohong = Person()
注意:和其余语言的最大不一样是:建立实例不用new。函数式编程
虽然能够经过Person类建立出xiaoming、xiaohong等实例,可是这些实例看上除了地址不一样外,没有什么其余不一样。在现实世界中,区分xiaoming、xiaohong要依靠他们各自的名字、性别、生日等属性。函数
如何让每一个实例拥有各自不一样的属性?因为Python是动态语言,对每个实例,均可以直接给他们的属性赋值,例如,给xiaoming这个实例加上name、gender和birth属性:
xiaoming = Person() xiaoming.name = 'Xiao Ming' xiaoming.gender = 'Male' xiaoming.birth = '1990-1-1'
给xiaohong加上的属性不必定要和xiaoming相同:
xiaohong = Person() xiaohong.name = 'Xiao Hong' xiaohong.school = 'No. 1 High School' xiaohong.grade = 2
实例的属性能够像普通变量同样进行操做:
xiaohong.grade = xiaohong.grade + 1
例子:
请建立包含两个 Person 类的实例的 list,并给两个实例的 name赋值,而后按照 name 进行排序。
sorted() 是高阶函数,接受一个比较函数。
参考代码:
class Person(object): pass p1 = Person() p1.name = 'Bart' p2 = Person() p2.name = 'Adam' p3 = Person() p3.name = 'Lisa' L1 = [p1, p2, p3] L2 = sorted(L1, lambda p1, p2: cmp(p1.name, p2.name)) print L2[0].name print L2[1].name print L2[2].name
虽然咱们能够自由地给一个实例绑定各类属性,可是,现实世界中,一种类型的实例应该拥有相同名字的属性。例如,Person类应该在建立的时候就拥有 name、gender 和 birth 属性,怎么办?
在定义 Person 类时,能够为Person类添加一个特殊的__init__()方法,当建立实例时,__init__()方法被自动调用,咱们就能在此为每一个实例都统一加上如下属性:
class Person(object): def __init__(self, name, gender, birth): self.name = name self.gender = gender self.birth = birth
__init__() 方法的第一个参数必须是 self(也能够用别的名字,但建议使用习惯用法),后续参数则能够自由指定,和定义函数没有任何区别。(与其余语言的构造函数最大的不一样)
相应地,建立实例时,就必需要提供除 self 之外的参数:
xiaoming = Person('Xiao Ming', 'Male', '1991-1-1') xiaohong = Person('Xiao Hong', 'Female', '1992-2-2')
有了__init__()方法,每一个Person实例在建立时,都会有 name、gender 和 birth 这3个属性,而且,被赋予不一样的属性值,访问属性使用.操做符:
print xiaoming.name # 输出 'Xiao Ming' print xiaohong.birth # 输出 '1992-2-2'
要特别注意的是,初学者定义__init__()方法经常忘记了 self 参数:
>>> class Person(object): ... def __init__(name, gender, birth): ... pass ... >>> xiaoming = Person('Xiao Ming', 'Male', '1990-1-1') Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: __init__() takes exactly 3 arguments (4 given)
这会致使建立失败或运行不正常,由于第一个参数name被Python解释器传入了实例的引用,从而致使整个方法的调用参数位置所有没有对上。
例子:
请定义Person类的__init__方法,除了接受 name、gender 和 birth 外,还可接受任意关键字参数,并把他们都做为属性赋值给实例。
要定义关键字参数,使用 **kw;
除了能够直接使用self.name = 'xxx'设置一个属性外,还能够经过 setattr(self, 'name', 'xxx') 设置属性。
参考代码:
class Person(object): def __init__(self,name,gender,birth,**kw): self.name=name self.gender=gender self.birth=birth for k,v in kw.iteritems(): setattr(self,k,v) xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student') print xiaoming.name print xiaoming.job
注意:当函数的参数不肯定时,可使用*args 和**kw(全称**kwargs),*args 没有key值,**kw有key值。传形参时,**kw对应的参数里=左边的能够当成key,=右边的能够当成value。
咱们能够给一个实例绑定不少属性,若是有些属性不但愿被外部访问到怎么办?
Python对属性权限的控制是经过属性名来实现的,若是一个属性由双下划线开头(__),该属性就没法被外部访问。看例子:
class Person(object): def __init__(self, name): self.name = name self._title = 'Mr' self.__job = 'Student' p = Person('Bob') print p.name # => Bob print p._title # => Mr print p.__job # => Error Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Person' object has no attribute '__job'
可见,只有以双下划线开头的"__job"不能直接被外部访问。
可是,若是一个属性以"__xxx__"的形式定义,那它又能够被外部访问了,以"__xxx__"定义的属性在Python的类中被称为特殊属性,有不少预约义的特殊属性可使用,一般咱们不要把普通属性用"__xxx__"定义。
以单下划线开头的属性"_xxx"虽然也能够被外部访问,可是,按照习惯,他们不该该被外部访问。
关于 _xxx 和 __xxx ,通常来说,2个的区别是:
_xxx 能够在子类中使用。(至关于其余语言中的保护类型)
__xxx 不能够在子类中使用。(至关于其余语言中的私有类型)
而__xxx__做为特殊属性,在其余语言中通常没有。
类是模板,而实例则是根据类建立的对象。
绑定在一个实例上的属性不会影响其余实例,可是,类自己也是一个对象,若是在类上绑定一个属性,则全部实例均可以访问类的属性,而且,全部实例访问的类属性都是同一个!也就是说,实例属性每一个实例各自拥有,互相独立,而类属性有且只有一份。
定义类属性能够直接在 class 中定义:
class Person(object): address = 'Earth' def __init__(self, name): self.name = name
由于类属性是直接绑定在类上的,因此,访问类属性不须要建立实例,就能够直接访问:
print Person.address # => Earth
对一个实例调用类的属性也是能够访问的,全部实例均可以访问到它所属的类的属性:
p1 = Person('Bob') p2 = Person('Alice') print p1.address # => Earth print p2.address # => Earth
因为Python是动态语言,类属性也是能够动态添加和修改的:
Person.address = 'China' print p1.address # => 'China' print p2.address # => 'China'
由于类属性只有一份,因此,当Person类的address改变时,全部实例访问到的类属性都改变了。
例子:
请给 Person 类添加一个类属性 count,每建立一个实例,count 属性就加 1,这样就能够统计出一共建立了多少个 Person 的实例。
因为建立实例一定会调用__init__()方法,因此在这里修改类属性 count 很合适。
参考代码:
class Person(object): count = 0 def __init__(self, name): Person.count = Person.count + 1 self.name = name p1 = Person('Bob') print Person.count # => 1 p2 = Person('Alice') print Person.count # => 2 p3 = Person('Tim') print Person.count # => 3
修改类属性会致使全部实例访问到的类属性所有都受影响,可是,若是在实例变量上修改类属性会发生什么问题呢?
class Person(object): address = 'Earth' def __init__(self, name): self.name = name p1 = Person('Bob') p2 = Person('Alice') print 'Person.address = ' + Person.address p1.address = 'China' print 'p1.address = ' + p1.address print 'Person.address = ' + Person.address print 'p2.address = ' + p2.address
结果以下:
Person.address = Earth p1.address = China Person.address = Earth p2.address = Earth
咱们发现,在设置了 p1.address = 'China' 后,p1访问 address 确实变成了 'China',可是,Person.address和p2.address仍然是'Earch',怎么回事?
缘由是 p1.address = 'China'并无改变 Person 的 address,而是给 p1这个实例绑定了实例属性address ,对p1来讲,它有一个实例属性address(值是'China'),而它所属的类Person也有一个类属性address,因此:
访问 p1.address 时,优先查找实例属性,返回'China'。
访问 p2.address 时,p2没有实例属性address,可是有类属性address,所以返回'Earth'。
可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问。
当咱们把 p1 的 address 实例属性删除后,访问 p1.address 就又返回类属性的值 'Earth'了:
del p1.address print p1.address # => Earth
可见,千万不要在实例上修改类属性,它实际上并无修改类属性,而是给实例绑定了一个实例属性。
一个实例的私有属性就是以__(两个短横)开头的属性,没法被外部访问,那这些属性定义有什么用?
虽然私有属性没法从外部访问,可是,从类的内部是能够访问的。除了能够定义实例的属性外,还能够定义实例的方法。
实例的方法就是在类中定义的函数,它的第一个参数永远是 self,指向调用该方法的实例自己,其余参数和一个普通函数是彻底同样的:
class Person(object): def __init__(self, name): self.__name = name def get_name(self): return self.__name
get_name(self) 就是一个实例方法,它的第一个参数是self。__init__(self, name)其实也可看作是一个特殊的实例方法。
调用实例方法必须在实例上调用:
p1 = Person('Bob') print p1.get_name() # self不须要显式传入 # => Bob
在实例方法内部,能够访问全部实例属性,这样,若是外部须要访问私有属性,能够经过方法调用得到,这种数据封装的形式除了能保护内部数据一致性外,还能够简化外部调用的难度。
例子:
请给 Person 类增长一个私有属性 __score,表示分数,再增长一个实例方法 get_grade(),能根据 __score 的值分别返回 A-优秀, B-及格, C-不及格三档。
注意:get_grade()是实例方法,第一个参数为self。
class Person(object): def __init__(self, name, score): self.__name = name self.__score = score def get_grade(self): if self.__score >= 80: return 'A' if self.__score >= 60: return 'B' return 'C' p1 = Person('Bob', 90) p2 = Person('Alice', 65) p3 = Person('Tim', 48) print p1.get_grade() print p2.get_grade() print p3.get_grade()
咱们在 class 中定义的实例方法其实也是属性,它其实是一个函数对象:
class Person(object): def __init__(self, name, score): self.name = name self.score = score def get_grade(self): return 'A' p1 = Person('Bob', 90) print p1.get_grade # => <bound method Person.get_grade of <__main__.Person object at 0x109e58510>> print p1.get_grade() # => A
也就是说,p1.get_grade 返回的是一个函数对象,但这个函数是一个绑定到实例的函数,p1.get_grade() 才是方法调用。
由于方法也是一个属性,因此,它也能够动态地添加到实例上,只是须要用 types.MethodType() 把一个函数变为一个方法:(相似于java的动态代理)
import types def fn_get_grade(self): if self.score >= 80: return 'A' if self.score >= 60: return 'B' return 'C' class Person(object): def __init__(self, name, score): self.name = name self.score = score p1 = Person('Bob', 90) p1.get_grade = types.MethodType(fn_get_grade, p1, Person) print p1.get_grade() # => A p2 = Person('Alice', 65) print p2.get_grade() # ERROR: AttributeError: 'Person' object has no attribute 'get_grade' # 由于p2实例并无绑定get_grade
给一个实例动态添加方法并不常见,直接在class中定义要更直观。
因为属性能够是普通的值对象,如 str,int 等,也能够是方法,还能够是函数,你们看看下面代码,其中 p1.get_grade 是函数而不是方法:
class Person(object): def __init__(self, name, score): self.name = name self.score = score self.get_grade = lambda: 'A' p1 = Person('Bob', 90) print p1.get_grade print p1.get_grade()
p1.get_grade是属性,只不过这里的属性是一个函数对象;
p1.get_grade()是方法,前面的p1就是调用这个方法的对象,即实例,整句来讲就是实例方法。
和属性相似,方法也分实例方法和类方法。
在class中定义的所有是实例方法,实例方法第一个参数 self 是实例自己。
要在class中定义类方法,须要这么写:
class Person(object): count = 0 @classmethod def how_many(cls): return cls.count def __init__(self, name): self.name = name Person.count = Person.count + 1 print Person.how_many() p1 = Person('Bob') print Person.how_many()
经过标记一个 @classmethod,该方法将绑定到 Person 类上,而非类的实例。类方法的第一个参数将传入类自己,一般将参数名命名为 cls,上面的 cls.count 实际上至关于 Person.count。
由于是在类上调用,而非实例上调用,所以类方法没法得到任何实例变量,只能得到类的引用。
例子:
若是将类属性 count 改成私有属性__count,则外部没法读取__score,但能够经过一个类方法获取,请编写类方法得到__count值。
注意:类方法须要添加 @classmethod
类必定要有__init__()方法
class Person(object): __count = 0 @classmethod def how_many(cls): return cls.__count def __init__(self, name): self.name = name Person.__count = Person.__count + 1 print Person.how_many() p1 = Person('Bob') print Person.how_many()
老是从某个类中继承,若没有明显的类继承关系,那就是继承于object类:
class Myclass(object): pass
不要忘记在子类的构造函数中调用父类的构造函数:
def __init__(self,args): super(SubClass,self).__init__(args) pass
若是已经定义了Person类,须要定义新的Student和Teacher类时,能够直接从Person类继承:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender
定义Student类时,只须要把额外的属性加上,例如score:
class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score
必定要用 super(Student, self).__init__(name, gender) 去初始化父类,不然,继承自 Person 的 Student 将没有 name 和 gender。
函数super(Student, self)将返回当前类继承的父类,即 Person ,而后调用__init__()方法,注意self参数已在super()中传入,在__init__()中将隐式传递,不须要写出(也不能写)。
函数isinstance()能够判断一个变量的类型,既能够用在Python内置的数据类型如str、list、dict,也能够用在咱们自定义的类,它们本质上都是数据类型。
假设有以下的 Person、Student 和 Teacher 的定义及继承关系以下:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score class Teacher(Person): def __init__(self, name, gender, course): super(Teacher, self).__init__(name, gender) self.course = course p = Person('Tim', 'Male') s = Student('Bob', 'Male', 88) t = Teacher('Alice', 'Female', 'English')
当咱们拿到变量 p、s、t 时,可使用 isinstance 判断类型:
>>> isinstance(p, Person) True # p是Person类型 >>> isinstance(p, Student) False # p不是Student类型 >>> isinstance(p, Teacher) False # p不是Teacher类型
这说明在继承链上,一个父类的实例不能是子类类型,由于子类比父类多了一些属性和方法。
咱们再考察 s :
>>> isinstance(s, Person) True # s是Person类型 >>> isinstance(s, Student) True # s是Student类型 >>> isinstance(s, Teacher) False # s不是Teacher类型
s 是Student类型,不是Teacher类型,这很容易理解。可是,s 也是Person类型,由于Student继承自Person,虽然它比Person多了一些属性和方法,可是,把 s 当作Person的实例也是能够的。
这说明在一条继承链上,一个实例能够当作它自己的类型,也能够当作它父类的类型。
类具备继承关系,而且子类类型能够向上转型看作父类类型,若是咱们从 Person 派生出 Student和Teacher ,并都写了一个 whoAmI() 方法:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def whoAmI(self): return 'I am a Person, my name is %s' % self.name class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score def whoAmI(self): return 'I am a Student, my name is %s' % self.name class Teacher(Person): def __init__(self, name, gender, course): super(Teacher, self).__init__(name, gender) self.course = course def whoAmI(self): return 'I am a Teacher, my name is %s' % self.name
在一个函数中,若是咱们接收一个变量 x,则不管该 x 是 Person、Student仍是 Teacher,均可以正确打印出结果:
def who_am_i(x): print x.whoAmI() p = Person('Tim', 'Male') s = Student('Bob', 'Male', 88) t = Teacher('Alice', 'Female', 'English') who_am_i(p) who_am_i(s) who_am_i(t)
运行结果:
I am a Person, my name is Tim I am a Student, my name is Bob I am a Teacher, my name is Alice
这种行为称为多态。也就是说,方法调用将做用在 x 的实际类型上。s 是Student类型,它实际上拥有本身的 whoAmI()方法以及从 Person继承的 whoAmI方法,但调用 s.whoAmI()老是先查找它自身的定义,若是没有定义,则顺着继承链向上查找,直到在某个父类中找到为止。
因为Python是动态语言,因此,传递给函数 who_am_i(x)的参数 x 不必定是 Person 或 Person 的子类型。任何数据类型的实例均可以,只要它有一个whoAmI()的方法便可:
class Book(object): def whoAmI(self): return 'I am a book'
这是动态语言和静态语言(例如Java)最大的差异之一。动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就能够调用。
例子:
ython提供了open()函数来打开一个磁盘文件,并返回 File 对象。File对象有一个read()方法能够读取文件内容:
例如,从文件读取内容并解析为JSON结果:
import json f = open('/path/to/file.json', 'r') print json.load(f)
因为Python的动态特性,json.load()并不必定要从一个File对象读取内容。任何对象,只要有read()方法,就称为File-like Object,均可以传给json.load()。
请尝试编写一个File-like Object,把一个字符串 r'["Tim", "Bob", "Alice"]'包装成 File-like Object 并由 json.load() 解析。
只要为Students类加上 read()方法,就变成了一个File-like Object。
参考代码:
import json class Students(object): def read(self): return r'["Tim", "Bob", "Alice"]' s = Students() print json.load(s)
除了从一个父类继承外,Python容许从多个父类继承,称为多重继承。
多重继承的继承链就不是一棵树了,它像这样:
class A(object): def __init__(self, a): print 'init A...' self.a = a class B(A): def __init__(self, a): super(B, self).__init__(a) print 'init B...' class C(A): def __init__(self, a): super(C, self).__init__(a) print 'init C...' class D(B, C): def __init__(self, a): super(D, self).__init__(a) print 'init D...'
看下图:
像这样,D 同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的所有功能。多重继承经过 super()调用__init__()方法时,A 虽然被继承了两次,但__init__()只调用一次:
>>> d = D('d') init A... init C... init B... init D...
多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。
举个例子,Python的网络服务器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服务器运行模式有 多进程ForkingMixin 和 多线程ThreadingMixin两种。
要建立多进程模式的 TCPServer:
class MyTCPServer(TCPServer, ForkingMixin) pass
要建立多线程模式的 UDPServer:
class MyUDPServer(UDPServer, ThreadingMixin): pass
若是没有多重继承,要实现上述全部可能的组合须要 4x2=8 个子类。
拿到一个变量,除了用 isinstance() 判断它是不是某种类型的实例外,还有没有别的方法获取到更多的信息呢?
例如,已有定义:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender class Student(Person): def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score def whoAmI(self): return 'I am a Student, my name is %s' % self.name
首先能够用 type() 函数获取变量的类型,它返回一个 Type 对象:
>>> type(123) <type 'int'> >>> s = Student('Bob', 'Male', 88) >>> type(s) <class '__main__.Student'>
其次,能够用 dir() 函数获取变量的全部属性:
>>> dir(123) # 整数也有不少属性... ['__abs__', '__add__', '__and__', '__class__', '__cmp__', ...] >>> dir(s) ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'gender', 'name', 'score', 'whoAmI']
对于实例变量,dir()返回全部实例属性,包括`__class__`这类有特殊意义的属性。注意到方法`whoAmI`也是 s 的一个属性。
如何去掉`__xxx__`这类的特殊属性,只保留咱们本身定义的属性?回顾一下filter()函数的用法。
dir()返回的属性是字符串列表,若是已知一个属性名称,要获取或者设置对象的属性,就须要用 getattr() 和 setattr( )函数了:
>>> getattr(s, 'name') # 获取name属性 'Bob' >>> setattr(s, 'name', 'Adam') # 设置新的name属性 >>> s.name 'Adam' >>> getattr(s, 'age') # 获取age属性,可是属性不存在,报错: Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age' >>> getattr(s, 'age', 20) # 获取age属性,若是属性不存在,就返回默认值20: 20
例子:
对于Person类的定义:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender
但愿除了 name和gender 外,能够提供任意额外的关键字参数,并绑定到实例,请修改 Person 的 __init__()定 义,完成该功能。
传入**kw 便可传入任意数量的参数,并经过 setattr() 绑定属性。
参考代码:
class Person(object): def __init__(self, name, gender, **kw): self.name = name self.gender = gender for k, v in kw.iteritems(): setattr(self, k, v) p = Person('Bob', 'Male', age=18, course='Python') print p.age print p.course
若是要把一个类的实例变成 str,就须要实现特殊方法__str__():
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __str__(self): return '(Person: %s, %s)' % (self.name, self.gender)
如今,在交互式命令行下用 print 试试:
>>> p = Person('Bob', 'male') >>> print p (Person: Bob, male)
可是,若是直接敲变量 p:
>>> p
<main.Person object at 0x10c941890>
彷佛__str__() 不会被调用。
由于 Python 定义了__str__()和__repr__()两种方法,__str__()用于显示给用户,而__repr__()用于显示给开发人员。
有一个偷懒的定义__repr__的方法:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __str__(self): return '(Person: %s, %s)' % (self.name, self.gender) __repr__ = __str__
对 int、str 等内置数据类型排序时,Python的 sorted() 按照默认的比较函数 cmp 排序,可是,若是对一组 Student 类的实例排序时,就必须提供咱们本身的特殊方法 __cmp__():
class Student(object): def __init__(self, name, score): self.name = name self.score = score def __str__(self): return '(%s: %s)' % (self.name, self.score) __repr__ = __str__ def __cmp__(self, s): if self.name < s.name: return -1 elif self.name > s.name: return 1 else: return 0
上述 Student 类实现了__cmp__()方法,__cmp__用实例自身self和传入的实例 s 进行比较,若是 self 应该排在前面,就返回 -1,若是 s 应该排在前面,就返回1,若是二者至关,返回 0。
Student类实现了按name进行排序:
>>> L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 77)] >>> print sorted(L) [(Alice: 77), (Bob: 88), (Tim: 99)]
注意: 若是list不只仅包含 Student 类,则 __cmp__ 可能会报错:
L = [Student('Tim', 99), Student('Bob', 88), 100, 'Hello'] print sorted(L)
解决:能够写一个filter函数:
def isStudent(x): if isinstance(x,Student): return True else: return False
若是一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。要让 len() 函数工做正常,类必须提供一个特殊方法__len__(),它返回元素的个数。
例如,咱们写一个 Students 类,把名字传进去:
class Students(object): def __init__(self, *args): self.names = args def __len__(self): return len(self.names)
只要正确实现了__len__()方法,就能够用len()函数返回Students实例的“长度”:
>>> ss = Students('Bob', 'Alice', 'Tim') >>> print len(ss) 3
Python 提供的基本数据类型 int、float 能够作整数和浮点的四则运算以及乘方等运算可是,四则运算不局限于int和float,还能够是有理数、矩阵等。
要表示有理数,能够用一个Rational类来表示:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q
p、q 都是整数,表示有理数 p/q。
若是要让Rational进行+运算,须要正确实现__add__:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __add__(self, r): return Rational(self.p * r.q + self.q * r.p, self.q * r.q) def __str__(self): return '%s/%s' % (self.p, self.q) __repr__ = __str__
如今能够试试有理数加法:
>>> r1 = Rational(1, 3) >>> r2 = Rational(1, 2) >>> print r1 + r2 5/6
Rational类虽然能够作加法,但没法作减法、乘方和除法,请继续完善Rational类,实现四则运算。
若是运算结果是 6/8,在显示的时候须要归约到最简形式3/4。
参考代码:
def gcd(a, b): if b == 0: return a return gcd(b, a % b) class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __add__(self, r): return Rational(self.p * r.q + self.q * r.p, self.q * r.q) def __sub__(self, r): return Rational(self.p * r.q - self.q * r.p, self.q * r.q) def __mul__(self, r): return Rational(self.p * r.p, self.q * r.q) def __div__(self, r): return Rational(self.p * r.q, self.q * r.p) def __str__(self): g = gcd(self.p, self.q) return '%s/%s' % (self.p / g, self.q / g) __repr__ = __str__ r1 = Rational(1, 2) r2 = Rational(1, 4) print r1 + r2 print r1 - r2 print r1 * r2 print r1 / r2
Rational类实现了有理数运算,可是,若是要把结果转为 int 或 float 怎么办?
考察整数和浮点数的转换:
>>> int(12.34) 12 >>> float(12) 12.0
若是要把 Rational 转为 int,应该使用:
r = Rational(12, 5)
n = int(r)
要让int()函数正常工做,只须要实现特殊方法__int__():(注意是int不是init)
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __int__(self): return self.p // self.q
结果以下:
>>> print int(Rational(7, 2)) 3 >>> print int(Rational(1, 3)) 0
同理,要让float()函数正常工做,只须要实现特殊方法__float__()。
继续完善Rational,使之能够转型为float。
将self.p转型为float类型,再做除法就能够获得float:
float(self.p) / self.q
参考代码:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __int__(self): return self.p // self.q def __float__(self): return float(self.p) / self.q print float(Rational(7, 2)) print float(Rational(1, 3))
注意:python中/表示浮点数,//表示整除,另外还有导入from __future__ import division,要不会报错
考察 Student 类:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
当咱们想要修改一个 Student 的 scroe 属性时,能够这么写:
s = Student('Bob', 59) s.score = 60
可是也能够这么写:
s.score = 1000
显然,直接给属性赋值没法检查分数的有效性。
若是利用两个方法:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score def get_score(self): return self.__score def set_score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score
这样一来,s.set_score(1000) 就会报错。
这种使用 get/set 方法来封装对一个属性的访问在许多面向对象编程的语言中都很常见。
可是写 s.get_score() 和 s.set_score() 没有直接写 s.score 来得直接。
有没有一箭双鵰的方法?----有。
由于Python支持高阶函数,在函数式编程中咱们介绍了装饰器函数,能够用装饰器函数把 get/set 方法“装饰”成属性调用:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score
注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。
如今,就能够像使用属性同样设置score了:
>>> s = Student('Bob', 59) >>> s.score = 60 >>> print s.score 60 >>> s.score = 1000 Traceback (most recent call last): ... ValueError: invalid score
说明对 score 赋值实际调用的是 set方法。
例子:
若是没有定义set方法,就不能对“属性”赋值,这时,就能够建立一个只读“属性”。
请给Student类加一个grade属性,根据 score 计算 A(>=80)、B、C(<60)。
用 @property 修饰 grade 的 get 方法便可实现只读属性。
参考代码:
class Student(object): def __init__(self, name, score): self.name = name self.__score = score @property def score(self): return self.__score @score.setter def score(self, score): if score < 0 or score > 100: raise ValueError('invalid score') self.__score = score @property def grade(self): if self.score < 60: return 'C' if self.score < 80: return 'B' return 'A' s = Student('Bob', 59) print s.grade s.score = 60 print s.grade s.score = 99 print s.grade
注意:
@property---这是关键字,固定格式,能让方法当“属性”用。
@score.setter---前面的"score"是@property紧跟的下面定义的那个方法的名字,"setter"是关键字,这种“@+方法名字+点+setter”是个固定格式与@property搭配使用。
因为Python是动态语言,任何实例在运行期均可以动态地添加属性。
若是要限制添加的属性,例如,Student类只容许添加 name、gender和score 这3个属性,就能够利用Python的一个特殊的__slots__来实现。
顾名思义,__slots__是指一个类容许的属性列表:
class Student(object): __slots__ = ('name', 'gender', 'score') def __init__(self, name, gender, score): self.name = name self.gender = gender self.score = score
如今,对实例进行操做:
>>> s = Student('Bob', 'male', 59) >>> s.name = 'Tim' # OK >>> s.score = 99 # OK >>> s.grade = 'A' Traceback (most recent call last): ... AttributeError: 'Student' object has no attribute 'grade'
__slots__的目的是限制当前类所能拥有的属性,若是不须要添加任意动态的属性,使用__slots__也能节省内存。
例子:
假设Person类经过__slots__定义了name和gender,请在派生类Student中经过__slots__继续添加score的定义,使Student类能够实现name、gender和score 3个属性。
Student类的__slots__只须要包含Person类不包含的score属性便可。
参考代码:
class Person(object): __slots__ = ('name', 'gender') def __init__(self, name, gender): self.name = name self.gender = gender class Student(Person): __slots__ = ('score',) def __init__(self, name, gender, score): super(Student, self).__init__(name, gender) self.score = score s = Student('Bob', 'male', 59) s.name = 'Tim' s.score = 99 print s.score
在Python中,函数实际上是一个对象:
>>> f = abs >>> f.__name__ 'abs' >>> f(-123) 123
因为 f 能够被调用,因此,f 被称为可调用对象。
全部的函数都是可调用对象。
一个类实例也能够变成一个可调用对象,只须要实现一个特殊方法__call__()。
咱们把 Person 类变成一个可调用对象:
class Person(object): def __init__(self, name, gender): self.name = name self.gender = gender def __call__(self, friend): print 'My name is %s...' % self.name print 'My friend is %s...' % friend
如今能够对 Person 实例直接调用:
>>> p = Person('Bob', 'male') >>> p('Tim') My name is Bob... My friend is Tim...
单看 p('Tim') 你没法肯定 p 是一个函数仍是一个类实例,因此,在Python中,函数也是对象,对象和函数的区别并不显著。
本篇博客参考:
慕课网课程:python进阶
廖雪峰老师官网:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000