二、面向对象的使用

1. 类和对象的概念

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

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

对象

对象 是由类创建出来的一个具体存在,可以直接使用,由哪一个类创建出来的对象,就拥有在 哪一个类 中定义的属性和方法

类和对象的关系

  • 类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
  • 类 只有一个,而 对象 可以有很多个
  • 类 中定义了什么 属性和方法,对象 中就有什么属性和方法

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

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

class 类名:

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

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

注意

  • 类名 的 命名规则 要符合 大驼峰命名法
  • 调用类的方法时,不需要传递 self 参数

例如:

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

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

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

3. 创建对象

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

对象变量 = 类名()

例如上面定义的Cat类之后创建对象可以这样操作:

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

注意:
在 Python 中使用类 创建对象之后,tom 变量中 仍然记录的是 对象在内存中的地址,也就是 tom 变量 引用 了 新建的Cat对象。
使用 print 输出 对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示),例如:
<main.Cat object at 0x1031a0a00>

4. self参数

在 Python 中,要 给对象设置属性,非常的容易, 只需要 对象名 . 属性名 =xxx 的形式就可以设置一个属性了, 例如:

tom = Cat()
# 设置对象的属性 name
tom.name = "Tom"
print(tom.name)

但是这种在类的外部定义属性的方式是不推荐使用, 因为不利于维护, 另外在运行时,没有找到属性,程序会报错, 通常会将属性定义在类的内部, 稍后会介绍。

当调用对象的方法时, 方法的第一个参数self就指代当前调用者对象, 例如:

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

    def eat(self, food):
        print("self==> %s, 正在吃:%s" % (self, food))


tom = Cat()
tom.eat("鱼")
print("tom==>", tom)

输出结果:

self==> <__main__.Cat object at 0x102c74a00>, 正在吃:鱼
tom==> <__main__.Cat object at 0x102c74a00>

从结果可以看出self和tom的输出的引用指向的地址都是同一个对象, 由此也可以证明self代表的就是调用该方法的对象。
既然self指代的是当前类的对象, 那么自然可以通过self来访问该对象的 属性和方法

5. 类的初始化方法

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

  • 为对象在内存中 分配空间 —— 创建对象
  • 为对象的属性 设置初始值 —— 初始化方法(init), 这个 初始化方法 就是 __init__ 方法,__init__ 是对象的内置方法, 它是专门用于给对象初始化属性使用的

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

  • __init__方法内部使用 self.属性名 = 属性的初始值 就可以 定义属性
  • 在定义属性时,如果 不知道设置什么初始值,可以设置为 None,None 关键字 表示 什么都没有,可以将 None 赋值给任何一个变量
  • 定义属性之后,再使用 Cat 类创建的对象,都会拥有该属性
class Cat:

    def __init__(self):
        print("这是一个初始化方法")
        # 定义用 Cat 类创建的猫对象都有一个 name 的属性
        self.name = "Tom"
        self.color = "黑色"

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

    def print_info(self):
        print("内部调用,name:%s,color%s" % (self.name, self.color))


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

# 外部修改属性的值
tom.name = "Jack"
tom.color = "白色"
# 在类外面访问对象的属性
print("外部调用,name:%s,color:%s" % (tom.name, tom.color))

输出结果:

这是一个初始化方法
Tom 爱吃鱼
内部调用,name:Tom,color黑色
外部调用,name:Jack,color:白色

在初始化方法内部接收参数定义属性

如果希望在 创建对象的同时,就设置对象的属性,那么需要给__init__方法添加参数, 然后在创建对象时,使用 类名(属性1, 属性2...) 创建, 例如:

class Cat:

    def __init__(self, name, color):
        # 定义属性并使用参数赋值
        self.name = name
        self.color = color

    def print_info(self):
        print("当前对象:%s,name:%s,color%s" % (self, self.name, self.color))


# 使用初始化参数创建对象
tom = Cat("Tom", "黑色")
tom.print_info()

jack = Cat("Jack", "白色")
jack.print_info()

输出结果:

当前对象:<__main__.Cat object at 0x105304a00>,name:Tom,color黑色
当前对象:<__main__.Cat object at 0x105319160>,name:Jack,color白色

6. 类的内置方法使用

__del__ 方法

当一个 对象被从内存中销毁 前,会 自动 调用__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")

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

