Python从入门到精通 第十一章(面向对象)

发布时间:2024年01月09日

一、类和对象

1、面向对象基本概念

(1)之前学习的编程方式是面向过程的,面向过程和面向对象是两种不同的编程方式。

(2)过程和函数:过程是早期的一个编程概念,过程类似于函数,只能执行,但是没有返回值;函数不仅能执行,还可以返回结果。

(3)面向过程——主要解决“怎么做”(如名片管理系统):

①面向过程的编程步骤:

[1]把完成某一个需求的所有步骤从头到尾逐步实现。

[2]根据开发需求,将某些功能独立的代码封装成一个又一个函数。

[3]最后完成的代码,就是顺序地调用不同的函数。

②面向过程的特点:

[1]注重步骤与过程,不注重职责分工。

[2]如果需求复杂,代码会变得很复杂。

[3]开发复杂项目,没有固定的套路,开发难度很大。

(4)面向对象——主要解决“谁来做”:

①相比较函数,面向对象是更大的封装,根据职责在 一个对象中封装多个方法。

②面向对象的编程步骤:

[1]在完成某一个需求前,首先确定职责——要做的事情(方法)。

[2]根据职责确定不同的对象,在对象内部封装不同的方法。

[3]最后完成的代码,就是顺序地让不同的对象调用不同的方法。

③面向对象的特点:

[1]注重对象和职责,不同的对象承担不同的职责。

[2]更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路。

[3]需要在面向过程基础上,再学习一些面向对象的语法。

2、类和对象的概念

类和对象是面向对象编程的两个核心概念。

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

①特征被称为属性,行为被称为方法。

②类就相当于制造飞机时的图纸,是一个模板,是负责创建对象的。

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

①由哪一个类创建出来的对象,就拥有在哪一个类中定义的属性和方法。

②对象就相当于根据图纸制造的飞机。

3、类和对象的关系

(1)类是模板,对象是根据类这个模板创建出来的,应该先有类,再有对象。

(2)类只有一个,而对象可以有很多个,不同的对象之间属性可能会各不相同。

(3)类中定义了什么属性和方法,对象中就有什么属性和方法,不可能多,也不可能少。

4、类的设计

(1)在使用面相对象开发前,应该首先分析需求,确定一下程序中需要包含哪些类。

(2)在程序开发中,要设计一个类,通常需要满足一下三个要素:

①类名:这类事物的名字,建议使用大驼峰命名法。

②属性:这类事物具有什么样的特征。

③方法:这类事物具有什么样的行为。

二、面向对象基础语法

1、dir内置函数

(1)在Python中,对象几乎是无所不在的,我们之前学习的变量、数据、函数都是对象。

(2)在标识符/数据后输入一个“.”,然后按下TAB 键,iPython会提示该对象能够调用的 方法列表。

(3)使用内置函数dir传入标识符/数据,可以查看对象内的所有属性及方法。其中提示“__方法名__”格式的方法是Python 提供的内置方法/属性。(下表所示是部分内置方法)

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

(1)面向对象是更大的封装,在一个类中封装多个方法,这样通过这个类创建出来的对象,就可以直接调用这些方法了。

(2)在Python中要定义一个只包含方法的类,语法格式如下:

class 类名:

????????def 方法1(self, 参数列表):

????????????????pass

????

????????def 方法2(self, 参数列表):

????????????????pass

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

(3)当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:

对象变量 = 类名()

(4)第一个面向对象程序:

需求:小猫爱吃鱼,小猫要喝水。

①定义一个猫类Cat。

②定义两个方法eat和drink。

③按照需求——不需要定义属性。

class Cat:
    """这是一个猫类"""

    def eat(self):
        print("小猫爱吃鱼")

    def drink(self):
        print("小猫要喝水")

tom = Cat()
tom.eat()
tom.drink()

lazy_cat = Cat()  # tom 和 lazy_cat 不是同一个对象!
lazy_cat.eat()
lazy_cat.drink()

(5)在面向对象开发中,引用的概念是同样适用的。在Python中使用类创建对象之后,tom 变量中仍然记录的是对象在内存中的地址,也就是tom变量引用了新建的猫对象。

