python中的类与继承
Class
类的定义以及实例的建立
Python中,类通过 class 关键字定义。
例如最简单的一个类定义可以为:
class Person(object): pass
Python 的编程习惯,类名以大写字母开头,紧接着是(object),表示该类是从哪个类继承下来的。
例如上面的person类,我们可以创建很多实例:
zhangsan=Person()
lisi=Person()
实例的属性及方法
Python是动态语言,对每一个实例,都可以直接给他们的属性赋值,即使类中并无定义,例如,给xiaoming这个实例加上name、gender和birth属性:
xiaoming = Person() xiaoming.name = 'Xiao Ming' xiaoming.gender = 'Male' xiaoming.birth = '1990-1-1'
__init__()
如上所说,我们可以自由地给一个实例绑定各种属性,但是,现实世界中,一种类型的实例应该拥
有相同名字的属性。例如,Person类应该在创建的时候就拥有 name、gender 和 birth 属性。
所以在创建类时,我们可以使用__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')
属性的访问限制(私有属性)
我们可以给一个实例绑定很多属性,如果有些属性不希望被外部访问到怎么办?
Python对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线开头(__),该属性就无
法被外部访问。例如:
class Person(object): def __init__(self, name): self.name = name self._title = 'Mr' self.__job = 'Student' p1=Person("zhangsan") print(p1.name,'\n') print(p1._title,'\n') try: print(p1.__job) except: print("Error")
结果:
zhangsan
Mr
Error
可以看出只有__job无法被访问,但是也有可以访问的办法,__attribute
变为 _ClassName__attribute
,可以这样访问:
class MyClass: def __init__(self): self.__hidden = "I'm hidden" obj = MyClass() print(obj._MyClass__hidden) # 输出: I'm hidden
实例的方法
虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。除了可以定义实例的属性外,还可以定义实例的方法。
例如我们可以定义一个__name私有属性,同时创建一个get_name方法去访问__name:
class Person(object): def __init__(self, name): self.__name = name def get_name(self): return self.__name p2=Person("Bob") print(p2.get_name())
结果
Bob
get_name(self) 就是一个实例方法,它的第一个参数是self。__init__(self, name)其实也可看做是一个特殊的实例方法。
在实例方法内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度。
python中实例方法也是属性
我们在 class 中定义的实例方法其实也是属性,它实际上是一个函数对象,因为方法也是一个属性,所以,它也可以动态地添加到实例上,只是需要用 types.MethodType() 把一个函数变为一个方法:
import types def Pn_get_name(self): return str("This person's name is :"+self.name) class Person(object): def __init__(self, name): self.name = name p1=Person("Alice") p2=Person("Bob") p1.get_name=types.MethodType(Pn_get_name,p1) print(p1.get_name(),'\n') print(p2.get_name())
结果:
This person's name is :Alice Traceback (most recent call last): File "D:/pythonwork/practise/test.py", line 15, in <module> print(p2.get_name()) AttributeError: 'Person' object has no attribute 'get_name'
给一个实例动态添加方法并不常见,直接在class中定义要更直观
类的属性及方法
类是模板,而实例则是根据类创建的对象。绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象,如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个,也就是说,实例属性每个实例各自拥有,互相独立,而类属性有且只有一份。
定义类属性可以直接在 class 中定义:
class Person(object): address = 'Earth' def __init__(self, name): self.name = name
因为类属性是直接绑定在类上的,所以,访问类属性可以直接用类名访问,也可以使用实例访问,例如:
class Person(object): address = 'Earth' def __init__(self, name): self.name = name p1=Person("zhangsan") print(Person.address) print(p1.address)
结果:
Earth
Earth
由于Python是动态语言,类属性也是可以动态添加和修改的:
Person.address = 'China' print(Person.address) print(p1.address)
结果:
China
China
因为类属性只有一份,所以,当Person类的address改变时,所有实例访问到的类属性都改变了。
注意:如果在实例变量上修改类属性,则实际上是给实例绑定了一个与类属性同名的实例属性,而
访问时会优先访问实例属性:
p1.address="China" print(Person.address) print(p1.address)
结果:
Earth
China
类的方法
和属性类似,方法也分实例方法和类方法。
在class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。
要在class中定义类方法,我们通过标记一个 @classmethod,该方法将绑定到 Person 类上,而非
类的实例。类方法的第一个参数将传入类本身,通常将参数名命名为 cls,下面例子中
的 cls.count 实际上相当于 Person.count。
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())
结果:
0
1
类的继承
如果已经定义了Person类,需要定义新的Student和Teacher类时,可以直接从Person类继承,定义Student类时,只需要把额外的属性加上,例如score:
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
注意:
一定要用 super(Student, self).__init__(name, gender) 去初始化父类,否则,继承自 Person 的 Student 将没有 name 和 gender。
函数super(Student, self)将返回当前类继承的父类,即 Person ,然后调用__init__()方法,注意self参数已在super()中传入,在__init__()中将隐式传递,不需要写出(也不能写),另外下方会写出其他的写法。
初始化父类的多种写法
class AAA(object): def __init__(self, name): self.name = name def get_name(self): print(self.name) return self.name class BBB(AAA): def __init__(self, age): self.age = age # 方式1 super().__init__(name='BBB') # 方式2 super(BBB, self).__init__(name='BBBC') # 方式3 AAA.__init__(self, name='BBBD') def get_age(self): print(self.age) return self.age
其中方法3适用于多继承写法,python中如果两个父类的非继承自同一个“爷类” super()初始化似乎只会初始继承的第一个父类,因此这种情况,需要用第三种方式编写(也或许是我理解有问题,编码过程中具体问题具体分析)
class AAA(): def __init__(self, name): self.name = name def get_name(self): print(self.name) return self.name class BBB(): def __init__(self, name2): self.name2 = name2 def get_name2(self): print(self.name2) return self.name2 class CCC(AAA, BBB): def __init__(self, age): self.age = age AAA.__init__(self, name='CCC') BBB.__init__(self, name2='CCC2') def get_age(self): print(self.age) return self.age TEST = CCC(2) TEST.get_name() TEST.get_name2()
python中的多态
类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Person 派生出 Student和
Teacher ,并都写了一个 同名的whoAmI() 方法,当我们调用实例的whoAmI() 方法时,总是先查找
它自身的定义,如果没有定义,则顺着继承链向上查找,直到在某个父类中找到为止。
例如:
class Person(object): pass def Pstr(self): print("This is Person class method") class Student(Person): def __init__(self, name): self.name = name def Pstr(self): print("This is Student class method") class Teacher(Person): def __init__(self, name): self.name = name p1=Student("Alice") p2=Teacher("Tom") p1.Pstr() p2.Pstr()
结果:
This is Student class method This is Person class method
python中的多重继承
除了从一个父类继承外,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中 __slots__
由于Python是动态语言,任何实例在运行期都可以动态地添加属性。
如果要限制添加的属性,例如,Student类只允许添加 name、gender和score 这3个属性,就可以利用Python的一个特殊的__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__也能节省内存。
python中 __str__和__repr__对于print的改变
例如原本对于类中的实例直接打印:
class Student(object): def __init__(self, name): self.name = name p1=Student("Alice") print(p1)
结果:
<__main__.Student object at 0x000002672671EF98>
但是我们想让print实例的时候直接打印类名与实例名我们就可以使用__str__:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student: %s' % self.name p1=Student("Alice") print(p1)
结果:
Student: Alice
但是我们在命令交互行中直接输入变量p1却不是这样:
>>>p1
<Student object at 0x0000023C8F331A58>
这是因为 Python 定义了__str__()和__repr__()两种方法,__str__()用于显示给用户,而__repr__()
用于显示给开发人员。
同理我们可以定义类的__repr__()例如直接:
__repr__ = __str__
这样直接在命令交互中直接输入变量p1也是同样效果了
>>>p1
Student: Alice
python3中__cmp__方法已经不再使用
在python2中该方法可实现对于sorted()函数改变,不再赘述
python中 __len__对于len()的改变
如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。
要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:
class Students(object): def __init__(self, *args): self.names = args def __len__(self): return len(self.names) ss = Students('Bob', 'Alice', 'Tim') print (len(ss))
结果:
3
类的实例的数学运算
同以上特殊方法一样,我们可以利用
__add__对符号 + 进行改变
__sub__对符号 -进行改变
__mul__对符号 * 进行改变
__div__对符号 / 进行改变
例如:
我们创建一个分数的类:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q
我们想实现符号+直接运用在我们创建的实例上我们需要这样写代码:
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类实现了有理数(分数)类的数学运算,但是,如果要把结果转为 int 或 float 怎么办?
我们对于整数和浮点数可以直接使用int()和float()函数直接转换,但是我们肯定无法直接使用在我们所创建的类的实例上,因此我们需要使用__int__()和__float__()
例如__int__:
class Rational(object): def __init__(self, p, q): self.p = p self.q = q def __int__(self): return self.p//self.q #python3中//是整数结果
结果:
>>> print int(Rational(7, 2)) 3
python中 @property
对于Student类:
class Student(object): def __init__(self, name, score): self.name = name self.score = score
当我们想要修改一个 Student 的 scroe 属性时,可以这么写:
s = Student('Bob', 59) 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中,python支持高阶函数,在函数式编程中我们介绍了装饰器函数,可以用装饰器函数@property。
把 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会怎么样呢:
s = Student('Bob', 59) s.score = 60 print (s.score) try: s.score = 1000 except: print("Error")
结果:
60
Error
注意: 第一个score(self)是get方法,用@property装饰,第二个score(self, score)是set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。说明对 score 赋值实际调用的是 set方法。
如果没有定义set方法即@score.setter,就不能对“属性”赋值,这时,就可以创建一个只读“属性”。
python中 __call__
在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中,函数也是对象,对象和函数的区别并不显著。