当前位置:首页>python>Python学习第三课(第1节:类之初体验)

Python学习第三课(第1节:类之初体验)

  • 2026-08-25 13:16:05
Python学习第三课(第1节:类之初体验)

Python 中的类

首先,牢记这句话:在 Python 的世界里,一切皆对象。

咱们的 Python 学习课程,从来不讲长篇大论,全都是通过一段段代码来拆解基本的知识点,最终让你能够写出真正的 Pythonic 代码。

所以,本节课的内容也是一步步让大家去了解 Python 类的基本概念、运行机制以及有哪些魔法方法。

一、什么是类?

说到面向对象编程,大家一定对“类”这个名字不陌生,英文是 class。类其实就是用来表示具有相同属性的对象的一个概念,比如人、猫、狗、兔子等不同的动物种类,我们可以叫做动物类。

可能有的同学在学 C/C++ 、Java 等高级语言的面向对象编程时,被告知对象是类的一个实例,就会惯性地认为 Python 中类和对象是同级别的存在。

事实上并不是这样,还记得文章开头的那句话——“在 Python 的世界里,一切皆对象。”——吗?类也不例外。所以,对象才是 Python 中的最顶级的存在!而经常和“类”相提并论的“对象”则是指类的实例化对象。

这么干说可能不太容易理解,下面我们先喝口水(就没那么干了,哈哈,活跃下气氛!),然后再来看看 Python 中的类是长什么样子。

class Say:    """一个简单的类"""    to_name = 'xxx'    def say(self):        return '520 1314'print(Say.to_name)I = Say()  # 初始化一个实例对象 Iprint(I.to_name, I.say())# 结果:xxx 520 1314

上面的例子中,Say 就是一个类,而 I 就是 Say 类的一个实例化对象(简称实例)。我们应该这样理解:I 这个实例,具有 Say 类的属性 to_name 和 say。所以,大家以后不要再把“类”和“对象”混为一谈了。

在 Say 类中,to_name 是类属性,可以不用初始化直接调用,比如 print(Say.to_name);而 say 方法(类里一般把函数叫做方法)则是实例属性,需要初始化一个实例对象后才可以调用。

如果想修改类或实例属性的值,直接赋值修改即可。

Say.to_name = 'ddd'print(Say.to_name)I.to_name = 'yyy'print(I.to_name, I.say())# 结果:yyy 520 1314

注意:实例的属性可以任意增加,比如已经存在的实例 I,通过 I.name = 'Jim',就相当于给实例对象 I 增加了一个值为 'Jim' 的属性 name,但你再通过 you = Say() 初始化一个实例 you 仍然不会有 name 属性。

有个问题,这个类的功能太单一,只能显示和修改 to_name,say的返回值只有 520 1314。那么,要如何修改才能更加通用呢?

请看下方的修改内容:

class Say:    """一个灵活的类"""    def __init__(self, to_name='xxx', say_words='520 1314'):        self.to_name = to_name        self.say_words = say_words    def say(self):        return self.say_wordsI = Say()  # 对象以默认值初始化一个实例print(I.to_name, I.say())  # 结果:xxx 520 1314I = Say('yyy''555')  # 对象以指定值初始化一个实例print(I.to_name, I.say())  # 结果:yyy 555

修改后的代码可以看出一个最大的改动,就是增加了一个 __init__ 函数。

__init__ 方法是类的构造函数,在类初始化时一定会调用。在第一个例子中虽然没写,但 Python 默认也是有这个方法存在的,只不过是一个空函数。一旦写了,我们就覆盖了默认的空 __init__ 方法,可以在这个函数里做我们想做的事情,比如在类初始化的时候给实例的两个属性赋初值 yyy 和 555。

然后是 to_name 变成了 self.to_name,新增了 self.say_words。如果同样的属性名称同时出现在实例和类中,属性查找会优先选择实例。

这时就有一个重要的问题需要解答:类里面经常看到的self是什么?

很多初学者认为 self 是一个关键字,其实并非如此!它只是一个约定俗成的参数名称,你可以写成任何合法的变量名,比如 cls:

class Say:    """一个灵活的类"""    to_name = 'ooo'  # 类成员变量    def __init__(cls, to_name='xxx', say_words='520 1314'):        cls.to_name = to_name  # 类实例的成员变量初始化        cls.say_words = say_words    def say(cls):        return cls.say_words

而且还需要记住一个关键概念:self 指向的是实例的内存地址,而不是类的。证明代码如下:

class Person:    def __init__(self, name):        self.name = name        # 打印 self 的内存地址,证明它就是实例本身        print(f"Inside __init__, self id: {id(self)}")print(f"Class Person id: {id(Person)}")  # 和 Inside 打印的 id 不同p = Person("xxx")print(f"Outside, p id: {id(p)}")  # 和 Inside 打印的 id 一致