3、方法中的self参数

(1)在Python中,给对象设置属性非常容易,只需要在类的外部的代码中直接通过“.”设置一个属性即可,但是不推荐使用,因为对象属性的封装应该封装在类的内部。

(2)在类的外部,通过“变量名.”访问对象的属性和方法,由哪一个对象调用的方法,方法内的self就是哪一个对象的引用。调用方法时,程序员不需要传递self参数。

(3)在类封装的方法内部,self就表示当前调用方法的对象自己。在类封装的方法中,通过“self.”访问对象的属性和方法。

(4)继续接上例,使用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()

print(tom)

# 再创建一个猫对象
lazy_cat = Cat()

lazy_cat.name = "大懒猫"

lazy_cat.eat()
lazy_cat.drink()

print(lazy_cat)

4、初始化方法

(1)上例代码存在的问题:在类的外部给对象增加属性。将上例代码进行调整,先调用方法,再设置属性,观察执行效果,会发现程序报错,这是在类的外部给对象增加属性容易引发的错误。

class Cat:

    def eat(self):
        # 哪一个对象调用的方法,self就是哪一个对象的引用
        print("%s 爱吃鱼" % self.name)

    def drink(self):
        print("%s 要喝水" % self.name)


# 创建猫对象
tom = Cat()

# 把这条语句往后搁,如果方法中需要使用name这个属性,但是这时name还未被添加,那么程序就会报错
# tom.name = "Tom"

tom.eat()
tom.drink()
tom.name = "Tom"

(2)在日常开发中,不推荐在类的外部给对象增加属性,如果在运行时没有找到属性,程序会报错。对象应该包含有哪些属性,应该封装在类的内部。

(3)当使用“类名()”创建对象时,会自动执行以下操作:

①为对象在内存中分配空间——创建对象。

②为对象的属性设置初始值——初始化方法(init)。这个初始化方法就是__init__方法,它是对象的内置方法,专门用来定义一个类具有哪些属性。

(4)在__init__方法内部使用“self.属性名 = 属性的初始值”就可以定义属性,定义属性之后,再使用该类创建的对象都会拥有该属性。

class Cat:

    def __init__(self):

        print("这是一个初始化方法")

        # self.属性名 = 属性的初始值  =>创建名字属性
        self.name = "Tom"

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

# 使用类名()创建对象的时候,会自动调用初始化方法 __init__
tom = Cat()

print(tom.name)

lazy_cat = Cat()
lazy_cat.eat()

(5)在开发中,如果希望在创建对象的同时就设置对象的属性,可以对__init__方法进行改造:

①把希望设置的属性值定义成__init__方法的参数。

②在方法内部使用“self.属性 = 形参”接收外部传递的参数。

③在创建对象时,使用“类名(属性1, 属性2...)”调用。

class Cat:

    def __init__(self, new_name):

        print("这是一个初始化方法")

        # self.属性名 = 属性的初始值
        # self.name = "Tom"
        self.name = new_name

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

# 使用类名()创建对象的时候,会自动调用初始化方法 __init__
tom = Cat("Tom")

print(tom.name)

lazy_cat = Cat("大懒猫")
lazy_cat.eat()

(6)在定义属性时,如果不知道设置什么初始值,可以设置为None。

①None表示什么都没有,表示一个没有方法和属性的空对象,是一个特殊的常量,可以将None赋值给任何一个变量。

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

③is与==区别:“is”用于判断两个变量引用对象是否为同一个;“==”用于判断引用变量的值是否相等。

5、__del__方法

(1)当使用“类名()”创建对象时,为对象分配完空间后,自动调用__init__方法;当一个对象被从内存中销毁前,会自动调用__del__方法。

(2)应用场景:如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__方法。

(3)一个对象的生命周期从调用“类名()”创建开始,一个对象的__del__方法一旦被调用,说明生命周期结束。在对象的生命周期内,可以访问对象属性,或者让对象调用方法。

class Cat:

    def __init__(self, new_name):

        self.name = new_name

        print("%s 来了" % self.name)

    def __del__(self):

        print("%s 我去了" % self.name)

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

