类把数据和对数据的操作包装在一起
类让你定义一种事物,比如一个用户、一个银行账户、一份配置、一个请求,以及这种事物应该能做的所有事。这个“事物”就是类的“实例”。这些“操作”叫作“方法”。
下面是最简单的类:
逐项拆解:
class User:开始一个类的定义。按照约定,类名使用大驼峰命名法(PascalCase)。__init__是初始化方法。创建新实例时它会自动运行。它用self.whatever = ...设置实例的属性。greet是一个方法。方法通过self来引用调用它的那个实例。User("Rosa", "rosa@example.com")创建一个实例。Python 调用__init__,把self设为这个新实例,其他参数原样传入。user.greet()调用这个方法。Python 会自动把user作为self传入。
self 只是第一个参数
self 不是关键字。它是 Python 中实例方法第一个参数的名字,也就是这个方法正在操作的实例。你“可以”给它起任何名字;大家都用 self,只因为大家都这么用。
每个实例方法都把 self 作为第一个参数。方法正是靠它知道要读取或修改哪个实例。
添加行为
方法可以读取和修改状态:
__repr__ 是另一个特殊方法,它是一个“打印时怎样显示这个对象”的钩子。定义一个好的 __repr__,调试时会让你受益匪浅。
类属性与实例属性
在类的主体中(__init__ 之外)定义的属性由所有实例“共享”。赋值给 self 的属性则是“每个实例各自独有”的:
类属性用于真正共享的值:常量、计数器、配置。实例特有的数据放在 __init__ 中。
继承
一个类可以“继承”另一个类,免费获得它的方法和属性,再按需添加或重写:
super() 让你能访问父类,常用于在 __init__ 中先初始化父类的属性,再添加自己的属性:
继承很有用,但很容易被滥用。Python 新手常常在组合(把一个对象作为属性持有)更清晰的时候选择继承。先从扁平的类结构开始,只有在真正存在“是一个”(is-a)关系时才引入继承。
Python 继承一页讲得更深入:用 super() 扩展父类方法、isinstance 和 issubclass、抽象基类、多重继承以及方法解析顺序。
dataclass:记录类数据的更好默认选择
如果你主要想让一个类保存几个属性,并具备相等比较和显示的功能,@dataclass 能帮你省掉大量样板代码:
对比一下手写 __init__、__repr__ 和 __eq__ 的工作量。如果你本来要写的 class Thing 里只有一个设置属性的 __init__,那么 dataclass 就是首选。
property:计算出来的属性
有时你想要一个实际上是计算出来的“属性”。@property 让它看起来就像访问属性一样:
在你本来会写 obj.get_something() 的地方使用 property,换成 property 会让 API 用起来更自然。不要用它来隐藏耗时的操作;使用者会认为访问属性是很快的。
约定俗成的私有
Python 没有真正的私有。按照约定,以下划线开头的属性“意在”私有,读代码的人不应该在类的外部操作它们:
class Cache:
def __init__(self):
self._store = {}
def put(self, key, value):
self._store[key] = value
def get(self, key, default=None):
return self._store.get(key, default)
双下划线开头的名字(__name)会触发“名称改写”(name mangling),把类名加到属性名中。这是在继承中避免命名冲突的工具,不是安全措施。
什么时候不该用类
如果你只有一组相关的数据,用 dataclass、普通字典或具名元组就可以了。如果你只有一个函数,它不需要一个类来装。当你同时有绑定在一起的状态和行为时,尤其是同一类东西会有很多实例时,再使用类。
最后一个例子
一个类似文件的小型日志缓冲区:
注意 __len__,这是另一个魔术方法。定义了它,len(logger) 就能用了,因为 len(x) 实际上是在调用 x.__len__()。Python 有几十个这样的钩子,你可以在需要时慢慢掌握。
常见问题
Python 中的类是什么?
类是创建对象的蓝图,对象把数据(属性)和行为(方法)打包在一起。class User: 定义了一个 User 类;User(...) 创建一个实例。在 Python 中,要给同时具有状态和行为的事物建模,类是主要的工具。
Python 中的 self 是什么意思?
self 是每个实例方法的第一个参数。当你调用 user.greet() 时,Python 会把 user 这个实例作为 self 传入。在方法内部,self.name 访问的就是这个用户的 name 属性。self 这个名字是约定,不是关键字,但所有 Python 程序员都这么写。
Python 是面向对象的语言吗?
是的,Python 中的一切都是对象,包括数字、字符串和函数。合适的时候你可以进行面向对象编程,但 Python 并不强制你这么做。很多实际的 Python 程序很少使用类,能用普通函数和数据结构的地方就尽量用它们。