Python类(class)参数self的理解
问题
在python类中,经常可以看见self参数,比如:
class Net(nn.Module):def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,is_training=True):super().__init__()self.num_inputs = num_inputsself.training = is_trainingself.lin1 = nn.Linear(num_inputs, num_hiddens1)self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)self.lin3 = nn.Linear(num_hiddens2, num_outputs)self.relu = nn.ReLU()
那么这个参数该如何理解呢?
self
是 Python 类(class)中最核心、最关键的概念之一。理解 self
对于掌握面向对象编程至关重要。
一、self
是什么?
self
是一个约定俗成的名称,它代表类的(实例)
- 每当你创建一个类的实例(对象),Python 会自动把这个实例作为第一个参数传递给类中的方法。
self
就是用来接收这个“实例本身”的参数。
二、举个例子来理解
class Person:def __init__(self, name):self.name = name # 把传入的 name 保存到这个实例中def greet(self):print(f"Hello, I am {self.name}")# 创建一个实例
p = Person("Alice")
p.greet() # 输出:Hello, I am Alice
我们来一步步分析:
p = Person("Alice")
- 创建了一个
Person
类的实例,叫p
。 - Python 自动调用
__init__
方法,并把p
这个实例作为第一个参数传进去。 - 所以在
__init__
中,self
就是p
。 self.name = name
相当于p.name = "Alice"
。
- 创建了一个
p.greet()
- 调用
greet
方法。 - Python 自动把
p
作为第一个参数传给greet
方法。 - 所以在
greet
中,self
也是p
。 self.name
就是p.name
,也就是"Alice"
。
- 调用
三、为什么需要 self
?
因为一个类可以创建多个实例,每个实例有自己的数据。self
让每个方法都知道“我现在操作的是哪一个实例”。
p1 = Person("Alice")
p2 = Person("Bob")p1.greet() # Hello, I am Alice
p2.greet() # Hello, I am Bob
虽然 p1
和 p2
都调用同一个 greet
方法,但 self
不同,所以访问的数据也不同。
四、self
在哪里出现?
定义方法时:必须把
self
作为第一个参数。def greet(self):...
访问实例变量或调用其他方法时:用
self.xxx
。self.name = "Alice" self.greet()
在
__init__
中初始化属性:def __init__(self, name):self.name = nameself.age = 0
五、重要说明
self
只是一个名字,你 technically 可以叫它this
、myself
或其他名字,但 强烈建议始终使用self
,这是 Python 社区的统一约定。self
参数在调用方法时不需要手动传,Python 会自动传。p.greet() # 正确,不需要写 p.greet(p)
六、什么是需要self.param = param
回到神经网络代码
dropout1, dropout2 = 0.2, 0.5class Net(nn.Module):def __init__(self, num_inputs, num_outputs, num_hiddens1, num_hiddens2,is_training = True):super(Net, self).__init__()self.num_inputs = num_inputsself.training = is_trainingself.lin1 = nn.Linear(num_inputs, num_hiddens1)self.lin2 = nn.Linear(num_hiddens1, num_hiddens2)self.lin3 = nn.Linear(num_hiddens2, num_outputs)self.relu = nn.ReLU()def forward(self, X):H1 = self.relu(self.lin1(X.reshape((-1, self.num_inputs))))# 只有在训练模型时才使用dropoutif self.training == True:# 在第一个全连接层之后添加一个dropout层H1 = dropout_layer(H1, dropout1)H2 = self.relu(self.lin2(H1))if self.training == True:# 在第二个全连接层之后添加一个dropout层H2 = dropout_layer(H2, dropout2)out = self.lin3(H2)return outnet = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
self.lin1
表示:这个网络实例的第一个线性层。self.relu
表示:这个网络实例的激活函数。- 每个
Net
实例都有自己的一套lin1
,lin2
等层,self
帮助区分和管理它们。
一个问题,对于实例中的参数,为什么这里定义了
self.num_inputs = num_inputs
self.training = is_training
但却没有定义
self.num_outputs = num_inputs
self.num_hiddens1 = num_hiddens1
即为什么有些参数被保存为实例变量,而有些没有?
答案核心:是否需要在类的其他方法中使用这些参数
在 Python 类中,是否将某个变量保存为 self.xxx
,取决于你是否希望在类的其他方法中访问它。
可以看到,在后续的def forward()中,调用了self.num_inputs、self.training,而没有调用到num_outputs
、num_hiddens1
总结
概念 | 说明 |
---|---|
self | 代表当前类的实例(对象) |
作用 | 让方法可以访问和修改实例的数据 |
出现位置 | 方法定义的第一个参数,访问属性/方法时用 self.xxx |
是否手动传 | 否,Python 自动传 |
为什么重要 | 实现“一个类,多个实例,各自独立” |
什么时候需要self.param=param | 是否在后续的函数或方法中需要调用该变量 |