# 对象删除后,就不能再使用了,会报错
print(tom)

输出结果:

Tom 创建了
Tom 销毁了

Traceback (most recent call last):
  File "/Users/chenyousheng/workspace/python/Learn/day01/main.py", line 128, in <module>
    print(tom)
NameError: name 'tom' is not defined

__str__ 方法

在 Python 中,使用 print 输出 对象变量,默认情况下,会输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)
如果希望使用 print 输出 对象变量 时,能够打印 自定义的内容,就可以利用 __str__ 这个内置方法了, return的时候返回自定义的内容。

class Cat:

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

    def __str__(self):
        return "这是一只肥猫:%s" % self.name


tom = Cat("Tom")
print(tom)

输出结果:

这是一只肥猫:Tom

7. 身份运算符

身份运算符用于 比较 两个对象的 内存地址 是否一致 —— 是否是对同一个对象的引用
在 Python 中针对 None 比较时,建议使用 is 判断
在这里插入图片描述

is 与 == 区别:

is 用于判断 两个变量 引用对象是否为同一个
== 用于判断 引用变量的 是否相等

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> b is a 
False
>>> b == a
True

8. 私有属性和方法

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

  • 私有属性 就是 对象 不希望公开的 属性
  • 私有方法 就是 对象 不希望公开的 方法
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()

有什么办法可以强制访问私有属性和方法?
在调用的时候在属性或者方法前加上_类名, 例如上面代码中改成如下方式调用就可以访问了
xiaofang._Women__age
xiaofang._Women__secret()

注意: 私有属性必须在__init__方法内定义才有效,如果在类的外部定义的两个下划线前缀的属性, 这种还是共有属性.

因此,Python 中,并没有 真正意义 的 私有。

9. 继承

面向对象三大特性:

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

单继承

继承的概念:子类 拥有 父类 的所有 方法 和 属性
在这里插入图片描述

继承的语法

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

继承的传递性

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

方法的重写

当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)
重写 父类方法有两种情况:

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

  • 对父类方法进行 扩展
    如果子类的方法实现 中 包含 父类的方法实现 ,父类原本封装的方法实现 是 子类方法的一部分,就可以使用 扩展 的方式

    1. 在子类中 重写 父类的方法
    2. 在需要的位置使用 super().父类方法 来调用父类方法的执行
    3. 代码其他的位置针对子类的需求,编写 子类特有的代码实现

注意:
在Python中是没有函数重载的!!!
这是因为Python 函数的调用方式是通过函数名称进行查找的。Python 解释器在调用函数时,仅仅只是根据函数的名称来查找该函数,而不会因为传递给函数的参数类型、个数或顺序的不同而自动调用重载函数。因此,如果在 Python 中定义了两个同名的函数,无论它们的参数列表是否相同,后面定义的函数会覆盖前面定义的函数。

关于 super

在 Python 中 super 是一个 特殊的类,super() 就是使用 super 类创建出来的对象, 常用在重写父类方法时,调用父类中封装的方法实现

在 Python 2.x 时,如果需要调用父类的方法,还可以使用以下方式:
父类名.方法(self)
这种方式,目前在 Python 3.x 还支持这种方式,但是这种方法 不推荐使用,因为一旦 父类发生变化,方法调用位置的 类名 同样需要修改, 而且在多继承的情况下, 很容易造成某个方法, 例如孙子类在__init__方法内调用了2个父类的__init__方法,而2个父类的__init__方法又调用了爷爷类的__init__方法, 这就会导致创建孙子类对象的时候触发了2次爷爷类的__init__方法执行.

而super的话会根据__mro__属性来决定调用顺序

不带参数的super调用

super() 这种方式调用父类方法默认会传递当前子类的类名和实例对象作为参数。这是因为 super() 调用的是当前类的父类的方法,而该方法需要的第一个参数通常是实例对象。因此,在使用 super() 调用父类的 方法时,Python 会自动传递子类实例的引用,同时也会传递子类的类名。

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


class Mammal(Animal):
    def __init__(self, name, warm_blooded=True):
        # 这里会调用父类Animal的构造方法
        super().__init__(name)
        self.warm_blooded = warm_blooded


