Python面向对象 学习笔记

01 . 类和对象

1.1 类

类是对一群具有相同属性特征或者行为的事物的一个统称,是抽象的,不能直接使用。

  • 特征被称为属性
  • 行为被称为方法

类的三要素:

  • 类名(满足大驼峰命名法)
  • 属性(这类事物具有什么样的特征)
  • 方法(这类事物具有什么样的行为)

-大驼峰命名法:每个单词首字母大写,单词与单词之间没有下划线

类的创建

class Student: //Student为类的名称(类名)由一个或多个单词组成,每个单词的首字母大写,其余小写
    pass

类的组成:类属性,实例方法,静态方法,类方法

1.2 对象

  • 对象是由类创建出来的一个具体存在,可以直接使用
  • 由哪一个类创建出来的对象,就拥有哪一个类中的定义:
    • 属性
    • 方法
  • 先有类,再有对象
  • 只有一个,而对象可以有多个(不同对象之间属性可能会各不相同)

02 . 面向对象基本语法

面向对象三大特性

  1. 封装 根据职责实现将属性方法 封装到一个抽象的
  2. 继承 实现代码的重用,相同的代码不需要重复的编写
  3. 多态 不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活性

2.1 dir内置函数

# 语法:dir(函数名)
def demo():
    pass
dir(demo)

2.2 定义简单的类(只包含方法)

class 类名:
    def 方法1(self, 参数列表):
        pass
    def 方法2(self, 参数列表):
        pass   

方法的定义格式和函数几乎一样,区别在于第一个参数必须是self

2.3 创建对象

对象变量 = 类名()

2.4 第一个面向对象程序

class Cat:
    def eat(self):
        print('小猫爱吃鱼')
    def drink(self):
        print('小猫要喝水')
# 创建猫对象
tom = Cat()
tom.eat()
tom.drink()

------案例进阶------

class Cat:
    def eat(self):
        print('小猫爱吃鱼')
    def drink(self):
        print('小猫要喝水')
# 创建猫对象
tom = Cat()
tom.eat()
tom.drink()
print(tom)
# 再创建一个猫对象
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
print(lazy_cat)
lazy_cat2 = lazy_cat
print(lazy_cat2)

# 输出结果
小猫爱吃鱼
小猫要喝水
<__main__.Cat object at 0x00000223053A9460>
小猫爱吃鱼
小猫要喝水
<__main__.Cat object at 0x0000022305491130># lazy_cat和tom不是同一只猫(使用同一个类可以创建出多个对象)
<__main__.Cat object at 0x0000022305491130># lazy_cat和lazy_cat2表示的是同一只猫

2.5 方法中的self参数

2.5.1 案例改造----给对象增加属性

  • 只需要在类的外部的代码中直接通过 . 设置一个属性即可(但是不推荐使用)
tom.name = "Tom"
...
lazy_cat.name = "大懒猫"

2.5.2 使用self在方法内部输出每一只猫的名字

哪一个对象调用的方法,方法内的 self 就是哪一个对象的引用

  • 在类封装的方法内部,self 就表示当前调用方法的对象自己
  • 调用方法时,程序员不需要传递self参数
  • 在方法内部
    • 可以通过 self. 访问对象的属性
    • 也可以通过 self. 调用其他的对象方法
class Cat:
    def eat(self):
        # 哪一个对象调用的方法,self就是哪一个对象的引用
        print('%s爱吃鱼' % self.name)
    def drink(self):
        print('%s要喝水' % self.name)

# 创建猫对象
tom = Cat()
# 可以使用  .属性名  利用赋值语句就可以了
tom.name = "Tom"

tom.eat()
tom.drink()

# 再创建一个猫对象
lazy_cat = Cat()
lazy_cat.name = "大懒猫"
lazy_cat.eat()
lazy_cat.drink()
lazy_cat2 = lazy_cat