# del 关键字可以删除一个对象,即使不用该关键字删除,对象也会在程序执行完成后自动销毁,只是生命周期不同罢了
del tom

print("-" * 50)

6、__str__方法

(1)在Python中,使用print输出对象变量,默认情况下会输出这个变量引用的对象是由哪一个类创建的对象,以及其在内存中的地址(十六进制表示)。

(2)如果在开发中,希望使用print输出对象变量时能够打印自定义的内容,就可以利用__str__这个内置方法了.

(3)注意,__str__方法必须返回一个字符串。

class Cat:

    def __init__(self, new_name):

        self.name = new_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)

三、面向对象封装案例

1、封装

(1)封装是面向对象编程的一大特点,面向对象编程的第一步就是将属性和方法封装到一个抽象的类中。

(2)外界使用类创建对象,然后让对象调用方法。

2、案例——小明爱跑步

(1)小明体重75.0公斤。

(2)小明每次跑步会减肥 0.5 公斤。

(3)小明每次吃东西体重增加1公斤。

class Person:

    """人类"""

    def __init__(self, person_name, person_weight):

        self.name = person_name
        self.weight = person_weight

    def run(self):

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

    def eat(self):

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

    def __str__(self):

        return "%s的体重是%.2f公斤" % (self.name, self.weight)

xiaoming = Person("小明", 75)
print(xiaoming)

xiaoming.run()
print(xiaoming)

xiaoming.eat()
print(xiaoming)

3、案例——摆放家具

(1)房子(House)有户型、总面积和家具名称列表三个属性,新房子没有任何的家具。

(2)家具(HouseItem)有名字和占地面积,其中席梦思(bed)占地4平米,衣柜(chest)占地 2平米,餐桌(table)占地1.5平米。

(3)将以上三件家具添加到房子中。

(4)打印房子时,要求输出户型、总面积、剩余面积、家具名称列表。

class HouseItem:

    def __init__(self, item_name, item_area):

        self.name = item_name
        self.area = item_area

    def __str__(self):

        return "%s占地%.2f平米" % (self.name, self.area)

class House:

    def __init__(self, area, house_type):

        self.house_list = []
        self.house_area = area
        self.house_free_area = area
        self.house_type = house_type
        print("新房子没有任何的家具")

    def add(self, house_item):

        if self.house_free_area < house_item.area:
            print("空间不足,添加失败")
        else:
            self.house_list.append(house_item.name)
            self.house_free_area -= house_item.area

    def __str__(self):

        return ("户型:%s\n总面积:%.2f平米\n剩余面积:%.2f平米\n家具名称列表:%s" %
                (self.house_type, self.house_area, self.house_free_area, self.house_list))


newhouse = House(100, "三室一厅")
for i in range(0, 9):
    newhouse.add(HouseItem("席梦思", 4))
    newhouse.add(HouseItem("衣柜", 2))
    newhouse.add(HouseItem("餐桌", 1.5))

print(newhouse)

四、私有属性和私有方法

1、应用场景及定义方式

(1)在实际开发中,对象的某些属性或方法可能只希望在对象的内部被使用,而不希望在外部被访问到,私有属性就是对象不希望公开的属性,私有方法就是对象不希望公开的方法。

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

class Women:

    def __init__(self, name):

        self.name = name
        # 不要问女生的年龄
        self.__age = 18

    def __secret(self):
        print("我的年龄是 %d" % self.__age)


xiaofang = Women("小芳")
# 私有属性,外部不能直接访问
# print(xiaofang.__age)

# 私有方法,外部不能直接调用
# xiaofang.__secret()

2、伪私有属性和私有方法

(1)需要注意的是,在日常开发中不要使用这种方式访问对象的私有属性或私有方法。

(2)在Python中并没有真正意义的私有,在给属性或方法命名时,实际是对名称做了一些特殊处理,使得外界无法访问到而已,外界如果想要访问私有属性或私有方法,只需要对名称做一些特殊处理即可(在名称前面加上“_类名”,得到“_类名__名称”)。

