跳转至

Python类与对象浅析

我在学习李沐动手学习深度学习的过程中,在学习层与块的概念时,对于实现自定义块的代码中的super函数的具体功能产生疑问,查找资料后做此记录。

Python面向对象

何为面向对象

在学习C语言的时候,我们倾向于将解决问题的过程显式地体现在一系列函数与数据中,通过按照特定顺序执行函数达到解决问题的效果。

在面向过程中,我们定义的函数和数据往往离散的分布在代码的各处,这大大降低了代码的可读性和扩展性。对代码重用性,灵活性和扩展性的需求催生了面向对象编程架构的诞生,面向对象把事物抽象成对象的概念,就是说这个问题里面有哪些对象,然后给对象赋一些属性和方法,然后让每个对象去执行自己的方法,问题得到解决。

类与对象是面向对象程序设计的核心概念。

  • 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。

类是对象的抽象,对象是类的具体。Python将一系列属性和方法封装在了名为类的容器中,并通过实例化对象提供访问类成员的接口。

为什么要面向对象

面向对象具有易维护、易复用、易扩展的特点,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护。相对的,面向对象相比面向过程(以C为代表)有着较低的性能表现。

如何面向对象

类继承 在Python中,我们使用class语句创建一个新类

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Emplooyee:
    'a class including all emploees'
    empCount = 0 # 类变量,在所有实例中共享

    def _init_(self, name, salary):
        self.name = name
        self.salary = salary
        Employee.empCount += 1

    def displayCount(self):
        print "Total employee %d" % Employee.empCount

    def dispalyEmployee(self):
        print "Name: ", self.name, ", Salary", self.salary
- empCount为类变量,使用Employee.empCount访问 - __init__()方法称为类构造函数,在创建实例时会自动调用此方法 - self代表类的实例,在定义类方法时必须出现在参数表头

你可以使用类的名称进行实例化,并使用.来访问对象的属性

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# create an object for class Employee
emp1 = Employee("Zara", 2000)
emp2 = Employee("Sam", 3000)
# vist a member
emp1.displayEmployee()
emp2.displayEmployee()
print "Total employee %d" % Employee.empCount
# 输出结果如下:
# Name :  Zara ,Salary:  2000
# Name :  Manni ,Salary:  5000
# Total Employee 2
# 可以看出,类变量不会随着新对象的实例化而重新生成,他是静态的
你也可以添加,删除,修改类属性
Python
1
2
3
emp1.age = 7  # 添加一个 'age' 属性
emp1.age = 8  # 修改 'age' 属性
del emp1.age  # 删除 'age' 属性
通过调用析构函数__del__()实现对象的销毁
Python
1
2
a = 40 # 声明了数据对象a
del a # 销毁a
你也可以在类中自定义析构函数
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Point: 
    def __init__( self, x=0, y=0): 
        self.x = x 
        self.y = y 

    def __del__(self): 
        class_name = self.__class__.__name__ 
        print class_name, "销毁" 

pt1 = Point() 
pt2 = pt1 
pt3 = pt1 
print id(pt1), id(pt2), id(pt3) # 打印对象的id 
del pt1 
del pt2 
del pt3
# 输出结果:
#3083401324 3083401324 3083401324
# Point 销毁
# pt2与pt3是对pt1的引用,他们对应同一片内存空间,在销毁pt1时,# pt2与pt3同步销毁
类继承 类的继承机制是面向对象中实现代码重用的方法之一
Python
1
2
3
# 类继承基础语法
class 派生类名(基类名)
    ...
如果子类不重写父类的构造方法,实例化时会自动调用父类构造方法
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name

class Son(Father):
    def getName(self):
        return 'Son '+self.name

if __name__=='__main__':
# 当python文件作为主程序运行时表达式为1,作为外部库import时为0
    son=Son('runoob')
    print ( son.getName() )
