类、实例、封装、继承以及多态
这里的搜集与整理主要是为了方便自己,是在自己有一定理解的基础上进行相应的阐述,目的是能够真正的去把问题弄懂,弄清楚。涉及到面向对象编程以及面向对象高级编程,后面可以不断的加入一些新的东西进行,加深对一些概念的理解和掌握也是非常值得自己去理解的。
参考资料如下:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017501655757856
面向对象编程的重要概念:类和实例
面向对象的设计思想是抽象出Class,根据Class创建Instance。
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。
面对对象的三大特点:
- 数据封装
- 继承
- 多态
- 类: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
访问限值:
- 如果想让内部属性不被外部访问,属性前加___,变成私有变量,只有内部可以访问,外部不能访问
- 效果:确保外部代码不能随意修改对象内部的状态,通过访问限制的保护,代码更加健壮
- 不是一定不能从外部进行访问(不能访问的原因:python解释器把__name变量改成_Student__name,可以通过_Student__name来访问__name变量)
- 需要获取的时候可以在类中加入get_name和get_score的方法。
- 方法中可以对参数进行检查,避免传入无效的参数。
- 如果还想修改的话:在类中增加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)
- 动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。
- 动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
总结:
- 继承可以把父类的所有功能都直接拿过来,这样就不必重零做起
- 子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写
多态:
- 当我们定义了一个class的时候,实际上就定义了一种数据类型,定义的数据类型与python自带的数据类型差不多,可以用isinstance()函数来判断
- 调用只管调用,不管细节,而当我们新增一种Animal的字类的时候,主要确保run()方法编写正确,不用管原来的代码是如何调用的
- 对扩展开放:允许新增Animal子类
- 对修改封闭:不需要修改依赖Animal类型的run_twice()函数
- 可以理解为一种接口,多种实现,从而提高代码的扩展性,接口的复用性,有几个必要条件:继承、重写(重载)、父类引用指向子类对象(覆盖)
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属性就显示出来了(再次向上访问)
总结:
- 实例属性属于各个实例所有,互不干扰;
- 类属性属于类所有,所有实例共享一个属性;
- 不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
面向对象高级编程
由面对对象的三个基础概念(封装、继承和多态)继续进行深入的延展——多重继承、定制类、元类等概念(面向对象高级编程)
使用__slots__
使用@property
在牛客网上的一些基础题上面经常碰到