class Women:

    def __init__(self, name):

        self.name = name
        # 不要问女生的年龄
        self.__age = 18

    def __secret(self):
        print("我的年龄是 %d" % self.__age)


xiaofang = Women("小芳")
# 私有属性,外部不能直接访问到
print(xiaofang._Women__age)

# 私有方法,外部不能直接调用
xiaofang._Women__secret()

五、继承

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

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

(2)继承的语法:

class 类名(父类名):

????????pass

(3)子类继承自父类,可以直接享受父类中已经封装好的方法,不需要再次开发。子类中应该根据职责,封装子类特有的属性和方法。

(4)关于继承的专业术语(以上图为例):

①Dog类是Animal类的子类,Animal类是Dog类的父类,Dog类从Animal类继承。

②Dog类是Animal类的派生类,Animal类是Dog类的基类,Dog类从Animal类派生。

(5)继承的传递性:

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

②子类拥有父类以及父类的父类中封装的所有属性和方法。

(6)举例:

①例1:

class Animal:

    def eat(self):
        print("吃---")

    def drink(self):
        print("喝---")

    def run(self):
        print("跑---")

    def sleep(self):
        print("睡---")


class Dog(Animal):

    # 子类拥有父类的所有属性和方法
    # def eat(self):
    #     print("吃")
    #
    # def drink(self):
    #     print("喝")
    #
    # def run(self):
    #     print("跑")
    #
    # def sleep(self):
    #     print("睡")

    def bark(self):
        print("汪汪叫")

# 创建一个对象 - 狗对象
wangcai = Dog()

wangcai.eat()
wangcai.drink()
wangcai.run()
wangcai.sleep()
wangcai.bark()

②例2:

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("我会飞")


class Cat(Animal):

    def catch(self):
        print("抓老鼠")

# 创建一个哮天犬的对象
xtq = XiaoTianQuan()

xtq.fly()
xtq.bark()
xtq.eat()

xtq.catch()

2、方法的重写

(1)当父类的方法实现不能满足子类需求时,可以对方法进行重写(override)。

(2)重写父类方法有两种情况:覆盖父类的方法、对父类方法进行扩展。

①覆盖父类的方法:如果在开发中,父类的方法实现和子类的方法实现完全不同,就可以使用覆盖的方式,在子类中重新编写父类的方法实现。在子类中定义了一个和父类同名的方法并且实现重写之后,在运行时只会调用子类中重写的方法,而不会再调用父类封装的方法。

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):
        print("叫得跟神一样...")


xtq = XiaoTianQuan()

# 如果子类中,重写了父类的方法
# 在使用子类对象调用方法时,会调用子类中重写的方法
xtq.bark()

②对父类方法进行扩展:如果在开发中,子类的方法实现中包含父类的方法实现,也就是父类原本封装的方法实现是子类方法的一部分,这时可以使用扩展的方式。

[1]在子类中重写父类的方法。

[2]在需要的位置使用“super().父类方法”来调用父类方法的执行。

[3]代码其它的位置针对子类的需求,编写子类特有的代码实现。

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()

        # 父类名.方法(self)
        Dog.bark(self)
        # 注意:如果使用子类调用方法,会出现递归调用,从而引发死循环!
        # XiaoTianQuan.bark(self)

        # 3. 增加其他子类的代码
        print("$%^*%^$%^#%$%")

xtq = XiaoTianQuan()
# 如果子类中,重写了父类的方法,在使用子类对象调用方法时,会调用子类中重写的方法
xtq.bark()

(3)关于super:

①在Python中,super是一个特殊的类,“super()”就是使用super类创建出来的对象。

②super最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现。

③如果需要调用父类的方法,还可以使用以下方式(上例中有该方式的使用示例):

父类名.方法(self)

[1]这种方式,目前在Python 3.x还支持这种方式

[2]这种方法不推荐使用,因为一旦父类发生变化,方法调用位置的类名同样需要修改。

[3]在开发时,父类名和super()两种方式不要混用。

[4]如果使用当前子类名调用方法,会形成递归调用,出现死循环。

3、父类的私有属性和私有方法

(1)子类对象不能在自己的方法内部直接访问父类的私有属性或私有方法。