class Dog(Mammal):
    def __init__(self, name, breed):
        # 这里会调用父类Mammal的构造方法
        super().__init__(name)
        # 相当于下面这句
        # super(Dog,self).__init__(name)
        self.breed = breed


dog = Dog("Fido", "Golden Retriever")
print(dog.name)  # 输出 "Fido"
print(dog.breed)  # 输出 "Golden Retriever"
print(dog.warm_blooded)  # 输出 True,因为 Mammal 的构造函数定义了默认参数为 True

带参数的super调用

在多继承中,super(指定类的类名, self)的调用方式可以明确的指定要调用哪个类的父类方法 ,指定类可以是直接父类也可以是间接父类, 例如:

class Parent(object):
    def __init__(self):
        print("Parent __init__")


class Son1(Parent):
    def __init__(self):
        print("Son1 __init__")
        super().__init__()


class Son2(Parent):
    def __init__(self):
        print("Son2 __init__")
        super().__init__()


# 孙子类
class Grandson(Son1, Son2):
    def __init__(self):
        print("Grandson __init__")
        # 根据Python中orm规则的搜寻顺序来寻找下一个父类的__init__方法
        super().__init__()


# 曾孙类
class GreatGrandSon(Grandson):
    def __init__(self):
        print("GreatGrandSon __init__")
        # 指定调用Son2类的父类__init__方法, 也就是Parent的__init__方法
        super(Son2, self).__init__()


print("========不带参数super()调用顺序=============")
gson = Grandson()

print("========Grandson类mro的调用顺序=============")
print(Grandson.__mro__)

print("=========带参数super()调用顺序============")
ggson = GreatGrandSon()

输出结果:

========不带参数super()调用顺序=============
Grandson __init__
Son1 __init__
Son2 __init__
Parent __init__
========Grandson类mro的调用顺序=============
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
=========带参数super()调用顺序============
GreatGrandSon __init__
Parent __init__

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

(1) 子类对象不能在自己的方法内部直接访问父类的私有属性或私有方法, 类的私有方法和私有属性都只能在本类中被访问,而在类定义之外的其他地方,包括实例对象和子类,都不能直接访问它们。例如:

class MyClass:
    def __init__(self):
        self.__private_value = "private_value"

    def __my_private_method(self):
        print("private_value:", self.__private_value)


obj = MyClass()
obj.__my_private_method()# 会抛出 AttributeError: 'MyClass' object has no attribute '__my_private_method'异常
print(obj.__private_value)# 会抛出 AttributeError: 'MyClass' object has no attribute '__private_value'异常

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

class Parent:
    def __init__(self, public_value, private_value):
        self.public_value = public_value
        self.__private_value = private_value

    def get_private(self):
        return self.__private_value


class Child(Parent):
    def access_private(self):
        return self.get_private()


parent = Parent("public", "private")
child = Child("public", "private")

print(parent.public_value)  # 输出 "public"
print(parent.get_private())  # 输出 "private"
print(child.public_value)  # 输出 "public"
print(child.access_private())  # 输出 "private"

多继承

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

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

多继承的使用注意事项

如果 不同的父类 中存在 同名的方法和属性,子类对象 在调用时,会调用 哪一个父类的呢?
Python 中针对 类 提供了一个 内置属性 __mro__ 可以查看 方法 和 属性 的搜索顺序, MRO是 method resolution order,主要用于 在多继承时判断 方法、属性 的调用 路径

class A:
    def __init__(self):
        self.name = "A"

    def test(self):
        print("A test run...")


class B:
    def __init__(self):
        self.name = "B"

    def test(self):
        print("B test run...")


class C(A, B):
    # 就是一个空语句,不做任何事情,一般用做占位语句
    pass


c = C()
c.test()
print(c.name)
# 查看C类的方法和属性的搜索顺序
print(C.__mro__)

输出结果:

A test run...
A
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

从最后一个结果也可以看到此时C类的方法和属性的搜索顺序是: C>A>B

  • 在搜索时,是按照 mro 的输出结果 从左至右 的顺序查找的
  • 如果在当前类中 找到,就直接执行,不再搜索
  • 如果 没有找到,就查找下一个类 中是否有,如果找到,就直接执行,不再搜索
  • 如果找到最后一个类,还没有找到,程序报错

