类、实例、封装、继承以及多态

这里的搜集与整理主要是为了方便自己,是在自己有一定理解的基础上进行相应的阐述,目的是能够真正的去把问题弄懂,弄清楚。涉及到面向对象编程以及面向对象高级编程,后面可以不断的加入一些新的东西进行,加深对一些概念的理解和掌握也是非常值得自己去理解的。
参考资料如下:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017501655757856

面向对象编程的重要概念:类和实例

面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
面对对象的三大特点:

  1. 数据封装
  2. 继承
  3. 多态
  • 类:Class(抽象的创建实例的模板)
  • 实例 :Instance(根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但是各个的数据可能不同,相互独立,互不影响)
  • 方法:与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
  • 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
  • Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:
#定义Student类,该类是从object类继承下来的
class Student(object):

  #类起到了模板的作用,把一些认为必须绑定的属性强制填进去
  #定义一个特殊的方法,在创建实例的时候就把name、score等属性绑上去
  #self为方法的第一个参数,表示创建实例本身,把各种属性绑定到self上
  #self指向创建实例的本身
  def __init__(self, name, score):  
      self.name = name
      self.score = score

#创建bart实例(指向一个Stuedent的实例)
#必须传入与方法匹配的参数(self的传入由python解释器操作)
#每一个实例都有各自的name和score数据
bart = Student('Bart Simpson', 59)   
print(bart.name)
print(bart.score)

数据封装:打印一个学生的成绩的方法(__init__方法)

class Student(object):

    #包含三个形参
    def __init__(self,name,score):   
        #初始化属性 name,获取与形参name相关联的值,将其赋给变量name,然后该变量被关联到当前创建的实例中
        #可以通过实例来访问变量称为属性
        self.name = name             
        self.score = score        

    #内部定义访问数据的函数(打印一个学生的成绩)
    #直接在类的内部定于访问数据的函数,把数据给封装起来了,封装数据的函数是和类本身是关联的
    def print_score(self):           
        print('%s: %s' % (self.name, self.score))

#从外部看,只需要知道创建实例给出的name和score,如何打印是Student类的内部定义的
#数据和逻辑被“封装起来”,调用很容易,不用知道内部实现的细节
lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
lisa.print_score()
bart.print_score()

#输出结果为:
#Lisa: 99
#Bart: 59

更进一步:利用封装给Student类增加新的方法

class Student(object):
    def __init__(self,name,score): 
        self.name = name             
        self.score = score

    #增加get_grade的方法
    #get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节
    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C' 

lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())

#输出结果为:
#Lisa A
#Bart C

访问限值:

  1. 如果想让内部属性不被外部访问,属性前加___,变成私有变量,只有内部可以访问,外部不能访问
  2. 效果:确保外部代码不能随意修改对象内部的状态,通过访问限制的保护,代码更加健壮
    • 不是一定不能从外部进行访问(不能访问的原因:python解释器把__name变量改成_Student__name,可以通过_Student__name来访问__name变量)
  3. 需要获取的时候可以在类中加入get_name和get_score的方法。
    • 方法中可以对参数进行检查,避免传入无效的参数。
  4. 如果还想修改的话:在类中增加set_score方法。
class Student(object):

    def __init__(self, name, score):
        #在name和score的属性前面加双下划线__,使其变为私有变量(private)
        #内部可以访问,外部不能访问
        self.__name = name
        self.__score = score

    #参照上面做相同更改
    def print_score(self):           
        print('%s: %s' % (self.__name, self.___score))
   
    #为了保证外部可以访问,增加get_name方法
    def get_name(self):
        return self.__name

    #为了保证外部可以访问,增加get_score方法
    def get_score(self):
        return self.__score

    #为了允许外部代码修改score,增加set_score方法
    #同时在方法中还可以对参数做检查,避免传入无效的参数
    def set_score(self, score):
        if 0 <= score <= 100:
            self.__score = score
        else:
            raise ValueError('bad score')

    #增加get_name方法
    def get_grade(self):
        if self.__score >= 90:
            return 'A'
        elif self.__score >= 60:
            return 'B'
        else:
            return 'C'

bart = Student('Bart Simpson', 59)
print('bart.get_name() =', bart.get_name())

bart.set_score(60)
print('bart.get_score() =', bart.get_score())

#解释器把__name变量变成了_Student__name,仍然可以通过_Student__name来访问__name变量
print('DO NOT use bart._Student__name:', bart._Student__name)

继承和多态

被继承的:父类(Base class/Super class)
基于父类继承的:子类(Subclass)

  • 动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
  • 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。

总结:

  • 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起
  • 子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写

多态:

  1. 当我们定义了一个class的时候,实际上就定义了一种数据类型,定义的数据类型与python自带的数据类型差不多,可以用isinstance()函数来判断
  2. 调用只管调用,不管细节,而当我们新增一种Animal的字类的时候,主要确保run()方法编写正确,不用管原来的代码是如何调用的
  3. 对扩展开放:允许新增Animal子类
  4. 对修改封闭:不需要修改依赖Animal类型的run_twice()函数
  5. 可以理解为一种接口,多种实现,从而提高代码的扩展性,接口的复用性,有几个必要条件:继承、重写(重载)、父类引用指向子类对象(覆盖)
class Animal(object):
    #打印的方法
    def run(self):
        print('Animal is running...')