(2)子类对象可以通过父类的公有方法间接访问到私有属性或私有方法。

(3)举例:

①例1:

class A:

    def __init__(self):

        self.num1 = 100
        self.__num2 = 200

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


class B(A):

    def demo(self):

        # 1. 在子类的对象方法中,不能访问父类的私有属性
        # print("访问父类的私有属性 %d" % self.__num2)

        # 2. 在子类的对象方法中,不能调用父类的私有方法
        # self.__test()
        pass

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

b.demo()

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

②例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()
        pass

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

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

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

4、多继承

(1)子类可以拥有多个父类,并且具有所有父类的属性和方法,例如孩子会继承自己父亲和母亲的特性。

(2)多继承语法:

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

????????pass

(3)如果不同的父类中存在同名的方法,子类对象在调用方法时取决于继承顺序选择调用的方法。(开发时,应该尽量避免这种容易产生混淆的情况,如果父类之间存在同名的属性或者方法,应该尽量避免使用多继承)

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(B, A):
    """多继承可以让子类对象,同时具有多个父类的属性和方法"""
    pass


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

c.test()
c.demo()

(4)Python中针对类提供了一个内置属性__mro__,它可以查看方法搜索顺序。

①MRO的全称是method resolution order,主要用于在多继承时判断方法、属性的调用路径。

②使用方式(以上例为基础):

输入:print(C.__mro__)

输出:(<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

③在搜索方法时,是按照__mro__的输出结果从左至右的顺序查找的。(这里主要是针对多继承中有同名方法的情况)

[1]如果在当前类中找到方法,就直接执行,不再搜索。

[2]如果没有找到,就查找下一个类中是否有对应的方法,如果找到,就直接执行,不再搜索。

[3]如果找到最后一个类,还没有找到方法,程序报错。

5、新式类与旧式(经典)类

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

(2)新式类是以object为基类的类,推荐使用;经典类是不以object为基类的类,不推荐使用。

(3)在Python 3.x中定义类时,如果没有指定父类,会默认使用object作为该类的基类,也就是说,Python 3.x中定义的类都是新式类。在Python 2.x中定义类时,如果没有指定父类,则不会以object作为基类。

(3)新式类和 经典类在多继承时会影响到方法的搜索顺序,为了保证编写的代码能够同时在Python 2.x和Python 3.x运行,在定义类时,如果没有父类,建议统一继承自object。

class 类名(object):

????????pass

六、多态

1、多态概述

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

(2)多态的作用:增加代码的灵活度,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化。

(3)多态以继承和重写父类方法为前提,是调用方法的技巧,不会影响到类的内部设计。

2、举例

(1)在Dog类中封装方法game——普通狗只是简单的玩耍。

(2)定义XiaoTianDog继承自Dog,并且重写game方法——哮天犬需要在天上玩耍。

(3)定义Person类,并且封装一个和狗玩的方法,在方法内部,直接让狗对象调用game方法。

(4)在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果。

class Dog(object):

    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(object):

    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)

七、类属性和类方法

1、类的结构

(1)对象创建后,内存中就有了一个对象的实实在在的存在——实例。通常把创建出来的对象叫做类的实例,把创建对象的动作叫做实例化,把对象的属性叫做实例属性,把对象调用的方法叫做实例方法。

(2)在程序执行时,对象各自拥有自己的实例属性,可以通过“self.”访问自己的属性或者调用自己的方法。

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

2、类是一个特殊的对象

(1)Python中一切皆为对象:类是一个特殊的对象——类对象,比如“class AAA:”定义的类属于类对象;“obj1 = AAA()”创建的对象属于实例对象。

(2)在程序运行时,类同样会被加载到内存,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例。

(3)除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法,即类属性和类方法,通过“类名.”的方式可以访问类的属性或者调用类的方法。

3、类属性和实例属性

(1)类属性就是给类对象中定义的属性(使用赋值语句在class关键字下方可以定义类属性),通常用来记录与这个类相关的特征,不会用于记录具体对象的特征。

(2)示例需求(如何定义和访问类属性示例):定义一个工具类,每件工具都有自己的name,现在需要知道使用这个类创建了多少个工具对象。

