类对象
上一节我们学习了实例对象,每个实例都可以保存自己的数据。
但有些数据属于整个类,比如所有游戏角色的默认阵营、已经创建了多少个角色。这些数据不适合在每个实例中重复保存,而应该交给类统一管理。哪些数据应该由类共享?如果一个函数只和类有关、与具体实例无关,又应该放在哪里?
这一节换一个角度看类本身:类不只是创建对象的模板,类自己也是对象。本节还会继续区分类属性、类方法、静态方法和私有成员的使用边界。
类对象与实例对象
类的继承基础
在 Python 里,自定义类最终都会继承自 object。
例如:
class Person:
pass虽然这里没有显式写出 object,但 Person 仍然继承自 object。
可以简单理解为:object 是 Python 类继承体系中最基础的类。
实例化过程
前面我们已经学过创建实例对象。
这里再把顺序串一下。
使用类创建对象,一般按下面的顺序进行:
- 先设计类
- 再通过
类名()创建对象 - 创建对象时,自动执行初始化逻辑
当我们写:
zx = Person()从当前阶段看,可以先把背后的过程理解成两步:
- 先在内存里给对象分配空间
- 再调用
__init__()初始化对象
所以:
- 创建出来的对象,叫实例对象
- 创建对象这个动作,叫实例化
- 每个对象各自保存的属性,叫实例属性
- 定义在类中、由实例对象调用的方法,叫实例方法
类对象
这里要记住一句很重要的话:
Python 里一切皆对象。
所以:
class AAA:定义出来的是类对象obj1 = AAA()创建出来的是实例对象
程序执行类定义后,会在内存中创建对应的类对象。一个类对象可以继续创建很多个实例对象。
类属性
当多个实例需要共享同一份数据时,应该把它放在实例属性中,还是放在类属性中?除了实例属性,类本身还可以有自己的属性。
这种属性就叫类属性。
类属性是所有实例对象共享的。
它更适合保存“这一类事物共同拥有的信息”。
例如:
class Person:
address = '地球'
def __init__(self, name, age):
self.name = name
self.age = age
def hello(self):
return f'{self.name} 正在打招呼'
print(Person.address)
zx = Person('正心', 18)
print(zx.address)
zx.address = '外太空'
print(zx.address)
print(Person.address)这里要注意:
Person.address是类属性- 实例对象也可以访问类属性
- 给
zx.address赋值,会在实例对象上新增一个同名属性
查找 zx.address 时,Python 会先找实例属性;如果实例中没有,再去找类属性。
因此,zx.address = '外太空' 只是让实例属性遮住了同名类属性,并没有修改 Person.address。最后一次输出仍然是 地球。
类属性的适用范围
一般适合放:
- 所有对象共享的数据
- 与“这一类”有关,而不是与“某一个实例”有关的数据
比如:
- 人类的居住地
- 游戏角色的默认阵营
- 某个类的计数器
类方法
如果一个操作只与类有关,不需要某个具体实例的数据,就可以定义成类方法。
类方法可以直接访问类属性,也可以调用其他类方法。
下面给 Person 定义一个类方法,用来读取类属性:
class Person:
address = '地球'
@classmethod
def show_address(cls):
print(f'人类居住在{cls.address}')
Person.show_address()这里要注意几个点:
- 类方法前面要写
@classmethod - 第一个参数通常写成
cls cls指的是当前这个类本身
和实例方法里的 self 类似:
self指对象cls指类
类方法通常通过 类名. 调用。调用 Person.show_address() 时,Python 会自动把 Person 类传给 cls。
在类方法内部,可以用 cls. 访问类属性和类方法。
静态方法
有时候,一个函数与某个类有关系,但既不需要实例属性,也不需要类属性。此时可以把它放进类中,定义为静态方法。
但这个函数:
- 不需要访问实例属性
- 不需要访问类属性
这时就可以写成静态方法。例如,判断年龄是否有效只需要使用传入的参数,不需要访问某个实例或类属性:
class Person:
@staticmethod
def check_age(age):
return 0 <= age <= 120
print(Person.check_age(18))
print(Person.check_age(150))静态方法前面要写 @staticmethod。
它既不需要 self,也不需要 cls。
静态方法通常也通过 类名. 调用。可以先把它理解成一个与当前类有关、放在类中管理的普通函数。
私有成员
如果某些数据和方法只应该由类的内部使用,就不能把它们都直接暴露给外部。在面向对象里,这类成员可以使用私有命名约定。
有些属性和方法,只想让类的内部自己用。
这时就可以使用私有成员。
Python 里,以双下划线 __ 开头、但不以双下划线结尾的属性或方法,通常只供类的内部使用。
class Person:
def __init__(self, name, age):
self.name = name
self.__age = age
def __get_age(self):
return self.__age
def introduce(self):
print(f'我叫{self.name},今年{self.__get_age()}岁')
zx = Person('正心', 18)
zx.introduce()外部可以正常调用公开方法 introduce(),再由它访问私有属性和私有方法。直接访问 zx.__age 或调用 zx.__get_age(),都会触发 AttributeError。
私有成员的适用范围
适合这种情况:
- 某些属性不希望被外部直接改
- 某些方法只是内部实现细节
- 不想让外部随便调用
名称改写边界
双下划线开头的名字会触发名称改写,但这不是绝对的安全机制。
基础阶段先记住一点就够了:
双下划线开头的成员应该留在类的内部使用,对外操作由公开方法提供。
小结
本节重点记住五件事:
- 类本身也是对象
- 类属性属于类,实例对象共享
- 类方法用
@classmethod定义,第一个参数通常是cls - 静态方法用
@staticmethod定义 - 以双下划线开头、但不以双下划线结尾的属性和方法,通常表示私有成员
练习
- 定义一个
Hero类,添加一个类属性,比如camp = '蜀国' - 定义一个类方法,打印这个类属性
- 再定义一个静态方法,输出一段固定提示文字
- 给类增加一个私有属性,观察外部直接访问时的表现