#定义Dog类时,直接从Animal类继承
class Dog(Animal):
    def run(self):
        print('Dog is running...')

#定义Cat类时,直接从Animal类继承
class Cat(Animal):
    def run(self):
        print('Cat is running...')

#定义一个函数,接受一个Animal变量类型的变量
def run_twice(animal):
    animal.run()
    animal.run()

#定义一个Tortoise类型的类,也从Animal派生:
#只要是Animal类或者子类,就会自动调用实际类型的run()方法(也即多态的本质)
class Tortoise(Animal):
    def run(self):
        print('Tortoise is running slowly...')

a = Animal()  #a是Animal类型的
d = Dog()  #d是Dog类型的
c = Cat()    #c是Cat类型的

#判断一个变量是否是某个类型可以用isinstance()判断
print('a is Animal?', isinstance(a, Animal))
print('a is Dog?', isinstance(a, Dog))
print('a is Cat?', isinstance(a, Cat))
#输出结果:
#a is Animal? True
#a is Dog? False
#a is Cat? False

print('d is Animal?', isinstance(d, Animal))
print('d is Dog?', isinstance(d, Dog))
print('d is Cat?', isinstance(d, Cat))
#输出结果:
#d is Animal? True
#d is Dog? True
#d is Cat? False

run_twice(c)
#输出结果:
#Cat is running...
#Cat is running...


#当调用run_twice()时,传入Tortoise的实例:
#不需要对run_twice()做任何修改
#任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行
run_twice(Tortoise())
#输出结果:
#Tortoise is running slowly...
#Tortoise is running slowly...

附上别人的代码进行辅助理解:

class Animal(object):   #编写Animal类
    def run(self):
        print("Animal is running...")

class Dog(Animal):  #Dog类继承Amimal类,没有run方法
    pass

class Cat(Animal):  #Cat类继承Animal类,有自己的run方法
    def run(self):
        print('Cat is running...')
    pass

class Car(object):  #Car类不继承,有自己的run方法
    def run(self):
        print('Car is running...')

class Stone(object):  #Stone类不继承,也没有run方法
    pass

def run_twice(animal):
    animal.run()
    animal.run()

run_twice(Animal())
run_twice(Dog())
run_twice(Cat())
run_twice(Car())
run_twice(Stone())

# 输出结果如下:
# Animal is running...
# Animal is running...
# Animal is running...
# Animal is running...
# Cat is running...
# Cat is running...
# Car is running...
# Car is running...

# AttributeError: 'Stone' object has no attribute 'run'
# 除石头吃了不会跑的亏外,其余的都能run,都是“鸭子”。

获取对象信息(待整理)

#使用type()函数来判断对象类型
print('type(123) =', type(123))
print('type(\'123\') =', type('123'))
print('type(None) =', type(None))
print('type(abs) =', type(abs))

import types

print('type(\'abc\')==str?', type('abc')==str)

# 输出结果如下:
# type(123) = <class 'int'>
# type('123') = <class 'str'>
# type(None) = <class 'NoneType'>
# type(abs) = <class 'builtin_function_or_method'>
# type('abc')==str? True
class MyObject(object):

    def __init__(self):
        self.x = 9

    def power(self):
        return self.x * self.x

obj = MyObject()

print('hasattr(obj, \'x\') =', hasattr(obj, 'x')) # 有属性'x'吗?
print('hasattr(obj, \'y\') =', hasattr(obj, 'y')) # 有属性'y'吗?
setattr(obj, 'y', 19) # 设置一个属性'y'
print('hasattr(obj, \'y\') =', hasattr(obj, 'y')) # 有属性'y'吗?
print('getattr(obj, \'y\') =', getattr(obj, 'y')) # 获取属性'y'
print('obj.y =', obj.y) # 获取属性'y'

print('getattr(obj, \'z\') =',getattr(obj, 'z', 404)) # 获取属性'z',如果不存在,返回默认值404

f = getattr(obj, 'power') # 获取属性'power'
print(f)
print(f())

示例属性和类属性(待整理)

由于Python是动态语言,根据类创建的实例可以任意绑定属性。给实例绑定属性的方法是通过实例变量,或者通过self变量:

# 写一个Student类,并定义一个类属性,这个属性归Student类所有,但是所有实例都可以访问到
class Student(object):
    name = 'Student'

 # 创建实例s
s = Student()
# 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性(向上查找)
print(s.name) 
# 输出为Student

# 打印类的name属性(原本就定义了类的name属性)
print(Student.name) 
# 输出为Student

# 给实例绑定name属性
s.name = 'Michael' 
# 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性(优先输出实例的属性)
print(s.name) 
# 但是类属性并未消失,用Student.name仍然可以访问
print(Student.name) 
# 分别输出为:Michael     Student

del s.name # 如果删除实例的name属性
print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了(再次向上访问)

总结:

  1. 实例属性属于各个实例所有,互不干扰;
  2. 类属性属于类所有,所有实例共享一个属性;
  3. 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。

面向对象高级编程

由面对对象的三个基础概念(封装、继承和多态)继续进行深入的延展——多重继承、定制类、元类等概念(面向对象高级编程)

使用__slots__

使用@property

在牛客网上的一些基础题上面经常碰到

多重继承

定制类

使用枚举类

使用元类

ToTOP