-
类的继承: 什么是什么, 比如人是动物, 所以人就属于动物类
-
类的组合: 什么有什么, 比如老师有名字, 老师有年龄, 老师有课程, 所以名字, 年龄, 课程这些属性类就可以组合到老师类
类的组合
class Teacher:
def __init__(self,name:str,age,sex,level:int,salary):
self.name=name
self.age=age
self.sex=sex
self.level=level
self.salary=salary
class Course:
def __init__(self,course_name,course_price,course_period):
self.course_name=course_name
self.course_price=course_price
self.course_period=course_period
python=Course('python',1000,'1mons')
teacher1=Teacher('ross',18,'male',10,3000)
teacher2=Teacher('joey',28,'male',20,3000)
teacher1.course=python #不需要改动Teacher类的代码就可以直接组合课程属性到对象teacher1
teacher2.course=python
print(teacher1.course.course_name)
抽象类
抽象类, 如下图, 每种动物都有行走的方法, 但是定义了不同的方法名字, 很不友好, 如果解决这个问题就可以使用抽象类
class People:
def walk(self):
print('people is walking')
class Pig:
def run(self):
print('pig is running')
peo1=People()
pig1=Pig()
peo1.walk()
pig1.run()
# people is walking
# pig is running
通过抽象类规范子类的方法, 抽象类只能被规范, 不能被实例化
引入模块`abc`, 下面的代码中父类Animal通过abc模块的abstractmethod的装饰器装饰了方法run和eat, 所以在子类People和Pig中必须要有被装饰的这两个方法run和eat, 如果子类中没有这两个方法, 则会报错如下
TypeError: Can't instantiate abstract class People with abstract methods eat, run
import abc
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def run(self):
pass
@abc.abstractmethod
def eat(self):
pass
class People(Animal):
def run(self):
print('people is walking')
def eat(self):
print('people is eating')
class Pig(Animal):
def run(self):
print('pig is running')
def eat(self):
print('pig is eating')
peo1=People()
pig1=Pig()
def func(obj):
obj.run()
func(peo1)
func(pig1)
super函数
通常情况下, 我们在子类中定义了和父类同样的方法名, 那么子类的方法就会覆盖父类的方法, 而super关键字实现了对父类方法的改写(增加了功能,增加的功能写在子类中,父类方法中原来的功能得以保留)。也可以说,super关键字帮助我们实现了在子类中调用父类的方法
class Animals(object):
def __init__(self,name):
self.name=name
def greet(self):
print('hello, I am %s' %self.name)
class Dog(Animals):
def greet(self):
super().greet()
print('wangwang')
dog=Dog('dog')
dog.greet()
print(Animals.mro())
# 输出结果
# hello, I am dog
# wangwang
# [<class '__main__.Animals'>, <class 'object'>]
python对于每一个类都有一个mro列表, 可以通过mro()方法直接查看, 例如class.mro()
class BaseClass(object):
def __init__(self):
print('enter BaseClass')
print('leave BaseClass')
class A(BaseClass):
def __init__(self):
print('enter A')
super().__init__()
print('leave A')
class B(BaseClass):
def __init__(self):
print('enter B')
super().__init__()
print('leave B')
class C(B,A):
def __init__(self):
print('enter C')
super().__init__()
print('leave C')
print(C.mro())
# 输出结果
# [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.BaseClass'>, <class 'object'>]
c=C()
# 输出结果
# enter C
# enter B
# enter A
# enter BaseClass
# leave BaseClass
# leave A
# leave B
# leave C
如果不使用super函数
class BaseClass(object):
def __init__(self):
print('enter BaseClass')
print('leave BaseClass')
class A(BaseClass):
def __init__(self):
print('enter A')
# super().__init__()
BaseClass().__init__()
print('leave A')
class B(BaseClass):
def __init__(self):
print('enter B')
# super().__init__()
BaseClass().__init__()
print('leave B')
class C(B,A):
def __init__(self):
print('enter C')
# super().__init__()
B().__init__()
A().__init__()
print('leave C')
print(C.mro())
# 输出结果:
# [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.BaseClass'>, <class 'object'>]
c=C()
# 输出结果:
enter C
enter B
enter BaseClass
leave BaseClass
enter BaseClass
leave BaseClass
leave B
enter B
enter BaseClass
leave BaseClass
enter BaseClass
leave BaseClass
leave B
enter A
enter BaseClass
leave BaseClass
enter BaseClass
leave BaseClass
leave A
enter A
enter BaseClass
leave BaseClass
enter BaseClass
leave BaseClass
leave A
leave C
封装
属性的隐藏
在需要隐藏的属性前加上双下划线__
class A:
__x=1 # 外部调用时会变形为_A__x
def __init__(self,name):
self.__name=name # 外部调用时会变形为_A__name
def __foo(self): # 外部调用时会变形为_A__foo
print('run foo')
这种变形的特点:
- 在类外部无法直接调用obj.__AttrName
- 在类内部可以直接调用obj.__AttrName
- 子类无法覆盖父类__开头的属性
这种变形需要注意的问题是:
-
这种机制也并没有真正意义上限制我们从外部直接访问属性, 知道了类名和属性名就可以拼出名字
-
变形过程只在类的定义时发生一次, 在定义后的赋值操作, 不会变形
-
在继承中, 父类如果不想让子类覆盖自己的方法, 可以将方法定义为私有的
下列代码子类的属性会覆盖同名的父类属性
class A:
def foo(self):
print('A.foo')
def bar(self):
print('A.bar')
self.foo()
class B(A):
def foo(self):
print('B.foo')
b=B()
b.bar()
# 输出结果
# A.bar 因为class B中没有bar这个方法, 所以会去父类当中找, 所以输出了A.bar
# B.foo 由于bar这个方法中有self.foo, 而B类中有这个方法, 所以就是输出B.foo, 子类的方法是可以覆盖父类的方法的
如果就是想要使用父类的方法, 而不是子类的方法, 这时候就会用到隐藏属性了
class A:
def __foo(self): #_A__foo
print('A.foo')
def bar(self): #_A__bar
print('A.bar')
self.__foo() #_A__foo
class B(A):
def __foo(self): #_B__foo
print('B.foo')
b=B()
b.bar()
# 输出结果
# A.bar
# A.foo
封装的意义
封装数据属性, 明确的区分内外, 控制外部对隐藏的属性的操作行为
class People:
def __init__(self,name,age): #隐藏name和age属性, 通过tell_info和set_info两个接口调用或者修改这两个属性,
self.__name=name #避免外部直接操作这两个属性
self.__age=age
def tell_info(self):
print('Name:<%s> Age:<%s>' %(self.__name,self.__age))
def set_info(self,name,age):
if not isinstance(name,str):
print('名字必须是字符串')
return
if not isinstance(age,int):
print('名字必须是数字类型')
return
self.__name=name
self.__age=age
p=People('ross',12)
p.tell_info()
p.set_info('van',18)
p.tell_info()
class ATM:
def __card(self):
print('插卡')
def __auth(self):
print('认证')
def __input(self):
print('输入取款金额')
def __print(self):
print('打印账单')
def __take(self):
print('取款')
def with_draw(self):
self.__card()
self.__auth()
self.__input()
self.__print()
self.__take()
a=ATM()
a.with_draw() #对于外部调用来说, 只有with_draw一个接口, 隔离复杂度
神奇的property
property
装饰器就是把方法变成属性调用的, 调用方法的时候不需要加括号
@property
的实现比较复杂,我们先考察如何使用。把一个getter
方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作, 可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
stu=Student()
stu.birth=1999
print(stu.age)
上面的birth
是可读写属性,而age
就是一个只读属性,因为age
可以根据birth
和当前时间计算出来。
class Screen:
@property
def width(self):
pass
@width.setter
def width(self,width):
if not isinstance(width,int):
print('wrong value type')
return 2
self.__width=width
@width.getter
def width(self):
print(self.__width)
@width.deleter
def width(self):
print('你们不要再打啦')
@property
def height(self):
pass
@height.setter
def height(self,height):
if not isinstance(height,int):
print('wrong value type')
return 2
self.__height=height
@property
def resolution(self):
return self.__width*self.__height
s = Screen()
s.width = 1024
s.height = 768
s.width
print('resolution =', s.resolution)