Python-__init__函数
__init__ 是 Python 语言规定好的一个特殊函数。
它在 Python 中有一个专门的分类,叫做魔术方法(Magic Methods) 或 “Dunder 方法” (Dunder 是 “Double Underscore” 双下划线的缩写)。
它的“特殊”主要体现在以下三个方面:
1. 特殊的命名:__...__
__init__ 这个名字是 Python 规定死的。
- 它以两个下划线开头,并以两个下划线结尾。
- 在 Python 中,所有具有这种
__名字__格式的函数都是“魔术方法”。它们都是 Python 预先定义好的,用于在特定时机自动执行某些操作。 - 你不能随便起一个自己的
__my_func__名字并期望它有魔术功能。你只能重写 (override) Python 已经定义好的这些魔术方法,比如__init__、__str__(用于print(obj)时) 或__add__(用于obj1 + obj2时)。
2. 特殊的调用时机:自动调用
这是 __init__ 最特殊的地方。
- 你永远不需要手动调用
__init__函数。 - 当你通过“调用类名”来创建对象(实例化)时,Python 会自动为你调用它。
# 1. 定义 "节点" 类
class TrieNode(object):def __init__(self):# 节点应该包含这两个属性self.children = {}self.isEndOfWord = False# 2. 定义 "树" 类 (这是你的模板)
class Trie(object):def __init__(self):# "树" 的构造函数: 创建一个 "节点" 作为根self.root = TrieNode() def insert(self, word):""":type word: str:rtype: None"""# 1. 从根节点开始node = self.rootfor char in word:if char not in node.children:# 2. 创建一个 *新的 TrieNode*node.children[char] = TrieNode()# 3. 移动到子节点node = node.children[char]# 4. 标记单词结尾node.isEndOfWord = Truedef search(self, word):""":type word: str:rtype: bool"""node = self.rootfor char in word:if char not in node.children:return Falsenode = node.children[char]return node.isEndOfWorddef startsWith(self, prefix):""":type prefix: str:rtype: bool"""node = self.rootfor char in prefix:if char not in node.children:return Falsenode = node.children[char]return True
我们来分解一下 trie_node = TrieNode() 这一行代码:
- 构造 (Creation):Python 先调用一个(隐藏的)
__new__方法,在内存中创建了一个空白的TrieNode对象。 - 初始化 (Initialization):Python 立刻、自动地调用
__init__方法,并把你创建的这个空白对象作为第一个参数(self)传进去。 - 赋值 (Assignment):
__init__函数执行完毕(即self.children = {}和self.isEndOfWord = False完成后),这个已经初始化好的对象才被赋值给trie_node变量。
所以,__init__ 的特殊之处在于它是一个“钩子”(hook) 函数:你不需要关心它何时被调用,你只需要在 class 里定义好它,Python 就会在创建对象的正确时机自动触发它。
3. 特殊的角色:初始化器 (Initializer)
__init__ 的唯一角色就是“初始化”。
- 它不负责创建对象(那是
__new__的工作)。 - 它负责**“装修”**这个刚刚被创建出来的空白对象,给它装上它一出生就该有的属性(比如
self.root)。
__init__ 的用法非常固定和清晰。它几乎只用来做一件事:设置对象(self)的初始属性(也叫“实例变量”)。
通俗地说,__init__ 就是这个对象“出生”时必须运行的设置程序。你在这里定义:“凡是这个类的对象,一出生就必须有这些东西。”
“一般用法”的三种常见模式
这里有三个最常见的例子,从简单到复杂:
模式一:最简单的用法(如 TrieNode)
目的: 对象一出生,就给它设置好“空”的默认属性。
在 TrieNode 的例子中,我们不需要在创建时给它任何信息。但每个节点必须有一个 children 字典和 isEndOfWord 标志。
class TrieNode:def __init__(self):# 1. 你没有给任何参数# 2. 你在 self (对象自己) 身上 "装上" 初始属性print("一个新节点被创建了!")self.children = {} # 它天生就有一个空的 children 字典self.isEndOfWord = False # 它天生就不是单词结尾# 当你调用...
new_node = TrieNode()
# ...Python 自动为你调用 __init__
# 输出: "一个新节点被创建了!"# 现在 new_node 已经有了这两个属性:
print(new_node.children) # 输出: {}
print(new_node.isEndOfWord) # 输出: False
模式二:最常见的用法(如 Person)
目的: 创建对象时,接收外部参数,并用这些参数来设置属性。
假设你要创建一个 Person 类。一个人“出生”时,总得有个名字和年龄吧。
class Person:# `name` 和 `age` 是你创建对象时必须提供的参数def __init__(self, name, age):print(f"一个叫 {name} 的人被创建了!")# 把接收到的参数 `name` 赋值给 self 的 `name` 属性self.name = name# 把接收到的参数 `age` 赋值给 self 的 `age` 属性self.age = ageself.is_sleeping = False # 也可以设置一个默认值# 当你调用...
# ("Alice", 30) 会被传递给 __init__ 的 (name, age)
p1 = Person("Alice", 30)
# 输出: "一个叫 Alice 的人被创建了!"# p1 对象现在有了这些属性:
print(p1.name) # 输出: Alice
print(p1.age) # 输出: 30
模式三:嵌套用法(如 Trie)
目的: 对象在初始化时,创建其他类的对象作为自己的属性。
这就是你的 Trie (树) 的例子。Trie 树“出生”时,它不需要外部给它 children 字典(它自己不是节点),但它必须有一个 root 属性,而这个 root 是另一个对象(一个 TrieNode)。
class Trie:def __init__(self):# 1. 你没有给任何参数# 2. 你在 self (树) 身上 "装上" root 属性# 3. 这个属性的值, 是通过创建另一个类的实例 (TrieNode) 得到的print("一棵新的前缀树被创建了!")self.root = TrieNode() # 当你调用...
trie_tree = Trie()
# 输出: "一棵新的前缀树被创建了!"
# (在创建 Trie 的过程中, 它内部又自动调用了 TrieNode 的 __init__,
# 所以你可能还会看到 "一个新节点被创建了!")# trie_tree 对象现在有了 root 属性:
print(trie_tree.root) # 输出: <__main__.TrieNode object at 0x....>
print(trie_tree.root.children) # 输出: {}
总结:__init__ 的“使用规则”
你应该做的(Do):
- 定义实例属性:
__init__的首要工作就是定义self.attribute = ...。 - 接收创建参数:把
MyClass(arg1, arg2)传进来的arg1和arg2,通过__init__(self, arg1, arg2)接收,并存到self.attribute1 = arg1中。 - 执行一次性设置:如果对象一创建就需要执行某个动作(比如
self.setup_database_connection()),可以放在__init__里。
你不应该做的(Don’t):
return任何东西:__init__必须返回None。Python 会自动处理它,你绝对不要在__init__里写return self或return True。- 放复杂的逻辑:
__init__应该非常快。它只是用来“设置属性”的,不要在里面放需要 10 秒钟才能算完的复杂计算。那些应该单独写成一个方法(比如obj.calculate_complex_stuff())。
我们来“扮演”一下 Python 解释器,看看当你写 node.children[char] = __init__() 时它会怎么想。
(我们暂时假装你代码里的 node = self.root 这一行是 node = self,这样它至少能开始运行,以便我们能分析到你问的这一行。)
场景: 你调用 trie.insert("app")
node被设为self(即trie对象)。- 循环开始,
char是'a'。 if 'a' not in node.children:(假设trie.children是空的),条件为True。- Python 运行到你的关键代码行:
node.children['a'] = __init__()。 - Python 停住了。它会想:“
__init__是什么?你让我调用一个叫__init__的函数,但我不知道它在哪里。”
我一开始犯的错误:

