第8章 面向对象之类和对象

0 阅读12分钟

第8章 面向对象之类和对象

8.1 面向过程和面向对象

面向过程编程(Procedural Programming)和面向对象编程(OOP)是两种不同的编程范式,它们在软件开发中都有广泛的应用。

Python 是一种混合型的语言,既支持面向过程的编程,也支持面向对象的编程。

  • 面向过程的编程是一种以过程为中心的编程方式,主要关注解决问题的步骤,并将这些步骤写成函数或方法。
  • 面向对象的编程是一种以对象为中心的编程方式,主要关注在解决问题的过程中涉及哪些对象以及这些对象如何交互。

1)面向过程举例

想象一下,你要做一顿美味的晚餐。在面向过程编程的思维下,你会把整个做饭的过程拆分成一系列的步骤。

def buy():
    print("去超市购买食材。")

def wash():
    print("清洗蔬菜。")

def cut():
    print("切菜。")

def cook():
    print("开始烹饪。")

def serve():
    print("上菜啦!")

buy()
wash()
cut()
cook()
serve()

上面就是一个典型的面向过程的程序,我们把整个做饭的过程分解成了一个个函数,每个函数完成一个特定的任务,然后按照顺序依次调用这些函数,就可以完成做晚餐的任务啦。

这种方式非常直接,适合一些简单的任务,它注重的是程序的流程和步骤。但是当我们的程序变得越来越复杂,代码就会变得越来越长、越来越乱,而且上面的代码步骤是没有通用性的。

2)面向对象举例(先感受)

用面向对象的思想实现上面的做菜功能:

class Dish:
    def __init__(self, name):
        self.name = name
    def prepare(self):
        pass

class Salad(Dish):
    def prepare(self):
        print(f"为 {self.name} 购买食材。")
        print(f"清洗 {self.name} 的蔬菜。")
        print(f"切 {self.name} 的蔬菜。")

class Stew(Dish):
    def prepare(self):
        print(f"为 {self.name} 购买食材。")
        print(f"切 {self.name} 的肉。")
        print(f"烹饪 {self.name}。")

class Soup(Dish):
    def prepare(self):
        print(f"为 {self.name} 购买食材。")
        print(f"煮 {self.name}。")

salad = Salad("蔬菜沙拉")
stew = Stew("炖肉")
soup = Soup("西红柿鸡蛋汤")
salad.prepare()
stew.prepare()
soup.prepare()

在这里,我们创建了一个 Dish 类,它就像是一个菜的模板。然后我们创建了 SaladStewSoup 这些子类,它们都继承自 Dish 类。每个子类都有自己的 prepare 方法,这个方法描述了如何准备这道菜。

面向对象编程的优势:

  • 把相关的数据(比如菜的名字)和操作(比如准备菜的过程)都封装在了一个类里面,这叫做"封装"。
  • 不同类型的菜可以有自己独特的准备方法,我们可以根据需要去修改或扩展这些方法,而不会影响其他类。
  • 当我们想要添加新的菜品时,我们只需要创建一个新的子类,定义它自己的 prepare 方法就好,不需要修改原来的代码。

3)面向对象历史

对象作为编程实体最早是于1960年代由 Simula 67 语言引入思维。Simula 这一语言是奥利-约翰·达尔和克利斯登·奈加特在奥斯陆的挪威计算中心为模拟环境而设计的。他们将不同的类型船只归纳为不同的类,而每一个对象,基于它的类,可以定义它自己的属性和行为。Simula 不仅引入了"类"的概念,还应用了实例这一思想,这可能是这些概念的最早应用。

8.2 类和对象

8.2.1 类(Class)

类描述了所创建的对象共同的属性(是什么)和方法(能做什么),属性和方法统称为类的成员。

  • 类是对大量对象共性的抽象
  • 类是创建对象的模板
  • 类是客观事物在人脑中的主观反映

8.2.2 对象(Object)

  • 在自然界中,只要是客观存在的事物都是对象
  • 类是抽象的,对象是类的实例(Instance),是具体的
  • 一个对象有自己的状态(属性)、行为(方法)和唯一的标识(本质上指内存中所创建的对象的地址)