注意:
在 Python 3.x 中定义类时,如果没有指定父类,会 默认使用 object 作为该类的 基类 而Python 2.x则不会, 因此为了保证编写的代码能够同时在 Python 2.x 和 Python 3.x 运行!如果没有父类,建议统一继承自 object
class 类名(object):

查看object类的内置方法

class D(object):
    pass


print(dir(D()))

输出结果:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

10. 多态

多态 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!
多态是以 继承重写父类方法 为前提的

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("旺财")
xiaoTian = XiaoTianDog("哮天犬")

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

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

输出结果:

小明 和 旺财 快乐的玩耍...
旺财 蹦蹦跳跳的玩耍...
小明 和 哮天犬 快乐的玩耍...
哮天犬 飞到天上去玩耍...

11. 类的结构

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

在这里插入图片描述

类对象

Python 中 一切皆对象:

  • class AAA: 定义的类属于 类对象,在程序运行时,类 同样会被加载到内存,在程序运行时,类对象 在内存中 只有一份,使用 一个类 可以创建出 很多个对象实例
  • obj1 = AAA() 属于 实例对象

类对象除了封装 实例 的 属性 和 方法外,类对象 还可以拥有自己的 属性 和 方法

  • 类属性
  • 类方法
    通过 类名. 的方式可以 访问类的属性 或者 调用类的方法

在这里插入图片描述

类属性的使用

类属性 不会用于记录 具体对象的特征,通常用来记录 与这个类相关 的特征

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

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

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


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 查看实例对象创建的个数
print("现在创建了 %d 个工具" % Tool.count)

输出结果:

现在创建了 3 个工具

属性的获取机制

在 Python 中 属性的获取 存在一个 向上查找机制, 首先会在实例对象内部查找,如果没有找到就会向上查找类对象的属性
因此,要访问类属性有两种方式:

  1. 类名.类属性
  2. 对象.类属性 (不推荐)

注意:
使用 对象.类属性 = 值 赋值语句,只会 给对象添加一个属性,而不会影响到 类属性的值

还是上面的Tool类为例:

# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")
# 这种方式只会添加一个实例属性,而不会修改类属性
tool3.count = 100
# 查看实例对象创建的个数
print("现在创建了 %d 个工具" % Tool.count)
print("tool1-->count:%s" % tool1.count)
print("tool2-->count:%s" % tool2.count)
print("tool3-->count:%s" % tool3.count)

输出结果:

现在创建了 3 个工具
tool1-->count:3
tool2-->count:3
tool3-->count:100

注意:

  • 当对象的实例属性和类属性同名时, 通过实例对象调用那么会优先调用实例属性, 使用类名调用会优先调用类属性
  • 当存在继承关系时, 优先查找子类, 搜索属性如下:
    子类实例属性>父类实例属性>子类类属性>父类类属性
class A:
    name = "classA"

    def __init__(self):
        self.name = "A"
 

class B(A):
    name = "classB"

    def __init__(self):
        super().__init__()
        self.name = "B"


b = B()
print(b.name)
print(B.name)

输出结果:

B
classB

修改如下:

class A:
    name = "classA"

    def __init__(self):
        self.name = "A" 


class B(A):
    name = "classB"

    def __init__(self):
        super().__init__()
        # self.name = "B"


b = B()
print(b.name)
print(B.name)

输出结果:

A
classB

再次修改:

class A:
    name = "classA"

    def __init__(self):
        # self.name = "A"
        pass


class B(A):
    name = "classB"

    def __init__(self):
        super().__init__()
        # self.name = "B"


b = B()
print(b.name)
print(B.name)

输出结果:

classB
classB

继续修改:

class A:
    name = "classA"

    def __init__(self):
        # self.name = "A"
        pass


class B(A):
    # name = "classB"

    def __init__(self):
        super().__init__()
        # self.name = "B"


b = B()
print(b.name)
print(B.name)

输出结果:

classA
classA

类属性的继承与覆盖

在 Python 中,类属性可以被子类继承。当子类继承父类时,如果它没有定义这个属性,那么它会从父类继承这个属性。
让我们看一个简单的代码示例:

class Animal:
    species = 'mammal'
    
class Dog(Animal):
    name = 'Buddy'
    
# 1.通过对象访问类属性
dog = Dog()
print(dog.species)  # mammal
print(dog.name)  # Buddy

