一.组合
1.什么是组合
组合指的是某一个对象拥有一个属性,该属性的值是另外一个类的对象
class Foo:
pass
class Bar:
pass
obj=Foo()
obj.attr=Bar()
obj.xxx
obj.attr.yyy
2.为何要用组合
通过为某一个对象添加属性(属性的值是另外一个类的对象)的方式,可以间接地将两个类关联/整合/组合到一起从而减少类与类之间代码冗余
class Foo1:
pass
class Foo2:
pass
class Foo3:
pass
class Bar:
pass
obj_from_bar=Bar()
obj1=Foo1()
obj2=Foo2()
obj3=Foo3()
obj1.attr1=obj_from_bar
obj2.attr2=obj_from_bar
obj3.attr3=obj_from_bar
3. 如何用组合
class OldboyPeople:
school = 'Oldboy'
def __init__(self,name,age,sex,):
self.name = name
self.age = age
self.sex = sex
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex,score):
OldboyPeople.__init__(self,name,age,sex)
self.score = score
def choose_course(self):
print('%s choosing course' % self.name)
class OldboyTeacher(OldboyPeople):
def __init__(self,name,age,sex,level,salary):
OldboyPeople.__init__(self,name,age,sex)
self.level=level
self.salary=salary
def score(self,stu,num):
stu.score=num
class Course:
def __init__(self,c_name,c_price,c_period):
self.c_name = c_name
self.c_price = c_price
self.c_period = c_period
def tell_info(self):
print('<课程名:%s 价钱:%s 周期:%s>' %(self.c_name,self.c_price,self.c_period))
# 创建课程对象
python=Course('python全栈开发',1900,'5mons')
linux=Course('linux架构师',900,'3mons')
stu1=OldboyStudent('刘二蛋',38,'male')
stu1.course=python
# print(stu1.__dict__)
stu1.course.tell_info()
tea1=OldboyTeacher('egon',18,'male',10)
tea1.course=python
# print(tea1.__dict__)
tea1.course.tell_info()
#===============================================================================================================
class OldboyPeople:
school = 'Oldboy'
def __init__(self,name,age,sex,):
self.name = name
self.age = age
self.sex = sex
class OldboyStudent(OldboyPeople):
def __init__(self, name, age, sex,score=0):
OldboyPeople.__init__(self,name,age,sex)
self.score = score
self.courses=[]
def choose_course(self):
print('%s choosing course' % self.name)
def tell_all_course(self):
print(('学生[%s]选修的课程如下' %self.name).center(50,'='))
for obj in self.courses:
obj.tell_info()
print('='*60)
class OldboyTeacher(OldboyPeople):
def __init__(self,name,age,sex,level):
OldboyPeople.__init__(self,name,age,sex)
self.level=level
self.courses=[]
def score(self,stu,num):
stu.score=num
def tell_all_course(self):
print(('老师[%s]教授的课程如下' %self.name).center(50,'*'))
for obj in self.courses:
obj.tell_info()
print('*'*70)
class Course:
def __init__(self,c_name,c_price,c_period):
self.c_name = c_name
self.c_price = c_price
self.c_period = c_period
def tell_info(self):
print('<课程名:%s 价钱:%s 周期:%s>' %(self.c_name,self.c_price,self.c_period))
# 创建课程对象
python=Course('python全栈开发',1900,'5mons')
linux=Course('linux架构师',900,'3mons')
stu1=OldboyStudent('刘二蛋',38,'male')
stu1.courses.append(python)
stu1.courses.append(linux)
# print(stu1.courses)
stu1.tell_all_course()
tea1=OldboyTeacher('egon',18,'male',10)
tea1.courses.append(python)
# print(tea1.courses)
tea1.tell_all_course()
当类之间有显著不同,并且较小的类是较大类所需要的组件时,用组合较好
二.封装
1.什么是封装
装:往容器/名称空间里存入名字
封:代表将存放于名称空间中的名字给藏起来,这种隐藏对外不对内
2.为何要封装
封装数据属性:将数据属性隐藏起来,类外就无法直接操作属性,需要类内开辟一个接口来外部的使用可以间接地操作属性,可以在接口内定义任意的控制逻辑,从而严格控制使用对属性的操作
封装函数属性:隔离复杂度
3.如何封装
在类内定义的属性前加__开头(没有__结束)
总结:
1. __开头的属性实现的隐藏仅仅只是一种语法意义上的变形,并不会真的限制类外部的访问
2. 该变形操作只在类定义阶段检测语法时发生一次,类定义阶段之后新增的__开头的属性并不会变形
3. 如果父类不想让子类覆盖自己的属性,可以在属性前加__开头
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,
#这种,在外部是无法通过__x这个名字访问到。
# 正常情况
class A:
def fa(self):
print('from A')
def test(self):
self.fa()
class B(A):
def fa(self):
print('from B')
b = B()
b.test() #from B
# 把fa定义成私有的,即__fa
class A:
def __fa(self): # 在定义时就变形为_A__fa
print('from A')
def test(self):
self.__fa() # 只会与自己所在的类为准,即调用_A__fa
class B(A):
def __fa(self):
print('from B')
b = B()
b.test() #from A
三.特性(property)
1.什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值,可以将类内部的函数属性伪装成一个数据属性
2.property的用法
#计算BMI指数
class People:
def __init__(self,name,weight,height):
self.name=name
self.weight=weight
self.height=height
@property
def bmi(self):
return self.weight / (self.height**2)
p1=People('egon',75,1.85)
print(p1.bmi)
#===========================================================
#圆的周长和面积
import math
class Circle:
def __init__(self,radius): #圆的半径radius
self.radius=radius
@property
def area(self):
return math.pi * self.radius**2 #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
#注意:此时的特性arear和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
3. 为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
除此之外,面向对象封装有三种方式
[public]:这种其实就是不封装,是对外公开的
[protected]:这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意 思,但中文都是叫“父类”)公开
[private]:这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter #注意:这个地方.deleter前面的那个值必须要和被property装饰的那个函数的名相同
def name(self):
raise TypeError('Can not delete')
f=Foo('egon')
print(f.__dict__)
print(Foo.__dict__)
# f.name=10 #抛出异常'TypeError: 10 must be str'
# del f.name #抛出异常'TypeError: Can not delete'