8.3 定义类

1)语法

class 类名:
    """类说明文档"""
    类体

类名一般使用大驼峰命名法

类体中可以包含类属性(也叫类变量)、方法实例属性(也叫实例变量)等。

2)案例

定义一个人的类,包含 __init__() 方法、eat() 方法和 drink() 方法。

class Person:
    """人的类"""
    home = "earth"

    def __init__(self):
        self.age = 0

    def eat(self):
        print("eating...")

    def drink(self):
        print("drinking...")

8.4 类的操作

类支持两种操作:成员引用实例化

1)成员引用

(1)语法

类名.成员名

(2)案例

class Person:
    """人的类"""
    home = "earth"

    def __init__(self):
        self.age = 0

    def eat(self):
        print("eating...")

    def drink(self):
        print("drinking...")

home = Person.home  # 获取一个字符串
eat_function = Person.eat  # 获取一个函数对象
doc = Person.__doc__  # 获取类的说明文档
print(home)  # earth
print(eat_function)  # <function Person.eat at 0x00000232C8230F40>
print(doc)  # 人的类

2)实例化

(1)语法

变量名 = 类名()

(2)案例

class Person:
    """人的类"""
    home = "earth"

    def __init__(self):
        self.age = 0

    def eat(self):
        print("eating...")

    def drink(self):
        print("drinking...")

p = Person()  # 创建一个对象
print(p.home)  # earth
print(p.age)  # 0
p.eat()  # eating...
p.drink()  # drinking...

8.5 __init__() 方法

__init__() 是一个特殊的方法,也被称作构造函数__init__() 方法的主要作用是在创建类的对象时,对对象的属性进行初始化。当你使用类名创建一个新的对象时,Python 会自动调用 __init__() 方法,并将新创建的对象作为第一个参数(通常命名为 self)传递给它。

注意:

  • self:这是一个约定俗成的参数名,它代表类的实例对象本身。在方法内部,通过 self 可以访问和修改对象的属性。
  • __init__() 方法不是必需的。如果类中没有定义 __init__() 方法,Python 会使用默认的构造函数,该构造函数不执行任何操作。
  • __init__() 方法只能返回 None,不能返回其他值。如果尝试返回其他值,会引发 TypeError 异常。
class Person:
    """人的类"""
    home = "earth"

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

p = Person("张三")  # 创建一个对象
print(p.name)  # 张三

8.6 self

8.6.1 self 作为实例传参

self 代表类的实例自身。调用实例方法时,实例对象会作为第一个参数被传入。因此,我们调用 p.eat() 时就相当于调用了 Person.eat(p)

class Person:
    """人的类"""
    home = "earth"

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

    def eat(self):
        print("eating...")

    def drink(self):
        print("drinking...")

p = Person("张三")  # 创建一个对象
p.eat()  # eating...
Person.eat(p)  # eating...

8.6.2 通过 self 在类中调用类的实例属性和实例方法

class Person:
    """人的类"""
    home = "earth"

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

    def eat(self):
        print("eating...")

    def drink(self):
        print("drinking...")

    def eat_and_drink(self):
        print(self.name)  # 在类中调用name
        self.eat()  # 在类中调用eat()方法
        self.drink()  # 在类中调用drink()方法

p = Person("张三")  # 创建一个对象
p.eat_and_drink()

8.7 属性

8.7.1 类属性

也叫类变量。在类中方法外定义的属性。

1)通过 类名.属性名实例名.属性名 访问

class Person:
    """人的类"""
    home = "earth"  # 定义类属性

print(Person.home)  # 通过类名访问类属性
p1 = Person()  # 创建一个实例对象
print(p1.home)  # 通过实例名访问类属性,(如果实例没有覆盖这个类属性的值)

2)通过 类名.属性名 添加与修改类属性

class Person:
    """人的类"""

Person.home = "earth"  # 添加类属性
print(Person.home)  # earth
Person.home = "mars"  # 修改类属性
print(Person.home)  # mars

