面向对象设计中最基础的3个概念:数据封装、继承和多态
使用_ _slots_ _
_ _slots_ _的作用:限制实例的属性
为了达到限制的目的,在定义class时,定义一个_ _slots_ _特殊变量,来限制该class实例添加的属性:
注意:1、_ _slots_ _仅对当前类实例起作用,对继承的子类是不起作用的
2、如果子类中也定义_ _slots_ _,那么子类实例允许添加的属性就是自身的_ _slots_ _加上父类的_ _slots_ _
3、使用_ _slots_ _添加属性时,一定要给属性加上引号
为了给各个实例都绑定方法,可以给class绑定方法
通常情况下,添加方法可直接定义在类中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。
1 class Student(object): 2 __slots__=('name','age') #用tuple定义允许绑定的属性名称 3 4 s=Student() #创建一个实例 5 s.name='Maria' 6 s.age=23 7 # s.score=99 由于在类Student中限制了属性,所以添加score就会报错 8 print("s.name:",s.name) 9 print("s.age:",s.age) 10 11 print("给s实例添加score属性后,报错类型为:") 12 try: 13 s.score=99 14 except AttributeError as e: 15 print("AttributeError:",e) 16 17 print(' ') 18 print("下面定义了子类:") 19 class Graduate(Student): #继承了Student类,那么也继承了Student类的_ _slots_ _ 20 __slots__=('score') #这在子类中也定义了_ _slots_ _ 21 #看出下面g.name和g.age都正常,说明子类继承了Student类的_ _slots_ _ 22 g=Graduate() 23 g.score=99 24 g.name='shirley' 25 g.age=34 26 print("g.name,g.age:",g.name,g.age) 27 print("g.score:",g.score)
运行结果:
@property
1 class Student(object): 2 @property 3 def score(self): 4 return self._score #是self._score,不是self.score,中间的下划线不能省略 5 6 @score.setter 7 def score(self,value): #都是raise ValueError, V和E要大写,先判断输入的数是不是Int,再判断是否在100以内 8 if not isinstance(value,int): 9 raise ValueError("please input an integer") 10 elif value<0 or value>100: 11 raise ValueError("请输入0到100之间的数!!!") 12 else: 13 self._score=value 14 15 s=Student() 16 s.score=99 17 print("s.score:",s.score) 18 print(" ") 19 s1=Student() 20 s1.score=999 21 print("s1.score的分数:",s1.score)
运行结果:
测试@property的效果:
1 class Screen(object): 2 __width = 0 3 4 def get_width(self): 5 return self.__width 6 7 @property 8 def width2(self): 9 return self.__width 10 11 s = Screen() 12 print("type(s.get_width)类型为:",type(s.get_width)) # <class 'method'> 13 print("type(s.width2)类型为:",type(s.width2)) # <class 'int'>
运行结果:
实战:请利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution:
1 class Screen(object): 2 @property 3 def width(self): 4 return self._width 5 @width.setter 6 def width(self,value): 7 self._width =value 8 9 @property 10 def height(self): 11 return self._height 12 @height.setter 13 def height(self,value): 14 self._height=value 15 16 @property 17 def resolution(self): #此处将resolution定义为只读属性 18 return self.width*self.height 19 20 s = Screen() 21 s.width = 1024 22 s.height = 768 23 print('resolution =', s.resolution) 24 if s.resolution == 786432: 25 print('测试通过!') 26 else: 27 print('测试失败!')
运行结果:
多重继承
参考:https://kevinguo.me/2018/01/19/python-topological-sorting/
多重:多个, 多重继承:即同时继承多个对象
通过多重继承,一个子类就可以同时获得多个父类的所有功能
MixIn
在设计类的继承关系时,通常,主线都是单一继承下来的,但是,如果需要“混入”额外的功能,通过多重继承就可以实现,这种设计通常称之为MixIn.
python允许使用多重继承,因此,MixIn就是一种常见的设计
只允许单一继承的语言(如JAVA),不能使用MixIn设计
目的:MixIn主要解决对同一对象用不同标准来分类的问题。从性别划分,你是男人;从国家划分,你是中国人;从职业划分,你是程序员; mixin可以轻松定义具备跟你一样特征的人:中国男程序员(天朝屌丝逗比程序员)
定制类
__str__
将__str__方法理解成,规范化输出格式,输出用户能看懂的格式
__repr__
直接显示变量调用的不是__str__( ),而是__repr__( ),两者的区别:
__str__( )返回用户看到的字符串
__repr__( )返回程序开发者看到的字符串,__repr__( )是为调试服务的
通常情况下,__repr__( )和 __str__( )的代码相同,所以偷懒时可以写成这种形式:__repr__=__str__ ,类似赋值,把__str__ 里的代码赋给__repr__
1 class Student(object): 2 def __init__(self,name): 3 self.name=name 4 5 s = Student("Michael") 6 print(s)
运行结果:
下面使用了__str__方法后:
1 class Student(object): 2 def __init__(self,name): 3 self.name=name 4 def __str__(self): 5 return 'Student Object (name :%s) ' % self.name #返回的内容,自己可以修改 6 7 s=Student("Michael") 8 print(s)
返回用户能够看懂的格式:
__str__方法 return 后面的语句可以自己修改
1 class Student(object): 2 def __init__(self,name): 3 self.name=name 4 def __str__(self): 5 return "Hello, My name is %s" % self.name 6 7 s=Student("Michael") 8 print(s)
__iter__
如果一个类想被用于for ……in 循环,类似list、tuple那样,就必须使用一个__iter__( )方法, 该方法返回一个迭代对象
举例:以斐波那契数列为例,写一个Fib类,可以作用于for循环:
1 class fib(object): 2 def __init__(self): 3 self.a,self.b=0,1 4 5 def __iter__(self): 6 return self #实例本身就是迭代对象,所以返回自己 7 8 def __next__(self): 9 self.a,self.b=self.b,self.a+self.b 10 if self.a>500: 11 raise StopIteration() 12 return self.a 13 14 for n in fib(): 15 print(n)
__getitem__
取下标
__getitem__( self, item)方法
item看作是“下标”, __getitem__( )传入的参数可能是一个int,也可能是一个切片对象slice
与 __getitem__对应的是__setitem__( )方法,__delitem__( )方法
1 class fib(object): 2 def __init__(self): 3 self.a,self.b=0,1 4 5 def __iter__(self): #__iter__方法,返回一个迭代对象 6 return self 7 8 def __next__(self): 9 self.a,self.b=self.b,self.a+self.b 10 if self.a>100: 11 raise StopIteration 12 return self.a 13 14 def __getitem__(self,n): #__getitem__方法 15 a,b=1,1 16 for x in range(n): 17 a,b = b, a+b 18 return a 19 20 f=fib() 21 print('f[0]:',f[0]) 22 print("f[1]:",f[1]) 23 print("f[2]:",f[2]) 24 print("f[3]:",f[3]) 25 print("f[13]:",f[13])
运行结果:
__getattr__
正常情况下,当调用的类的方法或属性不存在时,就会报错。
1 class Student(object): 2 def __init__(self): 3 self.name="lily" 4 5 s=Student() 6 print("s.name:",s.name) 7 print(" ") 8 print("s.score:",s.score) 9 print("s.score函数:",s.score())
由于类中没有score属性时,调用会报错
print("s.score:",s.score)语句的报错结果:
print("s.score函数:",s.score)语句的报错结果:
添加__getattr__方法后: return 99,所以调用时s.score即可
1 class Student(object): 2 def __init__(self): 3 self.name="lily" 4 5 def __getattr__(self, attr): #__getattr__方法 6 if attr == 'score': 7 return 99 8 9 10 11 s=Student() 12 print("s.name:",s.name) 13 print(" ") 14 print("s.score:",s.score)
返回函数也是可以的: return lambda : 25,所以调用时是s.score()
1 class Student(object): 2 def __init__(self): 3 self.name="lily" 4 5 def __getattr__(self, attr): #__getattr__方法 6 if attr == 'score': 7 return lambda :25 #此处是匿名函数lambda, 之前只是返回一个数值, return 99 8 # print('hell') 9 10 11 s=Student() 12 print("s.name:",s.name) 13 print(" ") 14 print("s.score:",s.score())#由于return后面返回的是一个函数,因此这里调用时应该是s.score(),带上括号, 15 # 而如果是return 99, 则调用时不必带括号
运行结果和上面相同:
__call__
调用实例方法时,使用“instance.method( )”形式来调用
问题:能不能直接在实例本身上调用呢?在python中,是肯定的
结果:任何类,只需要定义一个__call( )方法,就可以直接对实例进行调用
1 class Student(object): 2 def __init__(self,name): 3 self.name=name 4 5 def __call__(self): 6 return 'my name is %s' %self.name 7 8 s=Student('lily') 9 print(s())