欢迎来到簟纹灯影的博客

人生三从境界:昨夜西风凋碧树,独上高楼,望尽天涯路。 衣带渐宽终不悔,为伊消得人憔悴。 众里寻他千百度,蓦然回首,那人却在灯火阑珊处。

浅谈面向对象

浅谈面向对象

概要:面向对象,顾名思义,面向对象模式中的主体被称为对象(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'

posted @ 2019-08-27 20:10  簟纹灯影  阅读(181)  评论(0编辑  收藏  举报