python面向对象OOP入门

2023-01-31 05:01:53 python 面向对象 入门

 一、面向对象简介

      面向对象编程不是python独有,几乎所有高级语言都支持;面向对象不管在那个语言中都有三大特性:即:封装、继承、多态;具体的本文主要讲Python面向对象--类及三大特性的具体实现;



二、面向对象之python类特性一:封装

       python通过类实现面向对象的编程;编程大致分为面向过程式的函数式编程,和面向对象编程;

类(Class)和实例(Instance)是面向对象最重要的概念。

1、类的简单定义与使用

class '类名':
    语句块

如:

class Foo:
    def bar(self):
        print("Foo.bar")
        
        
obj = Foo()   #obj即是Foo类的实例
obj.bar()     #通过实例去调用调用Foo类中的方法

结果输出Foo.bar

以上就是最简单的类定义与使用;

2、self是什么?

我们注意到以上的示例中除了class关键字用来定义类,def定义方法(类中的函数我们称方法,可以理解为类中封装的函数就是方法)后面有一个self参数,那么 这个self参数是什么?先来一个例子:

class Foo:
    def bar(self,arg):
         print(self,arg)
         
z1 = Foo()
print(z1)
z1.bar(11)

输出结果:

9aa71c6f354aba5244e37212544fbef4.png-wh_

以上结果可以看到z1是Foo实例打印出内存地址和self的内存地址一样,这说明self就代指实例本身;

因此self就是类实例化出来的对象(实例 ),通俗的讲,就是那个实例对象调用,就代指那个实例对象


3、实例出对象

实例对象是从类实例化而来obj = 类名([参数....]),拥有自己的作用域命名空间,就是拥有自己的内存地址空间;同时可以对实例对象添加赋值;用于保存实例自己的数据;同时可以调用类中封装好的方法或数据;

class Foo:
    def bar(self,arg):
        print(self.name,self.age,arg)

obj = Foo()      #实例化obj对象
obj.name = "san"   #向实例中添加对象name并赋值
obj.age = 18        #向实例中添加对象age并赋值
obj.bar(666)        #调用类方法并传入参数6666

输出结果:

('san', '18', 666)

从上面的示例可以看出类在定义时并没有创建name 和age ;obj实例对象创建后通过赋值

让obj拥有了name和age;因此实例obj在调用类中bar方法时可以获取自身命名空间中name

和age值;实际上实例化后的对象如果没有做出限制(__slots__),可以向对象中保存添加任意多的值;


4、通过__slots__限制姨实例对象添加赋值

通过以上示例我们了解到,在从类实例化对象后,可以往对象中添加任意多的值,只要内存足够大;然而任何无节制的增加,都有可能带来隐患,所以我们通过__slots__功能来限制实例对象的赋值;

class Foo:
    __slots__ = ("name","age")    #只允许往实例赋name,age
    def bar(self,arg):
        print(self.name,self.age,arg)
    def Other(self):      
        print(self.other)
  
z = Foo()          #实例化z
z.name = "san"     #向z对象赋值name
z.age = 18         #向z对象赋值age
z.bar("6666")
z.other = "othter"   #向z对象赋值other
z.Other()

运行结果如图:

ea9e37732cb6c62cc58c1570d1a24778.png-wh_

这次没有赋值成功,被限制了提示:“AttributeError: 'Foo' object has no attribute 'other'“;这正是我们所要的。


5、构造函数__init__

对于以上的示例我们往实例化后的对象中赋值也可以限制能赋的值,如果要实例化出多个类对象,每个类对象有共同的属性值,如name,age,sex等,那么我们可以通过构造函数__init__方法来实现,即在实例化创建对象时传入name,age,来达到类似obj.name ="san" obj.age = 18这样的效果:

class Foo:
    def __init__(self,name,age):   #构造方法,这里有几个参数,实例化对象时就要传递对应的参数
        self.name = name
        self.age = age
        print("__init__ is start.")   #对象实例化时自动执行
    def foo(self,arg):
        print(self.name,self.age,arg)
        
