6.3类的封装性
类的3大特性:封装性、继承性、多态性。
在类的定义中,变量和方法若在类外可以访问,则表示公有成员;若在类外无法访问,则表示私有成员。
当变量和方法的命名格式为__xxx,则表示为私有成员。将类中的变量和部分方法隐藏起来,只给外部提供调用接口,称为类的封装性。
#程序6-13
class Person():
#this is a test
count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('__init__')
self.name = name #实例变量
self.age = age #实例变量
Person.count += 1
def __del__(self):
print('__del__')
Person.count -= 1
def print_info(self):
print('name:'+ self.name,', age:'+ str(self.age),end = ' , ')
Person.chinese_zodiac(self.age)
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.count))
@staticmethod
def chinese_zodiac(age): #静态方法:生肖
a = age%12
if a==0:
print('生肖:猪')
elif a==1:
print('生肖:狗')
elif a==2:
print('生肖:鸡')
elif a==3:
print('生肖:猴')
elif a==4:
print('生肖:羊')
elif a==5:
print('生肖:马')
elif a==6:
print('生肖:蛇')
elif a==7:
print('生肖:龙')
elif a==8:
print('生肖:兔')
elif a==9:
print('生肖:虎')
elif a==10:
print('生肖:牛')
elif a==11:
print('生肖:鼠')
p1 = Person()
p1.print_info()
p2 = Person('zhangsan',18)
p2.print_info()
print(p2.__dict__)
p2.name = 'lisi'
p2.age = 30
p2.print_info()
print(p2.__dict__)
运行结果:
__init__
name:none , age:0 , 生肖:猪
__init__
name:zhangsan , age:18 , 生肖:蛇
{'name': 'zhangsan', 'age': 18}
name:lisi , age:30 , 生肖:蛇
{'name': 'lisi', 'age': 30}
__del__
__del__
类定义中的变量和方法只要格式不是__xxx,都表示公有成员。公有成员可以在类外访问,如上述代码中的p2.name = 'lisi'、p2.age = 30。
#程序6-14
class Person():
#this is a test
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age),end = ' , ')
Person.chinese_zodiac(self.__age)
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
@staticmethod
def chinese_zodiac(age): #静态方法:生肖
a = age%12
if a==0:
print('生肖:猪')
elif a==1:
print('生肖:狗')
elif a==2:
print('生肖:鸡')
elif a==3:
print('生肖:猴')
elif a==4:
print('生肖:羊')
elif a==5:
print('生肖:马')
elif a==6:
print('生肖:蛇')
elif a==7:
print('生肖:龙')
elif a==8:
print('生肖:兔')
elif a==9:
print('生肖:虎')
elif a==10:
print('生肖:牛')
elif a==11:
print('生肖:鼠')
p1 = Person()
p1.print_info()
p2 = Person('zhangsan',18)
p2.print_info()
print(p2.__dict__)
p2.name = 'lisi'
p2.age = 30
p2.print_info()
print(p2.__dict__)
运行结果:
__init__
name:none , age:0 , 生肖:猪
__init__
name:zhangsan , age:18 , 生肖:蛇
{'_Person__name': 'zhangsan', '_Person__age': 18}
name:zhangsan , age:18 , 生肖:蛇
{'_Person__name': 'zhangsan', '_Person__age': 18, 'name': 'lisi', 'age': 30}
__del__
__del__
将类定义中的变量都修改为私有成员。当第一次执行print(p2.__dict__)后,发现类中的私有变量名全部被修饰过,name变成了_Person__name,age变成了_Person__age,这也是为什么在类外无法通过原变量名来访问的原因。
当执行p2.name = 'lisi'、p2.age = 30时,由于类中未定义变量name、age,因此点运算符会构造新的变量name、age。因此在第二次执行print(p2.__dict__)后,其打印结果中多出了变量name和age。
总结:私有成员无法访问的本质原因是原变量名被修饰过。
那么,通过修饰过的变量就可以访问类的私有变量。注:不建议这种做法,因为会破坏类的封装性。
#程序6-15
class Person():
#this is a test
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age),end = ' , ')
Person.chinese_zodiac(self.__age)
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
@staticmethod
def chinese_zodiac(age): #静态方法:生肖
a = age%12
if a==0:
print('生肖:猪')
elif a==1:
print('生肖:狗')
elif a==2:
print('生肖:鸡')
elif a==3:
print('生肖:猴')
elif a==4:
print('生肖:羊')
elif a==5:
print('生肖:马')
elif a==6:
print('生肖:蛇')
elif a==7:
print('生肖:龙')
elif a==8:
print('生肖:兔')
elif a==9:
print('生肖:虎')
elif a==10:
print('生肖:牛')
elif a==11:
print('生肖:鼠')
p1 = Person()
p1.print_info()
p2 = Person('zhangsan',18)
p2.print_info()
print(p2.__dict__)
p2._Person__name = 'lisi' #不建议这么访问
p2._Person__age = 30 #不建议这么访问
p2.print_info()
print(p2.__dict__)
运行结果:
__init__
name:none , age:0 , 生肖:猪
__init__
name:zhangsan , age:18 , 生肖:蛇
{'_Person__name': 'zhangsan', '_Person__age': 18}
name:lisi , age:30 , 生肖:蛇
{'_Person__name': 'lisi', '_Person__age': 30}
__del__
__del__
总结:程序6-14是一个比较完善的代码,类中定义了实例变量、类变量、实例方法、类方法、静态方法、专有方法(构造函数和析构函数)。
6.4类的继承性
从类A中派生出类B(或类B继承于类A),则类A称为基类(或父类),类B称为派生类(或子类)。
6.4.1单继承-构造函数和析构函数
一个派生类继承于一个基类,称为单继承。
#程序6-16
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
pass
s1 = Student('zhangsan',18)
s1.print_info()
s1.get_count()
print(s1.__dict__)
运行结果:
Person:__init__
name:zhangsan , age:18
count :1
{'_Person__name': 'zhangsan', '_Person__age': 18}
Person:__del__
代码中,Person类为基类,Student类为派生类。在派生类的定义中,通过class Student(Person)来指定Student的基类是Person。
总结:
(1)派生类可以继承基类的实例变量;但不继承类变量。
(2)派生类可以调用基类中的所有方法。
(3)若派生类中无构造函数和析构函数,则实例化派生类对象时会调用基类的构造函数,删除派生类对象时会调用基类的析构函数。
#程序6-17
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('Person:print_info')
# print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
s1 = Student('合肥一中',85)
s1.print_info()
print(s1.__dict__)
运行结果:
Student:__init__
Person:print_info
{'_Student__school': '合肥一中', '_Student__score': 85}
Student:__del__
总结:
(1)当派生类中有构造函数和析构函数时,则构造和删除对象只调用派生类中定义的构造函数和析构函数。
(2)当派生类不调用基类的构造函数时,就无法继承基类的实例变量;但可以访问基类中的方法。
若想继承基类的实例变量,则需要在派生类的构造函数中调用基类的构造函数,代码如下。
#程序6-18
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
s1 = Student('zhangsan',18,'合肥一中',85)
print(s1.__dict__)
运行结果:
Student:__init__
Person:__init__
{'_Person__name': 'zhangsan', '_Person__age': 18, '_Student__school': '合肥一中', '_Student__score': 85}
Student:__del__
当在派生类构造函数中调用基类的构造函数(个人建议使用方法1)。
方法1:Person.__init__(self,name,age),其中必须要加self,它代表的是Student对象。
方法2:使用关键字super,即super(Student,self).__init__(name, age)。
从print(s1.__dict__)的打印结果中,可以看到派生类Student继承了基类Person的实例变量name和age。
问题:在程序6-18中,调用了基类和派生类的构造函数,但是只调用了派生类的析构函数。若在派生类的析构函数中不调用基类的析构函数,会对代码产生影响吗?
#程序6-19
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person.__init__(self,name,age)
# super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
s1 = Student('zhangsan',18,'合肥一中',85)
print(s1.__dict__)
s1.get_count()
del s1
Person.get_count()
运行结果:
Student:__init__
Person:__init__
{'_Person__name': 'zhangsan', '_Person__age': 18, '_Student__school': '合肥一中', '_Student__score': 85}
count :1
Student:__del__
count :1
我们发现当删除了派生类Student的对象时,调用基类Person中的get_count()方法,扔打印1。因此,在派生类的析构函数中也要调用基类的析构函数。
#程序6-20
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
Person.__del__(self)
s1 = Student('zhangsan',18,'合肥一中',85)
print(s1.__dict__)
s1.get_count()
del s1
Person.get_count()
运行结果:
Student:__init__
Person:__init__
{'_Person__name': 'zhangsan', '_Person__age': 18, '_Student__school': '合肥一中', '_Student__score': 85}
count :1
Student:__del__
Person:__del__
count :0
程序6-20终于符合我们预期的效果。
6.4.2单继承-方法的重写
当派生类方法和基类方法重名时(即使参数列表不同),派生类对象会调用在派生类中定义的方法,我们称为方法的重写。
#程序6-21
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
Person.__del__(self)
def print_info(self):
print('shcool:'+ self.__school,', score:'+ str(self.__score))
s1 = Student('zhangsan',18,'合肥一中',85)
s1.print_info()
运行结果:
Student:__init__
Person:__init__
shcool:合肥一中 , score:85
Student:__del__
Person:__del__
由于在派生类的方法中无法访问基类中继承的实例变量,因此在派生类中定义的def print_info(self)无法访问name和age。
在基类和派生类中定义的def print_info(self)都无法满足我们的要求,那么可以将两者结合起来,代码如下。
#程序6-22
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
Person.__del__(self)
def print_info(self):
#super(Student,self).print_info()
Person.print_info(self)
print('shcool:'+ self.__school,', score:'+ str(self.__score))
s1 = Student('zhangsan',18,'合肥一中',85)
s1.print_info()
运行结果:
Student:__init__
Person:__init__
name:zhangsan , age:18
shcool:合肥一中 , score:85
Student:__del__
Person:__del__
程序6-22满足了我们的要求。
总结6.4.1和6.4.2:
(1)在单继承中,派生类对象可以访问基类的实例变量(公有成员)和基类的方法。
(2)单继承中,派生类方法和基类方法重名(包括构造函数、析构函数),会优先调用派生类中定义的方法。若要在派生类方法中访问基类的方法,可以使用基类名或关键字super来访问。
(3)单继承中,若派生类构造函数未调用基类的构造函数,则派生类对象不会继承基类的实例变量,但派生类对象仍可以访问基类的方法。
6.4.3多继承
一个派生类继承于多个基类,称为多继承。
#程序6-23
class Person1():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person1:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person1.__count += 1
def __del__(self):
print('Person1:__del__')
Person1.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Person2():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person2:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person2.__count += 1
def __del__(self):
print('Person2:__del__')
Person2.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Person3():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person3:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person3.__count += 1
def __del__(self):
print('Person3:__del__')
Person3.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person1,Person2,Person3):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person1.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
Person1.__del__(self)
def print_info(self):
#super(Student,self).print_info()
Person1.print_info(self)
print('shcool:'+ self.__school,', score:'+ str(self.__score))
s1 = Student('zhangsan',18,'合肥一中',85)
s1.print_info()
运行结果:
Student:__init__
Person1:__init__
name:zhangsan , age:18
shcool:合肥一中 , score:85
Student:__del__
Person1:__del_
当派生类方法重写基类方法时,派生类对象会优先调用派生类中的方法。
问题:当派生类中无重名的方法,而多个基类中有重名的方法,那么,派生类对象会优先调用谁呢?
答案:会从基类列表中,从左向右查询,找到需要调用的方法。
#程序6-24
class Person1():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person1:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person1.__count += 1
def __del__(self):
print('Person1:__del__')
Person1.__count -= 1
# def print_info(self):
# print('Person1')
# print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Person2():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person2:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person2.__count += 1
def __del__(self):
print('Person2:__del__')
Person2.__count -= 1
def print_info(self):
print('Person2')
# print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Person3():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person3:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person3.__count += 1
def __del__(self):
print('Person3:__del__')
Person3.__count -= 1
def print_info(self):
print('Person3')
# print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person1,Person2,Person3):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person1.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
Person1.__del__(self)
s1 = Student('zhangsan',18,'合肥一中',85)
s1.print_info()
运行结果:
Student:__init__
Person1:__init__
Person2
Student:__del__
Person1:__del__
在Python之禅中,告诉我们“简洁胜于复杂”。因此,尽量不要使用多继承。
6.4.4组合
当表示“包含”关系时,可以使用继承,如人类包含学生;当表示“有”关系时,需要使用组合,如学生有家庭作业。
#程序6-25
class Homework():
def __init__(self,homework):
print('Homework:__init__')
self.__homework = homework
def __del__(self):
print('Homework:__del__')
def do_homework(self):
print('He is doing ' + self.__homework + ' homework.')
class Student():
def __init__(self,name = 'none',age = 0,\
school = 'no school',score = 0,homework = 'no homework'):
print('Student:__init__')
self.__name = name
self.__age = age
self.__school = school
self.__score = score
self.homework = Homework(homework)
def __del__(self):
print('Student:__del__')
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age),\
', shcool:'+ self.__school,', score:'+ str(self.__score))
s1 = Student('zhangsan',18,'合肥一中',85,'English')
s1.print_info()
s1.homework.do_homework()
运行结果:
Student:__init__
Homework:__init__
name:zhangsan , age:18 , shcool:合肥一中 , score:85
He is doing English homework.
Student:__del__
Homework:__del__
组合和继承都是对已有类资源的有效利用,只是两者的概念和使用场景不同。
6.5类的多态性
在C++中,函数根据传入类对象的不同,而选择调用基类对象或派生类对象的成员函数,称为多态(实现方式:虚函数)。
在Python3中,根据调用对象的不同而选择不同的方法,称为多态。由于Python3中变量无数据类型,因此实现多态非常简单。
#程序6-26
class Person():
__count = 0 #类变量
def __init__(self,name = 'none',age = 0):
print('Person:__init__')
self.__name = name #实例变量
self.__age = age #实例变量
Person.__count += 1
def __del__(self):
print('Person:__del__')
Person.__count -= 1
def print_info(self):
print('name:'+ self.__name,', age:'+ str(self.__age))
@classmethod
def get_count(cls): #类方法
print('count :'+ str(cls.__count))
class Student(Person):
def __init__(self,name,age,school = 'no school',score = 0): #score表示分数
print('Student:__init__')
Person.__init__(self,name,age)
#super(Student,self).__init__(name, age)
self.__school = school
self.__score = score
def __del__(self):
print('Student:__del__')
Person.__del__(self)
def print_info(self):
#super(Student,self).print_info()
Person.print_info(self)
print('shcool:'+ self.__school,', score:'+ str(self.__score))
def func(cls_object):
cls_object.print_info()
p1 = Person('lisi',16)
s1 = Student('zhangsan',18,'合肥一中',85)
func(p1)
func(s1)
运行结果:
Person:__init__
Student:__init__
Person:__init__
name:lisi , age:16
name:zhangsan , age:18
shcool:合肥一中 , score:85
Person:__del__
Student:__del__
Person:__del__
在函数func中,根据传入对象的不同,而调用不同的print_info()方法。
6.6运算符重载和代码的拆分
6.6.1运算符重载
类的专有方法中,除了构造函数和析构函数,常用的还有比较运算__cmp__、加运算__add__、减运算__sub__、乘运算__mul__、除运算__truediv__、求余运算__mod__、乘方运算__pow__等。
在类定义中可以通过重写专有方法(运算符)来实现类的运算操作,称为运算符重载。
#程序6-27
class Vector():
def __init__(self,x,y):
print('__init__')
self.__x = x
self.__y = y
def __del__(self):
print('__del__')
def print_info(self):
print('x = '+str(self.__x),' y = '+str(self.__y))
def __add__(self,other):
self.__x = self.__x + other.__x
self.__y = self.__y + other.__y
return Vector(self.__x,self.__y)
v1 = Vector(1.3,2)
v2 = Vector(3,4)
# v = v1.__add__(v2)
v = v1 + v2
v.print_info()
运行结果:
__init__
__init__
__init__
x = 4.3 y = 6
__del__
__del__
__del__
可以通过v = v1 + v2或v = v1.__add__(v2)这两种方式来调用__add__方法。
6.6.2代码的拆分
在项目开发中,我们通常把类的定义和类的使用分开放在2个模块中。
#程序6-28:mod.py
class Vector():
def __init__(self,x,y):
print('__init__')
self.__x = x
self.__y = y
def __del__(self):
print('__del__')
def print_info(self):
print('x = '+str(self.__x),' y = '+str(self.__y))
def __add__(self,other):
self.__x = self.__x + other.__x
self.__y = self.__y + other.__y
return Vector(self.__x,self.__y)
#程序6-28:run.py
from mod import Vector
v1 = Vector(1.3,2)
v2 = Vector(3,4)
# v = v1.__add__(v2)
v = v1 + v2
v.print_info()
运行结果:
__init__
__init__
__init__
x = 4.3 y = 6
__del__
__del__
__del__
类的基本内容就讲到这里。