# 输出结果
Tom爱吃鱼
Tom要喝水
大懒猫爱吃鱼
大懒猫要喝水

2.6 初始化方法

2.6.1 之前代码存在的问题----在类的外部给对象增加属性

  • 将案例代码进行调整,先调用方法,再设置属性
tom = Cat()
# tom.name = "Tom"
tom.eat()
tom.drink()
tom.name = "Tom"
# 输出
AttributeError: 'Cat' object has no attribute 'name'
属性错误:'Cat'对象没有'name'属性
  • 对象应该包含哪些属性,应该封装在类的内部

2.6.2 初始化方法

  • 当使用 类名() 创建对象时,会自动执行以下操作:
    • 为对象在内存中分配空间----创建对象
    • 为对象的属性设置初始值----初始化方法(init)
  • 这个初始化方法就是__init__方法,__init__是对象的内置方法
  • __init__方法是专门用来定义一个类具有哪些属性的方法
class Cat:
    def __init__(self):
        print('这是一个初始化方法')
# 使用类名()创建对象时,会自动调用初始化方法
tom = Cat()

# 输出
这是一个初始化方法

2.6.3 在初始化方法内部定义属性

  • 在__init__方法内部使用 self.属性名 = 属性的初始值 就可以定义属性
  • 定义属性之后,再使用Cat类创建的对象,就会拥有该属性
class Cat:
    def __init__(self):
        print('这是一个初始化方法')

        # 定义用Cat类创建的猫对象都有一个name的属性
        self.name = 'Tom'

    def eat(self):
        print('%s爱吃鱼' % self.name)


# 使用类名()创建对象时,会自动调用初始化方法
tom = Cat()
# print(tom.name)
tom.eat()

# 输出
这是一个初始化方法
Tom爱吃鱼

2.6.4 改造初始化方法----初始化的同时设置初始值

  • 在开发中,如果希望在创建对象的同时,就设置对象的属性,可以对__init__方法进行改造
    • 把希望设置的属性值,定义成__init__方法的参数
    • 把方法内部使用 self.属性 = 形参 接收外部传递的参数
    • 在创建对象时,使用 类名(属性1,属性2...)调用
class Cat:
    def __init__(self, new_name):

        # 定义用Cat类创建的猫对象都有一个name的属性
        # self.name = 'Tom'
        self.name = new_name

    def eat(self):
        print('%s爱吃鱼' % self.name)


# 使用类名()创建对象时,会自动调用初始化方法
tom = Cat('Tom')
print(tom.name)
# tom.eat()
lazy_cat = Cat('大懒猫')
lazy_cat.eat()

# 输出
Tom
大懒猫爱吃鱼

2.7 内置方法和属性

  • __del__ 方法:对象被从内存中销毁前,会被自动调用
  • __str__ 方法:返回对象的描述信息,print函数输出使用

2.7.1 __del__ 方法

  • 在Python中
    • 当使用类名()创建对象时,为对象分配完空间后自动调用__init__方法
    • 当一个对象被从内存中销毁前,会自动调用__del__方法
  • 生命周期
    • 一个对象从调用类名()创建,生命周期开始
    • 一个对象的__del__方法一旦被调用,生命周期结束
class Cat:

    def __init__(self, name):
        self.name = name
        print('%s 来了' % self.name)

    def __del__(self):
        print('%s 走了' % self.name)


# tom是一个全局变量
tom = Cat('Tom')
print(tom.name)

# del关键字可以删除一个对象
del tom

print('-' * 50)

# 输出
Tom 来了
Tom
Tom 走了  # tom是一个全局变量,若不使用del关键字,分割线会在__del__方法被调用之前输出
--------------------------------

2.7.2 __str__方法

  • 在python中,使用print输出对象变量,默认情况下,会输出这个变量引用的对象由哪一个类创建的对象,以及在内存中的地址(十六进制表示)
  • 如果在开发中,希望使用print输出对象变量时,能够打印自定义内容,就可以利用__str__这个内置方法了
  • 注意:__str__方法必须返回一个字符串