obj = Foo("san",18)
obj.bar("Good")

运行结果如图:

18c3e5747dc3d500e64c039d5759db01.png-wh_

从运行结果中可以看出 只要实例化出对象obj = Foo(["参数...."])时就执行__init__方法中的内容;利用这个功能,我们给各实例传入name,age参数时自动赋值;这就是构造方法的功能;其中参数可选,有无参数在于你设计类时是否需要。


三、类特性二:继承

继承民是面向对象的特性之一,即子类(又叫派生类)继承父类(又叫基类或超类)的特性(属性,方法等);同时子类可以重写父类中的方法或属性。说到继承这时提一嘴,python2.x和python3.x继承有点区别,python2.x是经典类,通过class 类名(object)变成新式类,python3.x默认是新式类;新式类和经典类的区别在于,多继承时,继承顺序不一样;经典类采用”深度优先“方式 查找;而新式类则是广度优先去查找;这里就不详述,以下的示例在python 2.7中所以你会看到类名后有一个object,python3.x下可以不用object。关于深度优先和广度优先这里先不详述,下面有示例:


1、简单继承

示例:

class F(object):           #父类F
    def f1(self):
            print("F.f1")
    def f2(self):        
            print("F.f2")
class S(F):                #子类S
    def s1(self):
            print("s.s1")
            
son = S()    #从S类实例化对象son
son.s1()     #调用S类的s1方法
son.f1()     #由于在S类中没有找到f2,所以到父类F中找到f1

运行结果如图:

2fba26d013759d6e4fbd3ae2b784f7e0.png-wh_

说明:虽然S类中没有f2方法,但由于S类继承了F类,而F类中有f1方法,因此可以调用F中的f1方法。


2、重写

有时候我们在继承了父类后,父类中的方法可能并不能满足需求,直接修改父类中的方法又破坏了原类,可能影响到其他引用,此时可以通过在子类中定义一个父类中同名的方法,就可以达到重写父类方法的目的。

示例:

class F(object):
    def f1(self):
        print("F.f1")
    def f2(self):
        print("F.f2")
class S(F):
    def s1(self):
        print("s.s1")
    def f2(self):  # 不继承,重写
        print("S.f2")
son = S()
son.s1()  #S类的s1方法
son.f1()  #继承F类的f1方法
son.f2()  #S自己的f2方法,重写(覆盖)F类的f2方法

运行结果:

b1fafa511759fe0979af523debe8eb62.png-wh_

3、子类中调用超类方法

虽然上面的例子中重写了父类中的同名方法,满足了需求,但能否在重写的基础上,引用超类中的方法呢?即先运行超类中的同名方法,再定义自己的同名方法?答案是必须可以啊!

这里有两种方法,一起列举,现实中只用其中的一种,建议使用super:

class F(object):
    def f1(self):
        print("F.f1")
    def f2(self):
        print("F.f2")
class S(F):
    def s1(self):
        print("s.s1")
    def f2(self):  # 不继承,重写
        print("S.f2")
        super(S,self).f2()    #调用父类f2方法一: super(子类,self).父类中的方法(...)
        F.f2(self)            #调用父类f2方法二:父类.方法(self,....)
        
son = S()
son.s1()  #S类的s1方法
son.f1()  #继承F类的f1方法
son.f2()  #S自己的f2方法,重写(覆盖)F类的f2方法

运行结果:

51e043336dc34b0227b62683247ab525.png-wh_

由于S类中的f2方法除了输出自己的功能S.f2外还通过两种调用父类F中的f2方法,所以输出了两次F.f2;

注意这里为了演示只调用了父类中的f2方法,达到继承和重写优化父类中的f2方法,也可以通过这两种方法中的一种调用父类中的其他方法;


4、类多继承

类可以继承多个类,即在class 类名(父类1,父类2,...)那么问题来,如果所继承的类都有一个同名的方法,那调用时如何选择?上面提到了经典类是深度优先,新式类时广度优先,本文不做深度优先和广度优先查找比较,只讲新式类的广度优先;有兴趣的可以自行查找资料。

示例:

