Python开发-028_封装继承与多态
面向对象编程在很多语言中都存在,而面向对象编程有三大特性:封装、继承、多态
1 封装
在上一节文章中,我们主要讲解的就是变相对象的封装,所以我们在此进行总结即可
封装主要体现在两个方面:
- 将同一类方法封装到了一个类中,例如上节示例中:匪徒的相关方法都写在Terrorist类中;警察的相关方法都写在Police类中。
- 将数据封装到了对象中,在实例化一个对象时,可以通过
__init__
初始化方法在对象中封装一些数据,便于以后使用。
2 继承
2.1 继承的概念
在人们关系中,有个传统理念,儿女可以继承父亲的财产
在面向对象中也有这么一个继承的概念,即:子类可以继承父类中的方法和类变量(不是拷贝一份,父类的还是属于父类,子类可以继承而已)
# 父类与子类有两种叫法
父类 子类
基类 派生类
class Base:
def func(self):
print("Base.func")
def show(self):
print("Base.show")
# Base类是Son类的父类
class Son(Base):
def show(self):
print("Son.show")
s1 = Son()
s1.show() # Son.show
s1.func() # Base.func 优先在自己的类中找,自己没有才去父类。
s2 = Base()
s2.func() # Base.func
2.2 继承的作用
继承最大的作用就是帮助我们完成代码的重用
class Foo:
def f1(self):
pass
def f2(self):
pass
class Bar:
def f1(self):
pass
def f3(self):
pass
现在我们需要Foo、Bar
2个类,而他们三个中,都有一个方法是f1
,且内部代码完全相同,那么他们就可以通过继承来节约代码
新建一个父类Base
,将f1
的代码写入其中,然后让Foo、Bar
认Base
做父
class Base:
def f1(self):
pass
class Foo(Base):
def f2(self):
pass
class Bar(Base):
def f3(self):
pass
# 实例化后可以调用自己的,也可以调用父类的方法
demo1 = Foo()
demo1.f2()
demo1.f1()
2.3 对象创建时的空间与self空间绑定
class Base:
def __init__(self):
self.name = "Base.self"
def demo1(self):
print(self.name)
class Foo(Base):
def __init__(self):
self.name = "Foo.self"
a = Foo()
a.demo1() # Foo.self
我们使用foo
类实例化了对象a
,但是在a
调用方法的时候,方法需要在父类中进行查找,那么在父类中调用的self
指的其实是Foo
创建的空间self
,而不是Base
创建的
class Base:
def f1(self):
print('before')
self.f2() # 这里输出foo.f2 也是取obj创建的空间中找,而obj是根据Foo创建的
print('base.f1')
def f2(self):
print('base.f2')
class Foo(Base):
def f2(self):
print('foo.f2')
obj = Foo()
obj.f1()
2.4 Python是支持多继承的
class TCPServer:
def f1(self):
print("TCPServer")
class ThreadingMixIn:
def f1(self):
print("ThreadingMixIn")
class ThreadingTCPServer(ThreadingMixIn,TCPServer):
def run(self):
print("before")
self.f1()
print('after')
obj = ThreadingTCPServer()
obj.run()
例如ThreadingTCPServer
类,Python与其他编程语言不同的就是,它支持多个父类,如果多个父类有相同的方法,例如f1
,则是从子类的括号里从左至右选择,这里就是选择的ThreadingMixIn.f1()
# 输出结果
before
ThreadingMixIn
after
2.5 "嵌套"继承
注意:这里的嵌套是打了引号的,是我对它进行形容时的总结,并不是官方定义
class BaseServer:
def serve_forever(self, poll_interval=0.5):
self._handle_request_noblock()
def _handle_request_noblock(self):
self.process_request(request, client_address)
def process_request(self, request, client_address):
pass
class TCPServer(BaseServer):
pass
class ThreadingMixIn:
def process_request(self, request, client_address):
pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer):
pass
obj = ThreadingTCPServer()
obj.serve_forever()
obj
实例化是ThreadingTCPServer
类,但是调用了serve_forever
方法,而类中没有,就需要向上级父类查找,从左至右查看,ThreadingMixIn
父类和TCPServer
也没有,但是TCPServer
也有自己的父类,所以去到了BaseServer
中找到方法
我们简单来说,自己类中没有,就像上级父类查找,上级父类如果是多个,就按照定义父类时候的从左至右的顺序进行,如果还需要往父类的父类中查找,优先级顺序就需要遵循C3算法
,在此我们将与后面进行详细介绍
2.6 继承object
在Python3中编写类时,默认都会继承object(即使不写也会自动继承)
class Foo:
pass
class Foo(object):
pass
这一点在Python2是不同的,他们最核心的就是搜寻父类的优先级不同,详情会在后续c3算法章节讲到:
- 继承object,新式类
- 不继承object,经典类
小结:
- 执行对象.方法时,优先去当前对象所关联的类中找,没有的话才去她的父类中查找
- Python支持多继承:先继承左边、再继承右边的
- self到底是谁?去self对应的那个类中去获取成员,没有就按照继承关系向上查找
3 多态
多态,指的是参数的多种数据类型形态,在java或其他语言中的多态是基于:接口 或 抽象类和抽象方法来实现,让数据可以以多种形态存在
class Cat{
public void eat() {
System.out.println("吃鱼");
}
}
class Dog {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
public class Test {
public static void main(String[] args) {
obj1 = Cat()
obj2 = Cat()
show(obj1)
show(obj2)
obj3 = Dog()
show(obj3)
}
public static void show(Cat a) {
a.eat()
}
}
首先我们以Java为案例,在主函数中,使用Cat
类实例化了obj1
和obj2
两个对象,将两个实例化完成的对象,传入到show
函数中调用eat
方法,而由于传参需要规定类型,所以这里必须用public static void show(Cat a)
来规定传入的是Cat
实例化的对象,如果现在是Dog
实例化的对象obj3
,就无法作为参数传递到show
中运行
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
public class Test {
public static void main(String[] args) {
obj1 = Cat()
show(obj1)
obj2 = Dog()
show(obj2)
}
public static void show(Animal a) {
a.eat()
}
}
为了让Dog
实例化的对象也能够使用show
函数,所以Java
就引入了一个抽象类和抽象方法来实现,即专门定义一个abstract class Animal
的抽象类,类中在定义一个与要使用方法同名的一个抽象方法abstract void eat()
,然后让Cat
和DOg
继承Animal
在show
函数的参数类型规定为Animal a
,此后obj1
和obj2
就可以被认为是Animal
类定义的,就可以传输到show
函数中的搭配a.eat()
的运行
在Python中则不一样,由于Python对数据类型没有任何限制,所以他天生支持多态
class Email(object):
def send(self):
print("发邮件")
class Message(object):
def send(self):
print("发短信")
def func(arg):
v1 = arg.send()
print(v1)
v1 = Email()
func(v1) # v1.send()
v2 = Message()
func(v2) # v2.send()
在程序设计中,鸭子类型(duck typing)是动态类型的一种风格。在鸭子类型中,关注点在于对象的行为,能作什么;而不是关注对象所属的类型,例如:一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子
小结:
-
封装,将方法封装到类中 或 将数据封装到对象中,便于以后使用。
-
继承,将类中的公共的方法提取到基类中去实现。
-
多态,Python默认支持多态(这种方式称之为鸭子类型),最简单的基础下面的这段代码即可。
def func(arg): v1 = arg.copy() # 浅拷贝 print(v1) func("kinght") func([11,22,33,44])
4 回看数据类型
在初步了解面向对象之后,再来看看我们之前学习的:str、list、dict等数据类型,他们其实都一个类,根据类可以创建不同类的对象
# 实例化一个str类的对象v1
v1 = str("kinght")
# 通过对象执行str类中的upper方法。
data = v1.upper()
print(data)