• 浅谈面向对象


    浅谈面向对象

    概要:面向对象,顾名思义,面向对象模式中的主体被称为对象(object)。每个对象都是类(class)的实例(instance)。

    什么是面向对象

    其实在我学了辣么久的编程,直至今日仍然对所谓的“面向对象”的了解不够深刻,大概是“不识庐山真面目,只缘身在此山中”。一直没有什么作对比,所以只能不自量力做一个简单的分析:

    在了解面向对象之前我们先要了解面向过程编程,这样才能更方便地了解面向对象。面向过程类似于工厂的流水线,每一步的操作都基于上一步的运行结果,如果其中一步错了,那么后面的程序就都错了(一步错、步步错)。

    • 优点 : 复杂问题流程化、进而简单化

    • 确定 : 扩展性差

      那么面向对象就类似于创建了很多对象给你干活,以上帝的视角去定义对象,赋予他们属性及技能。

      • 优点 :可扩展性强
      • 缺点 : 编程的复杂性要高于面向过程

    类与对象

    类的概念就像猫科、犬科,即类别的意思,这是面向对象重要的概念,对象是特征与技能的结合体,而类就是一系列对象相似的结合体。(在编程中先有类再有对象)

    类的定义语法:

    class 类名:
        
        类的属性 = 属性值
        
        def __init__(self,初始化属性值):
            self.属性 = 属性值
    
    	def 方法(self):
            pass
    

    举例:

    class Student:
        
        school = "蓝翔"  # 这是类的属性
        
        def __init__(self,name):  # 类的实例化方法(构造函数或初始化方法)
            self.name = name
    
        def study(self):  # 类的方法
            print(self.name, "在学习!")
    

    那么实例化一个类的对象的方法就是:

    stu = Student("张三")  # 创建对象的方式,实际走的是类的__init__方法
    print(stu.school)     # 蓝翔
    print(stu.name)       # 张三
    stu.study()           # 张三 在学习!
    

    在python中所有的属性都可以通过.来调用或者赋值,如果没有这个属性就等于新建了这样一个属性。比如:stu.age = 18就是给这个对象追加一个age属性,赋值为18


    类中内置方法预热:

    #python为类内置的特殊属性
    类名.__name__# 类的名字(字符串)
    类名.__doc__# 类的文档字符串
    类名.__base__# 类的第一个父类(在讲继承时会讲)
    类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
    类名.__dict__# 类的字典属性
    类名.__module__# 类定义所在的模块
    类名.__class__# 实例对应的类(仅新式类中)
    
    

    属性查找

    类有两种属性:数据属性 和 函数属性

    • 类的数据属性是所有对象共享的(id都是一样的)
    • 类的函数属性是绑定给对象的 (对象调用就是对象的绑定方法)

    以上面的例子来说,stu.name首先会从自己的名称空间中找name,找不到就去类中找,类找不到就找父类,父类也没有就去object类中找,最后找不到就会抛出异常。

    对象的绑定方法

    类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数。

    Student.study(stu)  # 传入一个对象参数
    

    类中定义的函数(没有被任何装饰器装饰的),其实主要是给对象使用的,而且是绑定到对象的,虽然所有对象指向的都是相同的功能,但是绑定到不同的对象就是不同的绑定方法。

    #### 重点:对象的绑定方法特殊之处就在于,对象来调用就会把自身当做第一个参数(self)传给方法
    stu.study()  # 相当于 Student.study(stu)
    

    了解了对象的一些基本知识后,我们就正式进入面向对象的大门吧!

    面向对象的三大特性

    面向对象有最重要的三大特性:继承、封装、多态

    继承

    什么是继承

    继承是一种创建新类的方式,新建的类可以继承一个或多个父类(python支持多继承),父类又可称为基类或超类,新建的类称为派生类或子类。

    子类会“”遗传”父类的属性(拥有父类所有的属性和方法),从而解决代码重用问题

    单继承和多继承

    class A: #定义父类
        pass
    
    class B: #定义父类
        pass
    
    class C(A): #单继承,基类是A,派生类是C
        pass
    
    class D(A,B): #python支持多继承,用逗号分隔开多个继承的类
        pass
    

    查看继承

    >>> C.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
    (<class '__main__.A'>,)
    >>> D.__bases__
    (<class '__main__.A'>, <class '__main__.B'>)
    

    经典类与新式类

    1.继承object类的类就是新式类。(python3中所有的类都默认继承object)
    2.没有继承object类的类就是经典类。(只存在于python2中)
    

    属性查找

    按照类的mro()返回的查找顺序来查找(返回类的查找顺序列表)。

    派生

    子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

    组合与重用

    ​ 软件重用的重要方式除了继承之外还有另外一种方式,即:组合

    组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

    class Student:
        pass
    
    class Taacher:
        
        def __init__(self):
            self.stu_list = []
           
        def add_stu(self,stu):
            self.stu_list.append(stu)
            
    stu = Student()
    teacher1 = Teacher()
    teacher1.add_stu(stu)
    
    

    菱形问题

    在python中的子类可以继承多个父类,如F(D, E),如果继承关系为非菱形的,那么就会先从D这个分支开始找(包括D的所有父类),没找到就会 去E这条分支去找,如果这次也没有找到,就会报错。

    如果继承关系为菱形结构,那么属性查找的方式有两种,分别是深度优先和广度优先。

    在python3中的菱形查找顺序都是广度优先,图示如下:

    深度优先只存在于python2中才有,它会在第一个分支上就走到底。

    我们还可以直接用__mro__mro()方法查看继承顺序。不过只有新式类才有这个属性来查看先行列表,经典类没有这个属性。

    子类调用父类方法

    方式一:指名道姓(父类名.父类方法)

    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
    class Teacher(Person):
        
        def __init__(self,name,age,sex):
            Person(self,name,age)
            self.sex = sex
    

    方式二:super()

    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
            
    class Teacher(Person):
        
        def __init__(self,name,age,sex):
            super().__init__(name,age)
            self.sex = sex
            
    

    使用哪一种都可以,但是最好不要混用

    了解:

    • 继承关系也按照mro顺序进行!
    • super(父类名,self).父类方法名

    多态以及多态性

    • 多态指的是一类事物有多种形态

      比如猫科动物的多种形态:老虎、狮子、狸猫

      class Felid:
          def talk(self):
              pass
      
      class Tiger(Felid):
          def talk(self):
              print("吼吼吼~")
      
      
      class Lion(Felid):    
          def talk(self):
              print("嗷嗷嗷~")
      
      
      class Cat(Felid):
          def talk(self):
              print("喵喵喵~")
      
    • 多态性是指在不考虑实例类型的情况下使用实例

      多态性分为静态多态性和动态多态性

      具体如下:

      tiger = Tiger()
      lion = Lion()
      cat = Cat()
      
      # 只要是猫科动物都会有talk()方法,所以我们也不用管具体是什么品种而直接调用
      tiger.talk()
      lion.talk()
      cat.talk()
      
      # 我们可以定义一个统一的接口来使用
      def func(obj):
          obj.talk()
          
      

      多态性的好处:

      • 增加了程序的灵活性:

        无论对象怎么变,使用方式都是一样,如 func(obj)

      • 增加了程序的可扩展性

        使用者无需更改自己的代码,直接用func(obj)去调用

    • 鸭子类型

      python是十分崇尚鸭子类型的。鸭子类型的含义就是:如果看起来像鸭子、走路和叫声都想鸭子,那么它就是鸭子。

      如果我们想编写先有对象的自定义版本,可以继承该对象,也可以创建一个外观和行为相像的,但是与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。

    封装

    封装的概念就是把类的一些属性隐藏起来,封装=隐藏。

    在python中,使用双下划綫开头的方式可以将属性隐藏起来(私有属性)

    #其实这仅仅这是一种变形操作且仅仅只在类定义阶段发生变形
    #类中所有双下划线开头的名称如__x都会在类定义时自动变形成:_类名__x的形式:
    
    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这个名字访问到。
    

    这里有两点我们需要注意:

    • 这种机制实际就是将属性变形,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N,即这种操作并不是严格意义上的限制外部访问,主要用来限制外部的直接访问。
    • 变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形

    如果在继承中,父类不想让子类覆盖自己的方法,也可以将方法定义为私有的

    #正常情况
    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)

    property 是一种特殊的属性,访问它时会将函数的返回值包装成属性。

    示例 : 计算圆的面积和周长

    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
    '''
    

    为什么要使用property: 对象获取这个值时无法察觉到是调用了一个函数,这种特性的使用方式遵循了统一访问原则

    property的扩展:

    class Person:
        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
        def name(self):
            raise TypeError('Can not delete')
    
    f=Person('Du')
    print(f.name)
    # f.name=10 #抛出异常'TypeError: 10 must be str'
    del f.name #抛出异常'TypeError: Can not delete'
    

  • 相关阅读:
    PVE6.3去订阅
    帝国CMS灵动标签当天更新的日期显示红色其他颜色变成灰色
    灵动标签实现循环子栏目数据——实现 循环子栏目数据标签 (listsonclass)的效果
    帝国cms灵动标签实现循环子栏目数据
    帝国cms常用灵动标签
    51nod1847 奇怪的数学题
    CTS2019 氪金手游
    CTS2019 重复
    UR #5 怎样跑得更快
    AGC034 F
  • 原文地址:https://www.cnblogs.com/Du704/p/11420560.html
Copyright © 2020-2023  润新知