class Cat:

    def __init__(self, name):
        self.name = name
        print('%s 来了' % self.name)

    def __del__(self):
        print('%s 走了' % self.name)

    def __str__(self):
        # 必须返回一个字符串
        return '我是小猫[%s]' % self.name


# tom是一个全局变量
tom = Cat('Tom')
print(tom)

# 输出
Tom 来了
我是小猫[Tom]
Tom 走了

面向对象封装案例----01

 

class Person:
    def __init__(self, name, weight):
        # self.属性 = 形参
        self.name = name
        self.weight = weight

    def __str__(self):
        # 必须返回字符串
        return '我的名字叫%s 体重是%.2f公斤' % (self.name, self.weight)

    def run(self):
        print('%s爱跑步,跑步锻炼身体' % self.name)
        self.weight -=0.5

    def eat(self):
        print('%s是吃货,吃完这顿再减肥' % self.name)
        self.weight += 1


xiaoming = Person('小明', 75.0)

xiaoming.run()
xiaoming.eat()

print(xiaoming)

# 输出
小明爱跑步,跑步锻炼身体
小明是吃货,吃完这顿再减肥
我的名字叫小明 体重是75.50公斤

案例扩展

 

class Person:
    def __init__(self, name, weight):
        # self.属性 = 形参
        self.name = name
        self.weight = weight

    def __str__(self):
        # 必须返回字符串
        return '我的名字叫%s 体重是%.2f公斤' % (self.name, self.weight)

    def run(self):
        print('%s爱跑步,跑步锻炼身体' % self.name)
        self.weight -=0.5

    def eat(self):
        print('%s是吃货,吃完这顿再减肥' % self.name)
        self.weight += 1


xiaoming = Person('小明', 75.0)

xiaoming.run()
xiaoming.eat()

print(xiaoming)

# 小美
xiaomei = Person('小美', 45)
xiaomei.eat()
xiaomei.run()
print(xiaomei)

# 输出
小明爱跑步,跑步锻炼身体
小明是吃货,吃完这顿再减肥
我的名字叫小明 体重是75.50公斤
小美是吃货,吃完这顿再减肥
小美爱跑步,跑步锻炼身体
我的名字叫小美 体重是45.50公斤
  • 对象的方法内部,是可以直接访问对象的属性
  • 同一个类创建的多个对象之间,属性互不干扰

面向对象封装案例----02

class HouseItem:

    def __init__(self, name, area):
        self.name = name
        self.area = area

    def __str__(self):
        return '[%s] 占地 %.2f' % (self.name, self.area)


class House:

    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 ('户型:%s\n总面积:%.2f[剩余面积:%.2f]\n家具:%s'
                % (self.house_type, self.area,
                   self.free_area, self.item_list))

    def add_item(self, item):
        print('要添加 %s' % item)
        # 1.判断家具的面积
        if item.area > self.free_area:
            print('%s的面积太大了,无法添加' % item.name)
            return
        # 2.将家具的名称添加到列表中
        self.item_list.append(item.name)
        # 3.计算剩余面积
        self.free_area -= item.area


# 1.创建家具
bed = HouseItem('席梦思', 40)
print(bed)
chest = HouseItem('衣柜', 2)
print(chest)
table = HouseItem('餐桌', 20)
print(table)

# 2.创建房子对象
my_home = House('两室一厅', 60)

my_home.add_item(bed)
my_home.add_item(chest)
my_home.add_item(table)
print(my_home)

# 输出
[席梦思] 占地 40.00
[衣柜] 占地 2.00
[餐桌] 占地 20.00
要添加 [席梦思] 占地 40.00
要添加 [衣柜] 占地 2.00
要添加 [餐桌] 占地 20.00
餐桌的面积太大了,无法添加
户型:两室一厅
总面积:60.00[剩余面积:18.00]
家具:['席梦思', '衣柜']
  • 主程序只负责创建房子对象和家具对象
  • 让房子对象调用add_item方法将家具添加到房子
  • 面积计算、剩余面积、家具列表等处理都被封装到房子类的内部