class Base(object):
    def a(self):
        print("Base.a")
class F1(Base):
    def a1(self):
        print('F1.a')
    def a11(self):
        print("F1.a11")
        
class F2(Base):
    def a2(self):
        print('F2.a')
    def a11(self):
        print("F2.a11")
class S(F1,F2):
    pass
class S2(F2,F1):
    pass
obj = S()
obj.a11()
obj2 = S2()
obj2.a11()

运行结果:

d6e7b907756a11b080b43873044109ff.png-wh_

obj继承顺序是F1,F2   结果是F1.a11

obj2继承顺序是F2,F1 结果是F2.all


5、有交集的多继承查找


obj对象从A类实例化而来,A类继承了B和C类,B类继承D,C类也继承D类;baba方法在C类和D类中都有,此时obj.baba()方法结果是怎样?

示例:

class D:
    def bar(self):
        print('D.bar')
    def baba(self):
        print("D.baba")
class C(D):
    def bar(self):
        print('C.bar')
    def baba(self):
        print("C.baba")
class B(D):
    def bar(self):
        print('B.bar')
class A(B, C):
    def bar(self):
        print('A.bar')

obj = A()
obj.bar()
obj.baba()

运行结果:

240e625fda7accf24feb8d18c15590c5.png-wh_


# 执行bar和baba方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> C --> D
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了


同样的例子我们调回python2.7作为经典类执行,看结果。

2a17181b8338f0c29f30416761f2e9df.png-wh_

 执行bar和baba方法时
首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
 所以,查找顺序:A --> B --> D --> C
 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了

这里的baba方法在D中找到,所以显示为D.baba

深度优先和广度优先查找顺序如图:

67efcf62e4f16c675b9552340a4f0194.png-wh_


6、多继承之__init__执行顺序

以下是一个模拟实践中多继承情况下__init__构造方法的执行,有利于我们阅读项目源码

示例:

class BaseRequest:    #(3)
   def __init__(self):
       print("BaseRequest.init")
class RequestHandler(BaseRequest):  #(2)
    def __init__(self):
        print("RequestHandler.init")
        BaseRequest.__init__(self)   #调用父类的__init__方法
    #     super(RequestHandler,self).__init__()

    def serve_forever(self):     #(5)
        print("RequestHandler.serve_forever")
        self.process_request()

    def process_request(self):
        print('RequestHandler.process_request')
class Minx:
    def process_request(self):   #(6)
        print("Minx.process_request")

class son(Minx,RequestHandler):
    pass

obj = son()     #(1)
obj.serve_forever()    #(4)

以上的注释后的数字是执行的流程。obj = son()的作用:

实例化出obj对象;

执行__init__构造方法,在多继承环境 下,和上面讲的调用其他类方面查找一致,首先查找 son类中的__init__如果没有按新式类的方度优先顺序查找所继承类中的__init__,这里首先找到Minx,没有,再找到RequestHandler类,执行打印“RequestHandler.init”,里的__init__再次执行了父类中的__init__方法,因此又打印出"BaseRequest.init";

执行obj.serve_forever()时,同样的查找,打印出“RequestHandler.serve_forever”,注意这里又调用 了self.process_request()方法,由于self指的就是所调用的对象obj即obj.process_request()方法,因此重新从son(Minx,RequestHandler)中按顺序查找,而不是RequestHandler类下的 process_reques方法。因此找到Minx下的process_request方法,得到结果:“Minx.process_request”


执行结果如下:

43461690e1de0c056789b9529534ec27.png-wh_



四、python面向对像之多态

       多态简单的理解就是同一个方法在不现的对象调用时显示出的效果不一样,如+ 在×××数字相加时是数学运算,在字符串相加时加是连接符;

       python的面向对象原生支持多态,不像java等强类型语言,传递参数时必须指定类型,而python没有此此限制,这也是python原生动支持多态的原因之一。



本文主要说明了python面向对象的三大特性,封装,继承及多态,继承有多继承,新式继承与经典类继承的区别。个人总结,如有不当之处欢迎指正。



相关文章