问题
在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_inputs
self.training = is_training
self.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 = name self.age = 0
五、重要说明
-
self
只是一个名字,你 technically 可以叫它this
、myself
或其他名字,但 强烈建议始终使用self
,这是 Python 社区的统一约定。 -
self
参数在调用方法时不需要手动传 ,Python 会自动传。p.greet() # 正确,不需要写 p.greet(p)
六、什么是需要self.param = param
回到神经网络代码
python
dropout1, dropout2 = 0.2, 0.5
class 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_inputs
self.training = is_training
self.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))))
# 只有在训练模型时才使用dropout
if 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 out
net = Net(num_inputs, num_outputs, num_hiddens1, num_hiddens2)
self.lin1
表示:这个网络实例的第一个线性层。self.relu
表示:这个网络实例的激活函数。- 每个
Net
实例都有自己的一套lin1
,lin2
等层,self
帮助区分和管理它们。
一个问题,对于实例中的参数,为什么这里定义了
python
self.num_inputs = num_inputs
self.training = is_training
但却没有定义
python
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 | 是否在后续的函数或方法中需要调用该变量 |