为什么会这样?(两个核心错误)
我写的 __init__ 是一个方法 (Method),它“属于” Trie 类。它不是一个可以随处调用的全局函数 (Global Function)(像 print() 那样)。
错误 1:NameError(语法错误)
当只写 __init__() 时,Python 会在当前(insert 方法的)局部作用域和全局作用域里寻找一个独立的、名叫 __init__ 的函数。
它根本找不到。
它会立即崩溃,并抛出 NameError: name '__init__' is not defined。
错误 2:“那如果我改成 self.__init__() 呢?”(逻辑错误)
这是一个非常好的假设!我们来看看如果代码是 node.children['a'] = self.__init__(),会发生什么:
- Python:“哦!我知道
self.__init__是什么!它就是Trie类的那个初始化函数。好的,我来替你调用它!” self.__init__()开始执行。self.children = {}<-- 灾难!trie树刚刚把自己的children字典(里面可能已经有东西了)重置为了一个空字典!self.isEndOfWord = Falseself.__init__函数执行完毕。__init__函数的返回值是什么?根据 Python 规定,它必须返回None。- Python 回到
insert方法,继续执行赋值:node.children['a'] = None。
最终的结果是:
- 搞砸了
trie树(重置了它的children)。 - 往
node.children字典里存了一个None。 - 根本没有创造出一个新节点。
总结:想要的 vs 写的
- 想要做的: “请为我创建一个全新的节点对象 (Create a new
TrieNodeinstance)。” - 实际写的:
__init__()(这会导致NameError) - 可能想写的:
self.__init__()(这会重新初始化当前的Trie树并返回None)
正确的“命令” 不是调用 __init__。
正确的“命令”是调用类名:
node.children[char] = TrieNode()
这个命令才是告诉 Python:“请运行 TrieNode 类的完整创建流程(__new__ + __init__),并把那个全新的对象返回给我。”
