类的定义和使用

定义类#

同函数定义(def 语句)类似,类的定义通过 class 语句实现:

class ClassName:
    """类的文档字符串"""  # 类的帮助信息
    statement     # 类体
  • 其中 ClassName 用于指定类名,一般使用“驼峰式命名法”;
  • """类的文档字符串"""可以通过 ClassName.__doc__ 查看;
  • statement 主要由类变量(或称类成员)、方法和属性等定义语句组成。

示例:

class Student:
    """所有学生的基类"""
    count = 0  # 类变量,表示学生总数

    def __init__(self, id, name):  # 特殊的构造方法
        self.id = id
        self.name = name
        self.scores = {}
        Student.count += 1

    def set_score(self, course, score):  # 方法
        self.scores[course] = score

上页代码中:

  • count 变量是一个类变量,又称类属性,类变量定义在类中且在函数体之外,它的值将在这个类的所有实例之间共享。类属性可以通过类名称(如 Student.count)或实例名访问。
  • __init__set_score 这些在类中定义的函数,称为方法,方法是从属于对象的函数。
  • self 是方法的一个特殊参数,代表类的实例,它在方法的参数列表中首先给出,但在调用时不必传入相应的参数。
  • idnamescores 这种使用 self.name 形式赋值的变量称为实例变量,又称实例属性,一般应该在 __init__() 函数中定义所有实例变量
  • 所有这些类变量、实例变量、方法都可统称为属性(Attribute)。

可以通过属性引用的方式(obj.name)访问类变量,如下所示:

print(Student.count)  # 0

创建类的实例#

定义好的类相当于一栋房屋的设计图,它告诉我们房屋的模型,但本身不是房屋。要创建真实的房屋,需要把类实例化。

类的实例化使用函数表示法。如下所示:

class MyClass:  # 定义 MyClass 类
    pass

obj1 = MyClass()  # MyClass 类的实例化
obj2 = MyClass()  # MyClass 类的实例化

obj1 = MyClass() 创建类 MyClass 的新实例并将其赋值给变量 obj1

__init__() 方法是一种特殊的方法,称为构造函数构造器。每当创建一个类的新实例时,都会自动执行该方法。该方法必须包含一个 self 参数,且其必须是第一个参数。示例如下:

class Dog:
    def __init__(self):
        print('汪!汪!')

my_dog = Dog()  # 汪!汪!
说明

__init__() 方法的名称中,开头和结尾处是两个下划线(中间没有空格),这是一种约定,旨在区分 Python 的默认方法和普通方法。

zhangsan = Student(2001, '张三')
lisi = Student(2002, '李四')

print(Student.count)

访问属性#

在创建了类的实例后,可以通过属性引用访问实例的属性(实例变量和方法)。例如:

zhangsan = Student(2001, '张三')
zhangsan.set_score('数学', 79.0)  # 调用实例方法时不必给定 self 参数
print(zhangsan.scores)  # 访问其 scores 数据属性,{'数学': 79.0}
print(zhangsan.count)  # 甚至可以通过实例访问类变量,1

lisi = Student(2002, '李四')
lisi.set_score('数学', 88.5)
print(lisi.name)  # 李四
print(lisi.count)  # 2

print(Student.count)  # 2

类属性、实例属性都可以自由地添加、修改和删除:

class Dog:
    bark_mode = '汪!汪!'
    def __init__(self):
        print(Dog.bark_mode)

my_dog1 = Dog()  # 汪!汪!
Dog.bark_mode = '嗷...呜...'
my_dog2 = Dog()  # 嗷...呜...
Dog.name = '狗狗'
print(Dog.name)  # 狗狗
my_dog1.name = '阿黄'
print(my_dog1.name)  # 阿黄,同样的属性名称同时出现在实例和类中,则属性查找会优先选择实例
del Dog.bark_mode
del my_dog1.name

一般来说,实例变量用于每个实例的唯一数据,而类变量用于类的所有实例共享的属性和方法。

也可以使用以下函数的方式来访问属性:

  • getattr(object, name, /)getattr(object, name, default, /): 返回 object 对象的 name 属性,等价于 object.name
  • hasattr(object, name, /):检查是否存在一个属性,如果字符串 name 是对象 object 的属性之一的名称,则返回 True,否则返回 False
  • setattr(object, name, value, /):设置一个属性。如果属性不存在,会创建一个新属性,等价于 object.name = value
  • delattr(object, name, /):删除属性,等价于 del object.name

内置属性#

所有类具有一些内置属性:

  • __dict__: 类的属性(包含一个字典,由类的数据属性组成)。
  • __doc__:类的文档字符串。
  • __name__:类名。
  • __module__:类定义所在的模块(类的全名是 __main__.ClassName,如果类位于一个导入模块 mymod 中,那么 ClassName.__module__ 等于 mymod)。
  • __bases__:类的所有父类构成元素(包含了一个由所有父类组成的元组)。

访问限制#

为了保证类内部的某些属性或方法不被外部所访问,可以在属性或方法名前面添加双下划线 __private_attrs 声明该属性为私有,只允许定义这个类本身进行访问。如下所示:

class Student:
    """所有学生的基类"""
    __count = 0  # 私有类变量,表示学生总数

    def __init__(self, id, name):  # 特殊的构造方法
        self.__avg_score = None  # 私有示例变量
        self.id = id
        self.name = name
        self.scores = {}
        Student.__count += 1

    def set_score(self, course, score):  # 方法
        self.scores[course] = score
        self.__avg_score = sum(self.scores.values()) / len(self.scores)

    def avg_score(self):
        return self.__avg_score

zhangsan = Student(2001, '张三')
zhangsan.set_score('数学', 79.0)
print(zhangsan.avg_score())

单下划线、双下划线、头尾双下划线说明:

  • _foo:以单下划线开头的表示的是保护(protected)类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
  • __foo:以双下划线开头的表示的是私有类型(private)类型的变量,只能是允许这个类本身进行访问。
  • __foo__:头尾双下划线定义的是特殊属性,一般是系统定义名字,类似 __init__() 之类的。

Python 通过不同的下划线命名方式在一定程度上实现了封装(Encapsulation)。封装是面向对象编程的三大基本特征之一。封装思想保证了类内部数据结构的完整性,使用该类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。

概述类的继承