注意:Python 中一切皆对象,不仅 self 和 p 是对象,Person 类本身也是一个对象。我们可以使用 id(obj) 查看任何一个对象的id(内存地址)。

类属性 vs 实例属性:一个经典的“坑”

这是面试和实战中极易翻车的点。

陷阱:当通过实例去修改一个可变对象类型的类属性时,会发生什么?
class MyClass:    shared_list = [123]print(MyClass.shared_list)obj1 = MyClass()obj2 = MyClass()obj1.shared_list.append(4)obj2.shared_list.append(5)print(obj1.shared_list)print(obj2.shared_list)print(MyClass.shared_list)

运行上述代码后,你会发现,最后3个 print 打印出来的结果都是 [1, 2, 3, 4, 5]。因为在类属性中的可变对象在实例化对象后是共享内存的,所以上面的 obj1 和 obj2 修改 shared_list 后,类属性 share_list 的值也随之改变。

金句: “千万不要在类属性中定义可变对象(如列表、字典),除非你非常清楚自己在做什么,否则这就是一颗共享状态的定时炸弹。”

下面是在类中正确使用可变对象的方式:

class MyClass:    def __init__(self):        self._list = [1, 2, 3]obj1 = MyClass()obj2 = MyClass()obj1._list.append(4)obj2._list.append(5)print(obj1._list)  # 结果:[1234]print(obj2._list)  # 结果:[1235]

说到这里,我们又看到了 __init__ 函数,之前说这个函数是类默认就有的方法,像这种双下划线( __ )包含的方法,我们称之为魔法方法。那么,类中还有哪些类似的魔法方法呢?下面,我们就一起来看看吧!


二、类中的魔法方法

字符串的门面

__str__ 与 __repr__
class MyClass:    def __init__(self):        self.name = 'CMOS三好先生'I = MyClass()print(I)

这段代码打印实例对象 I 时会出现 <__main__.MyClass object at 0x...> 这种看上去不可解读的代码。那么,如何打印出可读性更强的内容呢?

解决方案:使用魔法方法 __str__ 和 __repr__。

  • __str__:给用户看(print 调用)。
  • __repr__:给开发者看(交互式命令行中调用)。
class MyClass:    def __init__(self):        self.name = 'CMOS三好先生'    def __str__(self):        return f'[__str__]这是一个 MyClass 类:{self.name}'    def __repr__(self):        return f'[__repr__]这是一个 MyClass 类:{self.name}'
在 Python 命令行中运行的结果如下:

这个只是一个功能展示,一般来说类都是比较复杂的,在 __str__ 方法里可以把需要展示的属性都构造为字符串展示给用户,__repr__ 方法则一般不用实现。

让对象可迭代

__getitem__ 与 __len__

在了解了两个比较基础的魔法方法后,让我们再来学习复杂一点儿的。

下面的代码展示了一个实战例子:编写一个类似 List 的自定义扑克牌类。

import randomclass Poke:    point = [str(n) for n in range(211)] + list('JQKA')    suits = '♠ ♣ ♥ ♦'.split()    def __init__(self):        self._cards = [f'{suit}{point}' \        for suit in self.suits for point in self.point]    def __len__(self):        return len(self._cards)    def __getitem__(self, position):        return self._cards[position]# 只要实现了__getitem__ 与 __len__# 这个类就自动支持切片、迭代、random.choice!poke = Poke()print(poke[0])      # 支持索引print(poke[:3])     # 支持切片for i in range(013):    print(random.choice(poke), end=' ')

让对象成为迭代器

__iter__ 与 __next__

这两个魔法方法能够让类实例对象成为一个迭代器(后面的课程会详细讲解),比如下面的代码就是一个执行反向循环的迭代器。

class Reverse:    """对一个序列执行反向循环的迭代器。"""    def __init__(self, data):        self.data = data        self.index = len(data)    def __len__(self):        return len(self.data)    def __iter__(self):        return self    def __next__(self):        if self.index == 0:            raise StopIteration        self.index = self.index - 1        return self.data[self.index]# 只要实现了 __iter__ 和 __next__# 这个类的实例就自动具有迭代器的特性it = Reverse('!uoy evol I')for _ in range(0len(it)):    print(next(it), end='')
除了上述的那些魔法方法,还有其他的一些方法,比如:__new__、__unicode__、__call__、__setattr__、__getattr__、__getattribute__、__delattr__、__setitem__、__delitem__、__del__、__dir__、__dict__、__exit__、__all__等等,由于这些方法基本用不到,在这里就不多占篇幅一一讲解了。(事实上,我也不知道还有这么多(ಥ◡ಥ))

好了,关于类的初次见面就到这里,我相信同学们对类已经有了一定的认识,下节课我们再深入学习一下类的继承与类装饰器。同学们,下课!

本节课的课后作业

实现一个自定义的字典类。要求有dict_obj[key] 和 dict_obj.get(key) 两种获取值的方法,以及 keys()、values() 和 items() 方法。

最新文章

随机文章