若使用 实例名.属性名 则会创建或修改实例属性,因此不建议类属性和实例属性同名。

class Person:
    """人的类"""
    home = "earth"

p1 = Person()
p2 = Person()
print(Person.home)  # earth
print(p1.home)  # earth
print(p2.home)  # earth

print("通过 类名.属性名 修改 类属性")
Person.home = "mars"
print(Person.home)  # mars
print(p1.home)  # mars
print(p2.home)  # mars

print("通过 实例名.属性名 会创建 实例属性")
p1.home = "venus"
print(Person.home)  # mars
print(p1.home)  # venus
print(p2.home)  # mars

3)所有该类的实例共享同一个类属性

class Person:
    """人的类"""
    home = "earth"  # 定义类属性,所有实例共享

p1 = Person()  # 创建一个实例对象
p2 = Person()  # 创建另一个实例对象
print(p1.home)  # earth
print(p2.home)  # earth

Person.home = "mars"  # 修改类属性
print(p1.home)  # mars
print(p2.home)  # mars

8.7.2 实例属性

也叫实例变量。在类 __init__ 方法中定义的属性。通过 self.属性名 定义。

1)通过 实例名.属性名 访问

class Person:
    """人的类"""
    def __init__(self, name, age):
        self.name = name  # 定义实例属性
        self.age = age  # 定义实例属性

p1 = Person("张三", 18)  # 创建一个实例对象
print(p1.name, p1.age)  # 张三 18
p2 = Person("李四", 81)  # 创建一个实例对象
print(p2.name, p2.age)  # 李四 81
print(Person.name)  # 报错

2)通过 实例名.属性名 添加与修改实例属性

class Person:
    """人的类"""
    pass

p1 = Person()  # 创建一个实例对象
p1.name = "张三"  # 添加实例属性
p1.age = 18  # 添加实例属性
print(p1.name, p1.age)  # 张三 18
p1.age = 25  # 修改实例属性
print(p1.name, p1.age)  # 张三 25

3)每个实例独有一份实例属性

class Person:
    """人的类"""
    def __init__(self, name):
        self.name = name  # 定义实例属性
        self.age = 0  # 定义实例属性

p1 = Person("张三")  # 创建一个实例对象
print(p1.name, p1.age)  # 张三 0
p1.age = 18  # 修改p1的age属性
print(p1.name, p1.age)  # 张三 18
p2 = Person("李四")  # 创建另一个实例对象
print(p2.name, p2.age)  # 李四 0

8.8 方法

Python 的类中有三种方法:实例方法静态方法类方法

8.8.1 实例方法

  • 实例方法在类中定义,第一个参数为 self,代表实例本身。
  • 实例方法只能被实例对象调用。
  • 可以访问实例属性、类属性、类方法。
class Person:
    """人的类"""
    home = "earth"

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

    def instance_method(self):
        print(self.name, self.home, Person.home)

p = Person("张三")
p.instance_method()  # 张三 earth earth,此时p中没有home实例属性,会去查找home类属性
Person.home = "venus"  # 修改类属性
p.home = "mars"  # 定义实例属性
p.instance_method()  # 张三 mars venus

8.8.2 类方法

  • 类方法在类中通过 @classmethod 定义,第一个参数为 cls,代表类本身。
  • 类方法可以被类和实例对象调用。
  • 可以访问类属性。
  • 在不创建实例的情况下调用,通过类名直接调用,非常方便,适合一些和类整体相关的操作。
class Person:
    """人的类"""
    home = "earth"  # 定义类属性

    @classmethod
    def class_method(cls):
        print(cls.home)

Person.class_method()  # 通过类调用类方法
p1 = Person()  # 创建一个实例对象
p1.class_method()  # 通过实例对象调用类方法

8.8.3 静态方法

  • 静态方法在类中通过 @staticmethod 定义
  • 不访问实例属性或类属性,只依赖于传入的参数
  • 可以通过类名或实例调用,但它不会访问类或实例的内部信息,更像是一个工具函数,只是为了方便组织代码,把它放在了类里面。