# 2.通过类名访问类属性
print(Dog.species)  # mammal
print(Dog.name)  # Buddy

在上面的代码中,Animal类有一个类属性species,它被初始化为字符串 mammal。Dog类继承了Animal类,但它没有定义species属性。因此,当我们创建Dog类的对象dog时,我们可以使用dog.species访问Animal类的类属性。
另外,Dog类还定义了一个名为name的类属性。我们可以使用dog.name访问这个属性,它将返回字符串Buddy。当然, 除了通过实例对象访问 ,也可以使用类名来访问类属性。

此外,类属性可以被子类覆盖。如果一个子类定义了与其父类相同名称的类属性,则该属性将会遮蔽父类的相应属性,并且使用子类的属性。
下面是一个示例:

class Animal:
    species = "mammal"


class Dog(Animal):
    species = "canine"


print(Animal.species)  # 输出 "mammal"
print(Dog.species)  # 输出 "canine"

类方法的使用

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

@classmethod
def 类方法名(cls):
  • 类方法需要用 修饰器@classmethod来标识,告诉解释器这是一个类方法
  • 类方法的 第一个参数 应该是 cls
    • 由 哪一个类 调用的方法,方法内的 cls 就是 哪一个类的引用
    • 这个参数和 实例方法 的第一个参数是 self 类似
    • 使用其他名称命名也可以,不过习惯使用 cls
  • 通过 类名.类方法名调用,调用方法时,不需要传递 cls 参数
  • 类方法也支持通过实例对象调用
  • 在方法内部可以通过 cls. 访问类的属性,也可以通过 cls. 调用其他的类方法
class Tool(object):
    # 使用赋值语句,定义类属性,记录创建工具对象的总数
    count = 0

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

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

    @classmethod
    def show_tool_count(cls):
        """显示工具对象的总数"""
        print("工具对象的总数 %d" % cls.count)


# 创建工具对象
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("铁锹")

# 查看实例对象创建的个数
# 通过类名调用
Tool.show_tool_count()
# 通过实例对象调用
tool1.show_tool_count()

输出结果:

工具对象的总数 3
工具对象的总数 3

类方法的继承与覆盖

在 Python 中,当一个子类继承了一个父类时,它会继承并拥有父类的所有方法和属性,包括类方法。如下所示:

class Animal:
    @classmethod
    def speak(cls):
        print("The {} says hello!".format(cls.__name__))


class Dog(Animal):
    pass


# 调用 Animal 类的 speak() 方法
Animal.speak()  # 输出 "The Animal says hello!"

# 调用 Dog 类的 speak() 方法
Dog.speak()  # 输出 "The Dog says hello!"

当子类定义一个与父类中同名的类方法时,该方法将覆盖父类的相应方法。当我们在子类中调用该方法时,它将使用子类的方法实现,而不是父类方法的实现。如下所示:

class Animal:
    @classmethod
    def speak(cls):
        print("The {} says hello!".format(cls.__name__))


class Dog(Animal):
    @classmethod
    def speak(cls):
        print("The {} barks!".format(cls.__name__))


# 调用 Animal 类的 speak() 方法
Animal.speak()  # 输出 "The Animal says hello!"

# 调用 Dog 类的 speak() 方法
Dog.speak()  # 输出 "The Dog barks!"

12. 静态方法

如果需要在 中封装一个方法,这个方法既 不需要 访问 实例属性 或者调用 实例方法,也不需要访问 类属性 或者调用 类方法,那么这个方法封装成一个 静态方法
语法如下:

@staticmethod
def 静态方法名():
  • 静态方法 需要用 修饰器 @staticmethod 来标识,告诉解释器这是一个静态方法
  • 通过 类名. 调用 静态方法,当然也可以使用实例 对象. 来调用
  • 静态方法内无法访问类和实例的属性及方法
class Dog(object):
    @staticmethod
    def run():
        # 静态方法内无法访问类和实例的属性及方法
        print("狗在跑...")

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


dog = Dog("旺财")
dog.run()
Dog.run()

静态方法继承与覆盖

在 Python 中,当一个子类继承了一个父类时,它继承了该类的所有方法和属性,包括静态方法。

class Animal:
    @staticmethod
    def speak():
        print("Hello, I'm an animal.")


class Cat(Animal):
    @staticmethod
    def speak():
        print("Meow, I'm a cat.")