面向对象封装案例----03

  • 一个对象的属性可以是另外一个类创建的对象

  • 定义没有初始值的属性
    • 在定义属性时,如果不知道设置什么初始值,可以设置为None
      • None关键字表示什么都没有
      • 表示一个空对象没有方法和属性是一个特殊的常量
      • 可以将None赋值给任何一个变量
class Gun:

    def __init__(self, model):
        # 1.枪的型号
        self.model = model
        # 2.子弹的数量
        self.bullet_count = 0

    def add_bullet(self, count):
        self.bullet_count += count

    def shoot(self):
        # 1.判断子弹数量
        if self.bullet_count <= 0:
            print('[%s]没有子弹了。。。' % self.model)
            return
        # 2.发射子弹
        self.bullet_count -= 1
        # 3.提示发射信息
        print('[%s]突突突。。。[%s]' % (self.model, self.bullet_count))


class Soldier:

    def __init__(self, name):
        # 1.姓名
        self.name = name
        # 2.枪 --- 新兵没有枪
        self.gun = None

    def fire(self):
        # 1.判断士兵是否有枪
        # if self.gun == None:
        if self.gun is None:
            print('[%s]还没有枪' % self.name)
            return
        # 2.高喊口号
        print('冲啊。。。[%s]' % self.name)
        # 3.让枪装填子弹
        self.gun.add_bullet(50)
        # 4.让枪发射子弹
        self.gun.shoot()


# 1.创建枪对象
ak47 = Gun('AK47')

# ak47.add_bullet(50)
# ak47.shoot()

# 2.创建许三多
xusanduo = Soldier('许三多')

xusanduo.gun = ak47
xusanduo.fire()
# print(xusanduo.gun)

# 输出
冲啊。。。[许三多]
[AK47]突突突。。。[49]

身份运算符

身份运算符用于比较两个对象的内存地址是否一致 —— 是否是对同一个对象的引用

  • 在python中针对None比较时,建议使用is判断

运算符

描述

实例

is

is是判断两个标识符是不是引用同一个对象

x is y,类似 id(x) == id(y)

is not

is not是判断两个标识符是不是引用不同对象

x is not y,类似 id(x) != id(y)

  • is 与 == 区别:
    • is 用于判断两个变量引用对象是否为同一个
    • == 用于判断引用变量的值是否相等
a = [1,2,3]
b = [1,2,3]
b is a
>>>False
b == a
>>>True

2.8 私有属性和私有方法

定义方式

  • 在定义属性或方法时,在属性名或者方法名前增加两个下划线,定义的就是私有属性或方法

伪私有属性和私有方法(科普)

提示:在日常开发中,不要使用这种方式,访问对象的私有属性或私有变量

python中,并没有真正意义的私有

  • 在给属性、方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到
  • 处理方式:在名称前面加上 _类名 ==> _类名__名称
class Women:
    def __init__(self, name):
        self.name = name
        self.__age = 18

    def __secret(self):
        # 在对象的方法内部,是可以访问对象的私有属性的
        print('%s的年龄是%d' % (self.name, self.__age))


xiaofang = Women('小芳')

# 伪私有属性在外界不能够被直接访问
# print(xiaofang.__age)
print(xiaofang._Women__age)
# 伪私有方法,同样不允许在外界直接访问
# xiaofang.__secret()
xiaofang._Women__secret()

# 输出
18
小芳的年龄是18

2.9 继承

2.9.1 单继承

2.9.1.1 继承的概念、语法和特点

继承的概念:子类拥有父类的所有方法和属性

继承的语法:

class 类名(父类名):
    pass
  • 子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发
  • 子类中应该根据职责,封装子类特有的属性和方法