class Person:
    """人的类"""
    home = "earth"  # 定义类属性

    @staticmethod
    def static_method():
        print("static method")

Person.static_method()  # 通过类调用静态方法
p1 = Person()  # 创建一个实例对象
p1.static_method()  # 通过实例对象调用静态方法

8.8.4 在类外定义方法

并非必须在类定义中进行方法定义,也可以将一个函数对象赋值给一个类内局部变量。

# 在类外定义的函数
def f1(self, x, y):
    print(x & y)

class C:
    f = f1

C().f(6, 13)  # 4

8.8.5 特殊方法

方法名中有两个前缀下划线和两个后缀下划线的方法为特殊方法,也叫魔法方法。上文提到的 __init__() 就是一个特殊方法。这些方法会在进行特定的操作时自动被调用。

几个常见的特殊方法:

  1. __new__():对象实例化时第一个调用的方法。
  2. __init__():类的初始化方法。
  3. __del__():对象的销毁器,定义了当对象被垃圾回收时的行为。使用 del xxx 时不会主动调用 __del__(),除非此时引用计数==0。
  4. __str__():定义了对类的实例调用 str() 时的行为。
  5. __repr__():定义对类的实例调用 repr() 时的行为。str()repr() 最主要的差别在于目标用户。repr() 的作用是产生机器可读的输出(大部分情况下,其输出可以作为有效的 Python 代码),而 str() 则产生人类可读的输出。
  6. __getattribute__():属性访问拦截器,定义了属性被访问前的操作。

8.9 动态添加属性与方法

8.9.1 动态给对象添加属性

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

p = Person("张三")
print(p.name)  # 张三
p.age = 18
print(p.age)  # 18

8.9.2 动态给类添加属性

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

p = Person("张三")
print(p.name)  # 张三
Person.age = 0
print(p.age)  # 0

8.9.3 动态给实例添加方法

1)添加普通方法

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

def eat():
    print("吃饭")

p = Person("张三")
p.eat = eat
p.eat()  # 吃饭

2)添加实例方法

给对象添加的实例方法只绑定在当前对象上,不对其他对象生效,而且需要传入 self 参数。需要使用 types.MethodType(方法名, 实例对象) 来添加实例方法。

import types

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

def eat(self):
    print(f"{self.name}在吃饭")

p = Person("张三")
p.eat = types.MethodType(eat, p)
p.eat()  # 张三在吃饭

8.9.4 动态给类添加方法

给类添加的方法对它的所有对象都生效,添加类方法需要传入 cls 参数,添加静态方法则不需要。

class Person:
    home = "earth"

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

# 定义类方法
@classmethod
def come_from(cls):
    print(f"来自{cls.home}")

# 定义静态方法
@staticmethod
def static_function():
    print("static function")

Person.come_from = come_from
Person.come_from()  # 来自earth

Person.static_function = static_function
Person.static_function()  # static function

8.9.5 动态删除属性与方法

  • del 对象.属性名
  • delattr(对象, 属性名)

8.9.6 __slots__ 限制实例属性与实例方法

Python 允许在定义类的时候,定义一个特殊的 __slots__ 变量,来限制该类的实例能添加的属性。使用 __slots__ 可以限制添加实例属性和实例方法,但类属性、类方法和静态方法还可以添加。__slots__ 仅对当前类生效,对其子类无效。

import types

class Person:
    __slots__ = ("name", "age", "eat")

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

def eat(self):
    print(f"{self.name}在吃饭")

def drink(self):
    print(f"{self.name}在喝水")

p = Person("张三")

# 添加实例属性
p.age = 10
print(p.age)  # 10

# 添加实例方法
p.eat = types.MethodType(eat, p)
p.eat()  # 张三在吃饭

# 添加实例属性(不在__slots__中)
p.weight = 100  # AttributeError: 'Person' object has no attribute 'weight'

# 添加实例方法(不在__slots__中)
p.drink = types.MethodType(drink, p)  # AttributeError