# 调用 Animal 类的静态方法
Animal.speak()  # 输出 "Hello, I'm an animal."

# 调用 Cat 类的静态方法
Cat.speak()  # 输出 "Meow, I'm a cat."

13. 单例

单例是一种设计模式, 是让类创建的对象,在系统中只有唯一的一个实例,在介绍python的单例时,我们先来了解下python的__new__方法
__new__方法
使用 类名() 创建对象时,Python 的解释器 首先 会 调用 __new__ 方法为对象 分配空间。
__new__ 是一个 由 object 基类提供的 内置的静态方法,主要作用有两个:

  • 在内存中为对象 分配空间
  • 返回 对象的引用
    Python 的解释器获得对象的 引用 后,将引用作为 第一个参数 self,传递给__init__方法, 重写__new__方法 一定要 return super().__new__(cls)否则 Python 的解释器 得不到 分配了空间的 对象引用,就不会调用对象的初始化方法

注意:__new__ 是一个静态方法,在调用时需要 主动传递 cls 参数

class MusicPlayer(object):

    def __new__(cls, *args, **kwargs):
        # 如果不返回任何结果,那么得到的实例是None
        print("__new__被调用")
        return super().__new__(cls)

    def __init__(self):
        print("__init__被调用...初始化音乐播放对象")


player = MusicPlayer()

print(player)

输出结果:

__new__被调用
__init__被调用...初始化音乐播放对象
<__main__.MusicPlayer object at 0x100a31a30>

单例的实现步骤

  • 定义一个 类属性,初始值是 None,用于记录 单例对象的引用
  • 重写 __new__ 方法
  • 如果 类属性 is None,调用父类方法分配空间,并在类属性中记录结果
  • 返回 类属性 中记录的 对象引用
class MusicPlayer(object):
    # 定义类属性记录单例对象引用
    single_instance = None

    def __new__(cls, *args, **kwargs):
        # 1. 判断类属性是否已经被赋值
        if cls.single_instance is None:
            cls.single_instance = super().__new__(cls)
        # 2. 返回类属性的单例引用
        return cls.single_instance


player1 = MusicPlayer()
player2 = MusicPlayer()

print(player1)
print(player2)
print(player1 == player2)

输出结果:

<__main__.MusicPlayer object at 0x104b15850>
<__main__.MusicPlayer object at 0x104b15850>
True

初始化方法的调用次数

使用 类名() 创建对象时,Python 的解释器都会自动调用两个方法:

  • __new__ 分配空间
  • __init__ 对象初始化
    有没有什么办法可以让__init__方法只调用一次?
    __init__方法的回调次数是没有办法左右的,但是我们可以让__init__方法内的初始化逻辑控制在只执行一次。可以给类对象添加一个类属性作为标记, 当__init__方法调用的时候判断下标记, 避免__init__方法内部逻辑调用多次, 示例代码如下:
class MusicPlayer(object):
    # 记录是否执行过初始化动作
    init_flag = False

    def __init__(self):
        # 判断标记,没有初始化才进行初始化
        if not MusicPlayer.init_flag:
            print("初始化播放器")
            MusicPlayer.init_flag = True


player1 = MusicPlayer()
player2 = MusicPlayer()

输出结果:

初始化播放器

从结果可以看出,虽然创建了2个对象, 但是__init__方法内的初始化操作仅初始化了一次。

14.迭代器

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
认识可迭代的对象
我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

from typing import Iterable


def main():
    print(isinstance([], Iterable))
    print(isinstance({}, Iterable))
    print(isinstance('abc', Iterable))
    print(isinstance(100, Iterable))

if __name__ == '__main__':
    main()

输出结果:

True
True
True
False

iter()函数与next()函数

list、tuple等都是可迭代对象,我们可以通过**iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()**函数来获取下一条数据。iter()函数实际上就是调用了可迭代对象的__iter__方法。
for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

def main():
    my_list = [1, 2, 3, 4]
    # 获得迭代器
    it = iter(my_list)
    while True:
        try:
            # 获取下一个元素
            element = next(it)
            print(element)
        except StopIteration:
            # 遍历到最后一个元素的时候灰抛出这个异常
            break


if __name__ == '__main__':
    main()

自定义可迭代的对象

