这篇博客属于学习笔记,在记录重点内容的同时添加了自己的思考,方便后期复习和补充,主要内容来自于教程视频>>>黑马程序员python教程
面向过程:
把完成某个需求的所有步骤,用功能独立的代码封装成函数去实现一个一个的步骤,然后按顺序调用这些函数,最终完成需求。
面向对象:
根据需求分解出承担不同职责的若干个对象,在对象内部封装若干方法;这些对象描述了怎样去实现各自的职责,最后让不同的对象调用各自的方法共同去实现需求。
运用面向对象的思想去解决问题几个关键点:
确定职责 ——> 确定对象 ——> 封装方法
1、类和对象:
类 是对一群具有相同特征或则行为的事物的一个统称,是抽象的,不能直接使用,类的作用是创建对象。
对象 是由类创建出来的一个具体的存在,可以直接使用。
打个比方,类相当于制造飞机的图纸,对象相当于按照图纸建造出来的飞机;
一张图纸可以制造出多架飞机,每架飞机可以拥有不同颜色的喷漆;
一个类可以创建多个对象,每个对象都可以拥有不同的属性。
2、类的设计
设计一个类,通常要满足三个要素:
- 类名 大驼峰命名法(每个单词的首字母大写,直接拼接在一起,中间不带任何符号)
- 属性 对象的特征描述
- 方法 对象的行为
练习1:
胖虎13岁,体重160KG,吃零食涨1KG,跑步瘦0.5KG。
静香12岁,体重45KG,吃零食涨1KG,跑步瘦0.5KG。
类名 | Person |
---|---|
属性 | age、weight |
方法 | eat( )、run( ) |
3、类的定义
class Cat(object): #也可以写成 Class Cat:
#定义属性
def __init__(self):
#暂无属性
pass
#定义方法
def eat(self):
print('小猫爱吃鱼')
def drink(self):
print('小猫要喝水')
#创建对象
tom = Cat()
#对象调用方法
tom.eat()
tom.drink()
在实际开发过程中,若没有要定义的属性或方法时,可以不写或者用 pass 占位,留待将来需要时补充。
3.1、self 参数的探索
下面两张图是对上述代码的调试,弄清 self 参数到底是一个怎样的存在。
运行到第10行时,可以看到 tom 变量指向了一个 Cat 对象,这个对象保存在内存中的一个地址里(请仔对比此地址和下图的地址);
继续单步调试,程序运行跳到了 eat 方法(第3行),此时发现 self 参数所显示的和 tom 变量一模一样,也就是说 self 就是变量 tom 指向的 Cat 对象。
结论:某个具体对象在调用方法时,self 就是该对象的引用,或者说 self 就表示当前对象。
4、类的内置方法
4.1、内置方法__init__(初始化属性的方法)
当使用 类名( ) 创建对象时,会自动执行以下步骤:
- 为对象在内存中分配空间——创建对象
- 为对象的属性设置初始值——初始化方法__init __
下图验证了创建对象时会自动调用初始化方法__init__
4.2、初始化时定义属性(区分属性名和形参名)
class Cat(object):
def __init__(self, name):
#self.属性名 = 属性初始值 (初始值可以直接定义也可以由参数传递)
self.name = name #(参数传递)
self.color = 'white' #(直接定义)
#等号左边name是属性名,右边name是形参,两者不同;
#可以修改形参名以便于区分,例如def __init__(self,new_name),效果一样
tom = Cat('Tom') #传递参数name
print(tom.name)
print(tom.color)
终端输出:
Tom
white
4.3、内置方法__del__
当使用 类名( ) 创建一个对象时,会为对象分配空间,并自动调用__init__方法;
当一个对象从内存中销毁时,会自动调用__del__方法。
用途:如果希望对象在被销毁前在做一些事情,可以用__def__方法。
class Cat(object):
def __init__(self, name):
self.name = name
print('%s 来了' % self.name)
def __del__(self):
print('%s 走了' % self.name)
tom = Cat('Tom')
print('大家好,我是%s' % tom.name)
print('程序运行结束,释放内存'.center(50,'*'))
终端输出:
Tom 来了
大家好,我是Tom
*******************程序运行结束,释放内存********************
Tom 走了 #对象被销毁前自动调用了__del__方法
#如果在最后一行代码前添加一行代码‘del tom’,删除对象,则输出会变成这样:
Tom 来了
大家好,我是Tom
Tom 走了
*******************程序运行结束,释放内存********************
4.4 、内置方法__str__
在python中,用print输出对象变量,默认情况下,会输出这个变量的类别及在内存中的地址;
如果我们希望用print输出对象变量时,能够输出自定义的内容,可以用__str__方法。
==注意:==使用__str__方法时必须保证返回的是一个字符串。
#未定义__str__方法
class Cat(object):
def __init__(self, name):
self.name = name
tom = Cat('Tom')
print(tom)
print(tom.name)
终端输出:
<__main__.Cat object at 0x00000221AB0A4860>
Tom
#定义__str__方法
class Cat(object):
def __init__(self, name):
self.name = name
def __str__(self):
return '我是小猫 %s' % self.name #使用保留字return
tom = Cat('Tom')
print(tom)
print(tom.name)
终端输出:
我是小猫 Tom
Tom
5、面对对象封装案例
需求:
- 房子(House)有户型、总面积、家具名称列表
新房子没有任何家具 - 家具(HouseItem)有名字、占地面积,其中
席梦思(bed)占地4平米
衣柜(chest)占地2
餐桌(table)占地1.5 - 将以上三件家具添加到房子中
- 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表
需求分析:
分担职责——> 设计两个类 (房类、家具类)——>设计封装方法
编写代码:
class HouseItem(object):
#家具类
def __init__(self,name,area):
self.name = name #家具名
self.area = area #家具占地面积
def __str__(self):
return '[%s] 占地 %.2f' % (self.name, self.area)
class House(object):
#房类
def __init__(self, house_type, area):
self.house_type = house_type #户型
self.area = area #房子总面积
self.free_area = area #房子剩余面积,初始化为总面积
self.item_list = [] #家具裂变,初始化空表
def __str__(self):
#python能够自动将一对括号内的代码连接在一起
return ('户型:{0}\n总面积:{1:.2f}\n剩余面积:{2:.2f}'.format
(self.house_type, self.area, self.free_area))
def add_item(self, item):
print('添加 %s' % item)
if item.area > self.free_area: #这里可以try异常处理改进代码
print('[%s] 面积太大,无法添加' % item.name)
return
#若执行return语句,则下两行的代码将不在执行
self.item_list.append(item)
self.free_area -= item.area
#创建家具对象
bed = HouseItem('席梦思', 4)
chest = HouseItem('衣柜', 2)
table = HouseItem('餐桌', 1.5)
#super_bed = HouseItem('超大床', 100) #测试用
'''调试代码
print(bed)
print(chest)
print(table)'''
#创建房子对象
my_home = House('三室一厅', 99)
#添加家具
my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
#my_home.add_item(super_bed) #测试用
print(my_home)
print('家具:')
for i in my_home.item_list:
print(i.name, end = ' ')
终端输出:
添加 [席梦思] 占地 4.00
添加 [衣柜] 占地 2.00
添加 [餐桌] 占地 1.50
户型:三室一厅
总面积:99.00
剩余面积:91.50
家具:
席梦思 衣柜 餐桌
6、访问限制(私有属性和方法)
当创建了一个对象,而又不希望该对象的内部属性被外部访问,可以在属性名称前加上 ‘ __ ’ 。在pytho中,实例对象的变量名如果以 ‘ __ ’开头,就变成了一个私有属性(private),只有内部可以访问,外部不能访问。
class Student(object):
def __init__(self,name,score):
self.name = name
self.__score = score #私有属性
stu_1 = Student('张三', 59)
print(stu_1.name)
print(stu_1.score)
终端输出:
张三 # name 属性可以访问, score 属性不被外部访问
Traceback (most recent call last):
...
AttributeError: 'Student' object has no attribute 'score'
若是想访问私有属性,可以定义 ‘ get_属性名(self) ’ 的方法
class Student(object):
def __init__(self,name,score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
stu_1 = Student('张三', 59)
print(stu_1.name)
print(stu_1.get_score())
终端输出:
张三
59
若是想修改私有属性,可以定义 ‘ set_属性名(self, arg) ’ 的方法
class Student(object):
def __init__(self,name,score):
self.name = name
self.__score = score
def get_score(self):
return self.__score
def set_score(self, new_score):
self.__score = new_score
stu_1 = Student('张三', 59)
print(stu_1.name)
stu_1.set_score(100)
print(stu_1.get_score())
终端输出:
张三
100
python中,并没有真正意义上的“私有属性”和“私有方法”,只是对其做了特殊处理,python解释器将 __score 变量改成了 _Student__score,所以下面这种方式可以在外部直接访问“私有属性”score:
print(stu_1._Student__score)
59
但是不建议这么干,不要随便访问私有属性和方法。
7、继承
当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
class Animal(object):
def eat(self):
print('Animal is eating !')
def run(self):
print('Animal is running !')
class Cat(Animal): #定义一个Cat类,继承自Animal类
pass
class Bird(Animal): #定义一个Bird类,继承自Animal类
pass
cat = Cat()
bird = Bird()
cat.run()
bird.run()
终端输出:
Animal is running !
Animal is running !
一旦子类继承了父类,自类就拥有了父类的全部功能;
如果父类还拥有父类,那么子类不仅会继承父类的功能,还会继承父类的父类的全部功能,也就是说继承具有传递性。
7.1、方法重写
当父类的方法不能满足子类的需求时,可以对方法进行重写:
class Cat(Animal):
def run(self):
print('Cat is running !')
class Bird(Animal):
def run(self):
print('Bird is flying !')
其他代码不变,再次运行:
Cat is running !
Bird is flying !
重写之后,程序运行时只会调用子类重写的方法
7.2、扩展父类方法
与方法重写类似,但不必将父类方法完全重写,区别在于父类的方法只能满足子类需求的一部分,在定义子类方法时,可以在可以在合适的位置使用super().父类方法 调用父类的方法。
super 是一个特殊的类,super() 是用super类创建的具体对象,常用于重写父类方法时,调用父类的方法。
class Bird(Animal):
def run(self):
super().run()
print('I can not only run,but also fly !')
#Animal.run(self) 也可以调用父类方法,但这是python2中的方式,不推荐使用
终端输出:
Animal is running !
I can not only run,but also fly !
7.3、访问父类私有属性和方法
class A(object):
def __init__(self):
self.__name = 'Iron Man'
def __test(self):
print('父类的私有方法')
class B(A):
def test(self):
try:
print(self.__name) #访问父类私有属性
except:
print('未能访问私有属性')
try:
self.__test() #访问父类私有方法
except:
print('未能访问私有方法')
temp = B()
temp.test()
终端输出:
未能访问私有属性
未能访问私有方法
上述代码证明了子类不能在自己的内部访问父类的私有属性和方法。
但子类可以通过父类的公有方法间接访问父类的私有方法:
class A(object):
def __init__(self):
self.__name = 'Iron Man'
def __test(self):
print('父类私有方法')
def test2(self):
print(self.__name)
self.__test()
class B(A):
def test3(self):
self.test2() #访问父类公有方法
temp = B()
temp.test2() #在子类外部访问
temp.test3() #在子类内部访问
终端输出:
Iron Man
父类私有方法
Iron Man
父类私有方法
8、多继承
多继承的好处是能够让子类同时继承多个父类的全部功能。
class A:
def test_A(self):
print('我是A类中的方法')
class B:
def test_B(self):
print('我是B类中的方法')
class C(A,B):
pass
temp = C()
temp.test_A()
temp.test_B()
终端输出:
我是A类中的方法
我是B类中的方法
多继承中需要特别注意的事项:父类之间尽量不要存在同名的属性和方法,避免出现混淆。
如:
class A:
def test(self):
print('我是A类中的test方法')
def demo(self):
print('我是A类中的demo方法')
class B:
def test(self):
print('我是B类中的test方法')
def demo(self):
print('我是B类中的demo方法')
class C(A,B):
pass
temp = C()
temp.test()
temp.demo()
8.1、MRO 方法搜索顺序(科普,可跳过)
python中针对 类 提供了一个 内置属性__mro__ ,可以查看方法顺序;
MRO(method resolution order),主要用于在多继承中判断方法、属性的调用路径。
class A:...
class B:...
class C(A,B):
pass
print(C.__mro__) # 类名.__mro__
终端输出:
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
终端输出了一个元组类型,它表示:
当C类对象调用某一个方法时,先从C类内部查找这个方法,有的话直接调用;若没有的话,按照元组中的顺序去下一个类中查找,也就是A类,以此类推,找到就调用,若直到最后的基类(obje)都没找到,则报错。
不妨尝试下改变C类的继承顺序:class C(B, A); 看看有什么变化。
建议:在实际开发中,若两个类中存在同名的属性或方法,则尽量不要使用多继承。
9、多态
多态:不同的子类对象, 调用相同的父类方法 , 产生不同的执行结果。
特点:
- 多态可以 增加代码的灵活度
- 以 继承 和 重写父类方法 为前提
- 是调用方法的技巧,不会影响到类的内部设计
直观感受 >>> 多态案例演练