python 类和对象 (学习笔记)

这篇博客属于学习笔记,在记录重点内容的同时添加了自己的思考,方便后期复习和补充,主要内容来自于教程视频>>>黑马程序员python教程

面向过程:

把完成某个需求的所有步骤,用功能独立的代码封装成函数去实现一个一个的步骤,然后按顺序调用这些函数,最终完成需求。

面向对象:

根据需求分解出承担不同职责的若干个对象,在对象内部封装若干方法;这些对象描述了怎样去实现各自的职责,最后让不同的对象调用各自的方法共同去实现需求。

运用面向对象的思想去解决问题几个关键点:

确定职责 ——> 确定对象 ——> 封装方法

1、类和对象:

是对一群具有相同特征或则行为的事物的一个统称,是抽象的,不能直接使用,类的作用是创建对象

对象 是由类创建出来的一个具体的存在,可以直接使用。

打个比方,相当于制造飞机的图纸,对象相当于按照图纸建造出来的飞机;
一张图纸可以制造出多架飞机,每架飞机可以拥有不同颜色的喷漆;
一个类可以创建多个对象,每个对象都可以拥有不同的属性。

2、类的设计

设计一个类,通常要满足三个要素:

  1. 类名 大驼峰命名法(每个单词的首字母大写,直接拼接在一起,中间不带任何符号)
  2. 属性 对象的特征描述
  3. 方法 对象的行为

练习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__(初始化属性的方法)

当使用 类名( ) 创建对象时,会自动执行以下步骤:

  1. 为对象在内存中分配空间——创建对象
  2. 为对象的属性设置初始值——初始化方法__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、面对对象封装案例

需求:

  1. 房子(House)有户型、总面积、家具名称列表
    新房子没有任何家具
  2. 家具(HouseItem)有名字、占地面积,其中
    席梦思(bed)占地4平米
    衣柜(chest)占地2
    餐桌(table)占地1.5
  3. 将以上三件家具添加到房子中
  4. 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表

需求分析:

分担职责——> 设计两个类 (房类、家具类)——>设计封装方法
在这里插入图片描述
编写代码:

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、多态

多态:不同的子类对象, 调用相同的父类方法 , 产生不同的执行结果。

特点:

  • 多态可以 增加代码的灵活度
  • 继承重写父类方法 为前提
  • 是调用方法的技巧,不会影响到类的内部设计

直观感受 >>> 多态案例演练

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值