步骤如下:

  • 可迭代的对象需要实现__iter__方法,内部持有迭代器对象,通过该方法返回迭代器对象
  • 迭代器对象持有可迭代的容器对象和记录当前迭代位置的变量,需要实现__next__方法,在该方法内实现迭代操作; 同时迭代器对象也需要实现__iter__方法,返回自身即可

代码实现如下:

class MyList(object):
    def __init__(self):
        self.items = []

    def add(self, val):
        self.items.append(val)

    def __iter__(self):
        # 注意: 这里传self即可,不需要传self.items
        myiter = MyIterator(self)
        return myiter


class MyIterator(object):
    def __init__(self, mylist):
        self.mylist = mylist
        self.current = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < len(self.mylist.items):
            value = self.mylist.items[self.current]
            # 更新当前位置
            self.current += 1
            return value
        else:
            # 没有可迭代的元素后,继续调用__next__方法就会抛出异常
            raise StopIteration


def main():
    my_list = MyList()
    my_list.add(1)
    my_list.add(2)
    my_list.add(3)

    for value in my_list:
        print(value)


if __name__ == '__main__':
    main()

13.类的property属性使用

@property注解使用

可以在类中定义一个方法, 使用@property注解声明,那么这个方法就可以当做实例属性来使用了.调用的时候无需使用()来调用.
使用 @property 修饰时,需要满足以下两个条件:

  • 该方法必须没有参数(除了 self 之外),而且必须是类的实例方法。
  • 该方法需要返回一个值,这个值就是属性的值。
    例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    @property
    def age_in_days(self):
        return self.age * 365

    @property
    def name_age(self):
        return f"{self.name} is {self.age} years old."

调用方式:

if __name__ == '__main__':
    p = Person("Alice", 20)
    print(p.age_in_days) # 7300
    print(p.name_age) # Alice is 20 years old.

@property属性的setter和deleter使用

@property 的 setter 和 deleter 在 Python 3.0 中被引入,之前的版本中并没有这个功能。在 Python 3.0 中,你可以使用 @property 装饰器来定义属性,并使用 @属性名.setter 来设置 setter 方法,使用 @属性名.deleter 来设置 deleter 方法。

class Goods(object):
    def __init__(self, original_price, discount):
        self.original_price = original_price
        self.discount = discount

    @property
    def price(self):
        return self.original_price * self.discount

    @price.setter
    def price(self, value): # 方法名必须保持一致
        self.original_price = value

    @price.deleter
    def price(self): # 方法名必须保持一致
        del self.original_price


if __name__ == '__main__':
    goods = Goods(100, 0.8)
    print(f"商品原价{goods.original_price}, 折后价:{goods.price}")  # 商品原价100, 折后价:80.0
    # 恢复原价
    goods.price = 100
    print(f"商品原价{goods.original_price}")  # 商品原价100

    # 删除原价
    del goods.price
    print(hasattr(goods, 'price'))  # False

property方法的使用

property() 是 Python 内置函数之一,可以用来定义属性,使得能够像访问普通属性一样去访问方法,从而提高代码的简洁性和可读性。使用 property() 函数时,需要指定三个方法:getter、setter 和 deleter。

  • getter:用于获取属性值的方法,可以省略该方法。当访问属性时,如果没有定义 getter,将会抛出 AttributeError 错误。
  • setter:用于设置属性值的方法,可以省略该方法。当试图设置属性时,如果没有定义 setter,将会抛出 AttributeError 错误。
  • deleter:用于删除属性的方法,可以省略该方法。当使用 del 语句删除属性时,如果没有定义 deleter,会抛出 AttributeError 错误。
    下面是一个使用 property() 函数来定义属性的示例:
class Person:
    def __init__(self, name):
        self.__name = name

    def get_name(self):
        return self.__name

    def set_name(self, value):
        self.__name = value.upper()

    def del_name(self):
        del self.__name

    name = property(get_name, set_name, del_name, 'Person name property')


if __name__ == '__main__':
    p = Person("Tom")
    print(f"修改前name={p.name}")  # 修改前name=Tom
    p.name = "aa"
    print(f"修改后name={p.name}")  # 修改后name=AA
    del p.name
    print(f"删除后,是否还存在{hasattr(p, 'name')}")  # 删除后,是否还存在False
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值