Python开发-028_封装继承与多态

面向对象编程在很多语言中都存在,而面向对象编程有三大特性:封装、继承、多态

1 封装

在上一节文章中,我们主要讲解的就是变相对象的封装,所以我们在此进行总结即可

封装主要体现在两个方面:

  • 将同一类方法封装到了一个类中,例如上节示例中:匪徒的相关方法都写在Terrorist类中;警察的相关方法都写在Police类中。
  • 将数据封装到了对象中,在实例化一个对象时,可以通过__init__初始化方法在对象中封装一些数据,便于以后使用。

2 继承

2.1 继承的概念

在人们关系中,有个传统理念,儿女可以继承父亲的财产

在面向对象中也有这么一个继承的概念,即:子类可以继承父类中的方法和类变量(不是拷贝一份,父类的还是属于父类,子类可以继承而已)

# 父类与子类有两种叫法
父类	子类
基类	派生类

image-20210126182724313

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、Bar2个类,而他们三个中,都有一个方法是f1,且内部代码完全相同,那么他们就可以通过继承来节约代码

新建一个父类Base,将f1的代码写入其中,然后让Foo、BarBase做父

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中找到方法

image-20220524162332295

我们简单来说,自己类中没有,就像上级父类查找,上级父类如果是多个,就按照定义父类时候的从左至右的顺序进行,如果还需要往父类的父类中查找,优先级顺序就需要遵循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类实例化了obj1obj2两个对象,将两个实例化完成的对象,传入到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(),然后让CatDOg继承Animal

show函数的参数类型规定为Animal a,此后obj1obj2就可以被认为是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等数据类型,他们其实都一个类,根据类可以创建不同类的对象

image-20220524220553902

# 实例化一个str类的对象v1
v1 = str("kinght") 

# 通过对象执行str类中的upper方法。
data = v1.upper()

print(data)

Python开发-028_封装继承与多态
http://localhost:8080/archives/1JHZQfzj
作者
kinght
发布于
2024年11月11日
更新于
2024年11月11日
许可协议