class Animal:
    
    def eat(self):
        print('吃')

    def drink(self):
        print('喝')

    def run(self):
        print('跑')

    def sleep(self):
        print('睡')


class Dog(Animal):
    # 子类拥有父类的所有属性和方法
    def dark(self):
        print('叫')
        
        
wangcai = Dog()
wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.dark()   

继承的传递性:

  • C类从B类继承,B类又从A类继承
  • 那么C类就具有B类和A类的所有属性和方法

2.9.1.2 方法的重写

  • 当父类方法实现不能满足子类需求时,可以对方法进行重写

 

重写父类方法有两种情况

  1. 覆盖父类的方法
  2. 对父类方法进行扩展

1) 覆盖父类的方法

  • 如果在开发中,父类的方法实现子类的方法实现完全不同
  • 就可以使用覆盖的方式,在子类中重新编写父类的方法实现

具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现

重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法

2) 对父类方法进行扩展

  • 如果在开发中,子类的方法实现中包含父类的方法实现
    • 父类原本封装的方法实现是子类方法的一部分
  • 就可以使用扩展的方式
  1. 在子类中重写父类的方法
  2. 在需要的位置使用 super().父类方法 来调用父类方法的执行
  3. 代码其他的位置针对子类的需求,编写子类特有的代码实现

关于super

  • 在python中 super一个特殊的类
  • super() 就是使用super类创建出来的对象
  • 最常使用的场景就是重写父类方法时,调用在父类中封装的方法实现
class Animal:

    def eat(self):
        print('吃')

    def drink(self):
        print('喝')

    def run(self):
        print('跑')

    def sleep(self):
        print('睡')


class Dog(Animal):
    # 子类拥有父类的所有属性和方法
    def bark(self):
        print('叫')


class XiaoTianQuan(Dog):

    def fly(self):
        print('飞')

    def bark(self):
        # 1.针对子类特有的需求,编写代码
        print('叫的厉害')
        # 2.使用super(). 调用原本在父类中封装的方法
        super().bark()
        # 3.增加其他子类的代码
        print('&******')


xtq = XiaoTianQuan()
xtq.bark()

# 输出
叫的厉害
叫
&******

调用父类的另一种方式(知道)

父类名.方法(self)
  • 不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改
  • 在开发中,父类名super()两种方式不要混用
  • 如果使用当前子类名调用方法,会形成递归调用,出现死循环

2.9.2 父类的私有属性和私有方法

  1. 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法
  2. 子类对象可以通过父类公有方法间接访问到私有属性或私有方法
  • 私有属性、方法是对象的隐私,不对外公开,外界以及子类都不能直接访问
  • 私有属性、方法通常用于做一些内部的事情

class A:
    def __init__(self):
        self.num1 = 100
        self.__num2 = 200

    def __test(self):
        print('私有方法 %d %d' % (self.num1, self.__num2))

    def test(self):
        print('父类的公有方法 %d' % self.__num2)

        self.__test()


class B(A):
    def demo(self):
        # 1.在子类的对象方法中,不能访问父类的私有属性
        # print('访问父类的私有属性 %d' % self.__num2)
        # 2.在子类的对象方法中,不能调用父类的私有方法
        # self.__test

        # 3.访问父类的公有属性
        print('子类方法 %d' % self.num1)
        # 4.调用父类的公有方法
        self.test()


# 创建一个子类对象
b = B()
# print(b)

b.demo()
# 在外界访问父类的私有属性或调用私有方法
# print(b.num1)
# b.test()

# 在外界不能直接访问对象的私有属性或调用私有方法
# print(b.__num2)
# b.__test

# 输出
子类方法 100
父类的公有方法 200
私有方法 100 200
B的对象不能直接访问__num2属性

 

  • B的对象不能直接访问__num2属性
  • B的对象不能在demo方法内访问__num2属性
  • B的对象可以在demo方法内,调用父类的test方法
  • 父类的test方法内部,能够访问__num2属性和__test方法