# 输出
# name: runoob
# Son runoob
如果子类重写父类构造方法,实例化时仅调用子类构造方法
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name) )
    def getName(self):
        return 'Father ' + self.name

class Son(Father):
    def __init__(self, name):
        print ( "hi" )
        self.name =  name
    def getName(self):
        return 'Son '+self.name

if __name__=='__main__':
    son=Son('runoob')
    print ( son.getName() )
# 输出
# hi
# Son runoob
如果子类重写构造方法,亦要继承父类构造方法,使用super关键字
Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Father(object):
    def __init__(self, name):
        self.name=name
        print ( "name: %s" %( self.name))
    def getName(self):
        return 'Father ' + self.name

class Son(Father):
    def __init__(self, name):
        super(Son, self).__init__(name)
        # 注:python支持super()方法省略参数传递
        # 与super()._init_(name)有相同功能
        print ("hi")
        self.name =  name
    def getName(self):
        return 'Son '+self.name

if __name__=='__main__':
    son=Son('runoob')
    print ( son.getName() )
# 输出
# name: runoob
# hi
# Son runoob

super函数与实例构造过程及其在PyTorch中的应用

super函数的基础用法

在Python中,我们通常使用super()函数来引用父类。基础的用法是带有两个参数:第一个是子类,第二个是对象,即super(subclass, instance)。其用途是返回一个临时对象,该对象绑定到父类的方法上,而不是子类的方法。

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Parent():
    def hello(self):
        print("Hello from Parent")

class Child(Parent):
    def hello(self):
        super(Child, self).hello()
        print("Hello from Child")

c = Child()
c.hello()
# 输出
# Hello from Parent
# Hello from Child

本质上,super(Child, self).hello()就等于Parent.hello(self)。使用super的好处是不必显式写出父类具体是哪一个,这样有利于后续维护与更新(比如改变了父类,但是这段代码不用改)。

super在类定义中的特殊用法

在类的定义中,super函数的使用可以更加简化。我们可以不必提供任何参数,Python解释器会自动填充参数。

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Parent():
    def hello(self):
        print("Hello from Parent")

class Child(Parent):
    def hello(self):
        super().hello()
        print("Hello from Child")

c = Child()
c.hello()

这段代码的输出与前一段代码相同。Python解释器在执行super().hello()时,会自动填充当前类和实例。也就是说,在类定义中,super()等价于super(Child, self)。实际上解释器使用的是__class__变量,每个函数中都能访问这个变量,它指向这个函数对应的类。

init & new 与实例化过程

Python的__init__方法在面向对象编程中非常重要。它是一种特殊的方法,用于在创建对象时初始化对象的状态。然而,需要注意的是,__init__方法并不是实例化一个对象的过程,它仅仅是初始化对象状态的一部分。

实际上,真正的实例化过程是由__new__方法完成的。__new__方法是一个静态方法,用于创建并返回一个对象。在Python中,调用Class()实际上包含两个步骤:首先调用__new__方法创建一个新对象,然后调用__init__方法来初始化该对象。

Python
1
2
3
4
5
6
7
8
9
class MyClass():
    def __new__(cls):
        print("Creating a new object")
        return super().__new__(cls)

    def __init__(self):
        print("Initializing the object")

m = MyClass()

这段代码会输出:

Python
1
2
Creating a new object
Initializing the object

通过这段代码,我们可以看到实例化过程实际上包括__new____init__两个步骤。

super().__init__()

在使用PyTorch构建神经网络模型时,我们通常需要自定义模型类并继承PyTorch的基础模块类nn.Module。在自定义的模型类中,通常需要在__init__方法中调用super().__init__(),这是为了正确地初始化nn.Module类的内部状态。只有调用了super().__init__()之后,才能创建子模块:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self, input_size, output_size):
        # 下面两行代码,交换顺序就会报错
        super().__init__()
        self.linear = nn.Linear(input_size, output_size)

    def forward(self, x):
        return self.linear(x)

model = MyModel(3, 5)