class Tool(object):

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

    def __init__(self, name):
        self.name = name  # 实例属性

        # 针对类属性做一个计数+1
        Tool.count += 1


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")
tool1 = Tool("挖掘机")
tool2 = Tool("镐子")
tool3 = Tool("铁棒")

# 知道使用 Tool 类到底创建了多少个对象?
print("现在创建了 %d 个工具" % Tool.count)  # 现在创建了 6 个工具

(3)在Python中,属性的获取存在一个向上查找机制,因此,访问类属性有两种方式:

①类名.类属性。

②对象.类属性。(不推荐)

(4)需要注意的是,如果使用“对象.类属性 = 值”赋值语句,只会给对象添加一个属性,而不会影响到类属性的值。

class Tool(object):

    # 使用赋值语句定义类属性,记录所有工具对象的数量
    count = 0

    def __init__(self, name):
        self.name = name  # 实例属性

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


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

# 2. 输出工具对象的总数
tool3.count = 99
print("工具对象总数 %d" % tool3.count)
print("===> %d" % Tool.count)

4、类方法和静态方法

(1)类方法就是针对类对象定义的方法,在类方法内部可以直接访问类属性或者调用其它的类方法,语法如下:

@classmethod

def 类方法名(cls):

????????pass

①类方法需要用修饰器“@classmethod”来标识,告诉解释器这是一个类方法。

②类方法的第一个参数应该是cls,由哪一个类调用的方法,方法内的cls就是哪一个类的引用,这个参数和实例方法的第一个参数是self类似,使用其它名称也可以,不过一般习惯使用cls。

③通过“类名.”调用类方法,调用方法时,不需要传递cls参数。

④在方法内部可以通过“cls.”访问类的属性,也可以通过“cls.”调用其它的类方法。

class Tool(object):

    # 使用赋值语句定义类属性,记录所有工具对象的数量
    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)在开发时,如果需要在类中封装一个方法,这个方法既不需要访问实例属性或者调用实例方法,也不需要访问类属性或者调用类方法,这个时候可以把这个方法封装成一个静态方法,语法如下:

@staticmethod

def 静态方法名():

????????pass

①静态方法需要用修饰器“@staticmethod”来标识,告诉解释器这是一个静态方法。

②通过“类名.”调用静态方法。

class Dog(object):

    @staticmethod
    def run():
        # 不访问实例属性/类属性
        print("小狗要跑...")


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

(3)实例方法可以访问实例属性和类属性(以及调用实例方法和类方法),类方法只能访问类属性(和调用类方法),静态方法不能访问实例属性和类属性(以及不能调用实例方法和类方法)。

八、单例

1、单例设计模式

(1)设计模式是前人工作的总结和提炼,通常被人们广泛流传的设计模式都是针对某一特定问题的成熟的解决方案,使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。

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

2、__new__方法

(1)使用“类名()”创建对象时,Python的解释器首先会调用__new__方法为对象分配空间。

(2)__new__是一个由object基类提供的内置的静态方法,主要作用有两个:

①在内存中为对象分配空间。

②返回对象的引用,Python的解释器获得对象的引用后,将引用作为第一个参数(也就是self),传递给__init__方法。

(3)重写__new__ 方法的代码非常固定:

①重写__new__方法一定要“return super().__new__(cls)”,否则Python的解释器得不到分配了空间的对象引用,就不会调用对象的初始化方法。

②在调用__new__时需要主动传递cls参数。

3、单例的实现

(1)重写__new__方法:

①定义一个类属性,初始值是None,用于记录单例对象的引用。

②如果类属性is None,调用父类方法分配空间,并在类属性中记录结果。

③返回类属性中记录的对象引用。

(2)重写__init__方法:

①定义一个类属性init_flag标记是否执行过初始化动作,初始值为False。

②在__init__方法中判断init_flag,如果为False就执行初始化动作,然后将 init_flag 设置为True。

class MusicPlayer(object):

    # 记录第一个被创建对象的引用
    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)
文章来源:https://blog.csdn.net/Zevalin/article/details/135482375
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。