2.9.3 多继承

概念:子类可以拥有多个父类,并且具有所有父类的属性和方法

语法:

class 子类名(父类名1, 父类名2...):
    pass

注意事项

  • 如果不同的父类中存在同名的属性或方法,应该尽量避免使用多继承
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


# 创建子类对象
c = C()
c.test()
c.demo()

# 输出
A --- test方法
A --- demo方法

python中的MRO——方法搜索顺序

  • python中针对提供了一个内置属性 __mro__ 可以查看方法搜索顺序
  • MRO是method resolution order,主要用于在多继承时判断方法、属性的调用路径
print(C.__mro__)

输出结果

(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
  • 在搜索方法时,是按照__mro__的输出结果从左至右的顺序查找的

新式类与旧时(经典)类

object是python为所有对象提供的基类,提供有一些内置的属性和方法,可以使用dir函数查看

  • 新式类:以object为基类的类,推荐使用
  • 经典类:不以object为基类的类,不推荐使用

新式类和经典类在多继承时,会影响到方法的搜索顺序

在定义类时,如果没有父类,建议统一继承自object

class 类名(object):
    pass

2.10 多态

不同的对象调用相同的方法,产生不同的执行结果

多态案例演练

 

class Dog:
    def __init__(self, name):
        self.name = name

    def game(self):
        print('%s 蹦蹦跳跳的玩耍。。。。' % self.name)


class XiaoTianDog(Dog):
    def game(self):
        print('%s 飞到天上去玩耍。。。' % self.name)


class Person:
    def __init__(self, name):
        self.name = name

    def game_with_dog(self, dog):
        print('%s 和 %s 快乐的玩耍。。。' % (self.name, dog.name))
        # 让狗玩耍
        dog.game()


# 1.创建一个狗对象
# wangcai = Dog('旺财')
wangcai = XiaoTianDog('飞天旺财')

# 2.创建一个小明对象
xiaoming = Person('小明')

# 3.让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)


# 输出
# 小明 和 旺财 快乐的玩耍。。。
# 旺财 蹦蹦跳跳的玩耍。。。。
小明 和 飞天旺财 快乐的玩耍。。。
飞天旺财 飞到天上去玩耍。。。

03. 类

3.1 实例

  1. 使用面向对象开发,第一步是设计类
  2. 使用 类名() 创建对象,创建对象的动作有两步:
    1. 在内存中为对象分配空间
    2. 调用初始化方法 __init__ 对象初始化
  3. 对象创建后,内存中就有了一个对象的实实在在的存在——实例

 

因此,通常也会把:

  1. 创建出来的对象叫做类的实例
  2. 创建对象的动作叫做实例化
  3. 对象的属性叫做实例属性
  4. 对象调用的方法叫做实例方法

在程序执行时:

  1. 对象各自拥有自己的实例属性
  2. 调用对象方法,可以通过 self.
    1. 访问自己的属性
    2. 调用自己的方法

结论

  • 每一个对象都有自己独立的内存空间,保存各自不同的属性
  • 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用传递到方法内部

3.2 类是一个特殊的对象

Python中一切皆对象

  • class AAA: 定义的类属于类对象
  • obj1 = AAA() 属于实例对象
  • 在程序运行时,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例
  • 除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法
    • 类属性
    • 类方法
  • 通过 类名. 的方式可以访问类的属性或者调用类的方法

 

3.3 类属性和实例属性

3.3.1 概念和使用

  • 类属性就是给类对象中定义的属性
  • 通常用来记录与这个类相关的特征
  • 类属性不会用于记录具体对象的特征

示例

 

class Tool:
    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

    def __init__(self, name):
        self.name = name

        # 让类属性的值+1
        Tool.count += 1


# 1.创建工具对象
tool1 = Tool('斧头')
tool2 = Tool('榔头')
tool3 = Tool('水桶')

# 2.输出工具对象的总数
print(Tool.count)  # 类名.类属性
# print('工具对象总数 %d' % tool3.count)  # 对象.类属性

# 输出
3
# 工具对象总数 3

3.3.2 属性的获取机制(科普)

  • 在python中属性的获取存在一个向上查找机制

 

  • 因此,要访问类属性有两种方式
    • 类名.类属性
    • 对象.类属性(不推荐)
  • 如果使用 对象.类属性 = 值 赋值语句,只会给对象添加一个属性,而不会影响到类属性的值

3.4 类方法和静态方法

3.4.1 类方法

  • 类属性 就是针对 类对象 定义的属性
    • 使用赋值语句 class 关键字下方可以定义 类属性
    • 类属性 用于记录与这个类相关的特征
  • 类方法 就是针对 类对象 定义的方法
    • 类方法内部可以直接访问 类属性 或者调用其他的 类方法

语法:

@classmethod
def 类方法名(cls):
    pass
  • 类方法需要用修饰器 @classmethod 来标识,告诉解释器这是一个类方法
  • 类方法第一个参数应该是 cls
    • 哪一个类调用的方法,方法内的cls就是哪一个类的引用
    • 这个参数和实例方法的第一个参数self类似
    • 提示 使用其他名称也可以,不过习惯使用cls
  • 通过 类名. 调用类方法,调用方法时,不需要传递cls参数
  • 在方法内部
    • 可以通过 cls. 访问类的属性
    • 也可以通过 cls. 调用其他的类方法

示例

 

class Tool:
    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

    @classmethod
    def show_tool_count(cls):
        print('工具对象的数量 %d' % cls.count)


    def __init__(self, name):
        self.name = name

        # 让类属性的值+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool('斧头')
tool2 = Tool('榔头')

# 调用类方法
Tool.show_tool_count()

# 输出
工具对象的数量 2

3.4.2 静态方法

  • 在开发时,如果需要在类中封装一个方法,这个方法:
    • 不需要访问实例属性或者调用实例方法
    • 不需要访问类属性或者调用类方法
  • 这个时候,可以把这个方法封装成一个静态方法

语法:

@staticmethod
def 静态方法名():
    pass
  • 静态方法需要用修饰器 @staticmethod 来标识,告诉解释器这是一个静态方法
  • 通过 类名. 调用静态方法
class Dog:

    @staticmethod
    def run():

        # 不访问实例属性/类属性,就可以把这个方法定义为静态方法
        print('小狗要跑。。。')


# 通过 类名. 调用静态方法  ---不需要创建对象
Dog.run()

# 输出
小狗要跑。。。

3.5 方法综合案例

 

class Game:
    # 历史最高分
    top_score = 0

    # 在初始化方法中定义实例属性
    def __init__(self, player_name):
        self.player_name = player_name

    @staticmethod
    def show_help():
        print('帮助信息:让僵尸进入大门')

    @classmethod
    def show_top_score(cls):
        print('历史记录 %d' % cls.top_score)

    def start_game(self):
        print('%s 开始游戏啦。。。' % self.player_name)


# 1.查看游戏的帮助信息
Game.show_help()
# 2.查看历史最高分
Game.show_top_score()
# 3.创建游戏对象
game = Game('小明')
game.start_game()

# 输出
帮助信息:让僵尸进入大门
历史记录 0
小明 开始游戏啦。。。

案例小结

  1. 实例方法——方法内部需要访问 实例属性
    1. 实例方法 内部可以使用 类名. 访问类属性
  2. 类方法——方法内部 只 需要访问 类属性
  3. 静态方法——方法内部,不需要访问 实例属性 和 类属性
  • 方法内部既需要访问实例属性,又需要访问类属性,应该定义什么方法?
    • 应该定义 实例方法
    • 因为 类只有一个,在实例方法内部可以使用 类名. 访问类属性

04. 单例

  • 单例设计模式
    • 目的 —— 让 创建的对象,在系统中只有唯一的一个实例
    • 每一次执行 类名() 返回的对象,内存地址是相同的

4.1 __new__ 方法

  • 使用 类名() 创建对象时,python的解释器首先会调用__new__方法为对象分配空间
  • __new__ 是一个由object基类提供的内置的静态方法,主要作用有两个:
    • 在内存中为对象分配空间
    • 返回对象的引用
  • python的解释器获得对象的引用后,将引用作为第一个参数,传递给__init__方法
  • 重写 __new__ 方法的代码非常固定!!
  • 重写 __new__ 方法一定要 return super().__new__(cls)
  • 否则python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法
  • 注意:__new__ 是一个静态方法,在调用时需要主动传递cls参数


 

class MusicPlayer:
    def __new__(cls, *args, **kwargs):
        # 1.创建对象时,new方法会被自动调用
        print('创建对象,分配空间')
        # 2.为对象分配空间
        instance = super().__new__(cls)
        # 3.返回对象的引用
        return instance

    def __init__(self):
        print('播放器初始化')


# 创建播放器对象
player = MusicPlayer()
print(player)

# 输出
创建对象,分配空间
播放器初始化
<__main__.MusicPlayer object at 0x0000027A4AF76D90>

4.2 python中的单例

  • 单例 —— 让 创建的对象,在系统中只有唯一的一个实例
    • 定义一个类属性,初始值是None,用于记录单例对象的引用
    • 重写 __new__ 方法
    • 如果类属性 is None,调用父类方法分配空间,并在类属性中记录结果
    • 返回类属性中记录的对象引用

class MusicPlayer:
    # 记录第一个被创建对象的引用
    instance = None

    def __new__(cls, *args, **kwargs):
        # 1.判断类属性是否是空对象
        if cls.instance is None:
            # 2.调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)
        # 3.返回类属性保存的对象引用
        return cls.instance


# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

# 输出
<__main__.MusicPlayer object at 0x000002441FE8C400>
<__main__.MusicPlayer object at 0x000002441FE8C400>

只执行一次初始化工作

  • 在每次使用 类名() 创建对象时,python的解释器都会自动调用两个方法:
    • __new__ 分配空间
    • __init__ 对象初始化
  • 之前对 __new__ 方法改造后,每次都会得到第一次被创建对象的引用
  • 但是,初始化方法还会被再次调用

需求

  • 让初始化动作只被执行一次

解决办法

  1. 定义一个类属性 init_flag 标记是否执行过初始化动作,初始值为 False
  2. 在 __init__ 方法中,判断 init_flag ,如果为 Flase 就执行初始化动作
  3. 然后将 init_flag 设置为 True
  4. 这样,再次自动调用 __init__ 方法时,初始化动作就不会被再次执行了

class MusicPlayer:
    # 记录第一个被创建对象的引用
    instance = None
    # 记录是否执行过初始化动作
    init_flag = False

    def __new__(cls, *args, **kwargs):
        # 1.判断类属性是否是空对象
        if cls.instance is None:
            # 2.调用父类的方法,为第一个对象分配空间
            cls.instance = super().__new__(cls)
        # 3.返回类属性保存的对象引用
        return cls.instance

    def __init__(self):
        # 1.判断是否执行过初始化动作
        if MusicPlayer.init_flag:
            return
        # 2.如果没有执行过,再执行初始化动作
        print('初始化播放器')
        # 3.修改类属性的标记
        MusicPlayer.init_flag = True


# 创建多个对象
player1 = MusicPlayer()
print(player1)

player2 = MusicPlayer()
print(player2)

# 输出
初始化播放器
<__main__.MusicPlayer object at 0x000002E1B7E2C400>
<__main__.MusicPlayer object at 0x000002E1B7E2C400>

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值