Search code examples
python-3.xclassneural-networkpytorchself

Using self in init part of a class in Python


Is there any difference between the following two codes related to initializing a class in Python?

class summation: 
    def __init__(self, f, s): 
        self.first = f 
        self.second = s 
        self.summ = self.first + self.second
    .
    .
    .

class summation: 
    def __init__(self, f, s): 
        self.first = f 
        self.second = s 
        self.summ = f + s
    .
    .
    .

If there exists any difference, what is that, and which code is preferable?

Edit: I am going to write an artificial neural network with Python (and Pytorch). In fact, the above two codes are just some examples. In the actual case, I have seen in various resources that when there exists self.input = input in the initialization of a class, in other parts it is used as self.input, not input.

My questions: What are the differences between these two approaches? Why is the use of self.input preferable, in my case?

Example: (from https://docs.dgl.ai/en/latest/tutorials/models/1_gnn/4_rgcn.html#sphx-glr-tutorials-models-1-gnn-4-rgcn-py)

import torch
import torch.nn as nn
import torch.nn.functional as F
from dgl import DGLGraph
import dgl.function as fn
from functools import partial

class RGCNLayer(nn.Module):
    def __init__(self, in_feat, out_feat, num_rels, num_bases=-1, bias=None,
                 activation=None, is_input_layer=False):
        super(RGCNLayer, self).__init__()
        self.in_feat = in_feat
        self.out_feat = out_feat
        self.num_rels = num_rels
        self.num_bases = num_bases
        self.bias = bias
        self.activation = activation
        self.is_input_layer = is_input_layer

        # sanity check
        if self.num_bases <= 0 or self.num_bases > self.num_rels:
            self.num_bases = self.num_rels

        # weight bases in equation (3)
        self.weight = nn.Parameter(torch.Tensor(self.num_bases, self.in_feat,
                                                self.out_feat))
        if self.num_bases < self.num_rels:
            # linear combination coefficients in equation (3)
            self.w_comp = nn.Parameter(torch.Tensor(self.num_rels, self.num_bases))

        # add bias
        if self.bias:
            self.bias = nn.Parameter(torch.Tensor(out_feat))

        # init trainable parameters
        nn.init.xavier_uniform_(self.weight,
                                gain=nn.init.calculate_gain('relu'))
        if self.num_bases < self.num_rels:
            nn.init.xavier_uniform_(self.w_comp,
                                    gain=nn.init.calculate_gain('relu'))
        if self.bias:
            nn.init.xavier_uniform_(self.bias,
                                    gain=nn.init.calculate_gain('relu'))

    def forward(self, g):
        if self.num_bases < self.num_rels:
            # generate all weights from bases (equation (3))
            weight = self.weight.view(self.in_feat, self.num_bases, self.out_feat)
            weight = torch.matmul(self.w_comp, weight).view(self.num_rels,
                                                        self.in_feat, self.out_feat)
        else:
            weight = self.weight

        if self.is_input_layer:
            def message_func(edges):
                # for input layer, matrix multiply can be converted to be
                # an embedding lookup using source node id
                embed = weight.view(-1, self.out_feat)
                index = edges.data['rel_type'] * self.in_feat + edges.src['id']
                return {'msg': embed[index] * edges.data['norm']}
        else:
            def message_func(edges):
                w = weight[edges.data['rel_type']]
                msg = torch.bmm(edges.src['h'].unsqueeze(1), w).squeeze()
                msg = msg * edges.data['norm']
                return {'msg': msg}

        def apply_func(nodes):
            h = nodes.data['h']
            if self.bias:
                h = h + self.bias
            if self.activation:
                h = self.activation(h)
            return {'h': h}

        g.update_all(message_func, fn.sum(msg='msg', out='h'), apply_func)

Solution

  • No. there is no difference between these two approaches in your case with this level of information. but could they? Yes. they could. if they have some modifications in their setters or getters. later in my answer I'll show you how.

    First of all, I prefer using this one:

    class summation: 
        def __init__(self, f, s): 
            self.first = f 
            self.second = s 
        @property
        def summ(self):
            return self.first+self.second
    

    the above implementation calculates the summation on demand. so when you change self.first or self.second, summ will be calculated automatically. you can access the sum as you did before.

    s = summation(1,9)
    print(s.summ)
    # 10
    s.first = 2
    s.second = 3
    print(s.summ)
    # 5
    

    So, How could they be different?

    let's implements them as follows. in setters I doubled the inputs to show you how setters can affect the results. it's just an imaginary example and is not exactly what you wrote.

    class summation1: 
        def __init__(self, f, s): 
            self.first = f 
            self.second = s 
            self.summ = self.first + self.second
    
        @property
        def first(self):
          return self.__first
    
        @first.setter
        def first(self,f):
          self.__first = f*2
    
        @property
        def second(self):
          return self.__second
    
        @second.setter
        def second(self,s):
          self.__second = s*2
    
    
    class summation2: 
        def __init__(self, f, s): 
            self.first = f 
            self.second = s 
            self.summ = f + s
    
        @property
        def first(self):
          return self.__first
    
        @first.setter
        def first(self,f):
          self.__first = f*2
    
        @property
        def second(self):
          return self.__second
    
        @second.setter
        def second(self,s):
          self.__second = s*2
    

    now let's take a look at the outputs:

    a = 3
    b = 2
    s1 = summation1(a,b)
    s2 = summation2(a,b)
    
    print(s1.summ)
    # 10
    print(s2.summ)
    # 5
    

    so, if you are not sure what to choose between those two, maybe the first approach is what you need.