Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。
- 数据与操作紧密相关
- 对象有许多状态需要维护,可以使用类中的属性来保存状态。
- 需要生成多个仅在部分属性不同的实例,可以使用类作为模板。
- 不同对象存在公共parent-child的层次关系,可以使用继承来复用代码。
- 隐藏对象的实现细节,只对外公开接口。
在Python中,类变量和实例变量是两个不同的概念:
-
类变量(Class Variable)
-
定义在类内部,但不在任何方法之内
- 被该类的所有实例对象所共享
- 可以通过类名或实例对象访问
-
用于定义与这个类相关的特征或属性
-
实例变量(Instance Variable)
-
定义在类内部的方法之内
- 每个实例对象拥有属于自己的变量副本
- 只能通过实例对象访问
- 用于定义实例对象的个性化特征或状态
例如:
class Person:
species = 'Human' # 类变量
def __init__(self, name):
self.name = name # 实例变量
p1 = Person('John')
p2 = Person('Mary')
print(p1.species) # Human
print(p2.species) # Human
print(p1.name) # John
print(p2.name) # Mary
综上,类变量用于定义类的通用属性,实例变量用于定义实例的独特属性。区分二者是理解Python面向对象的关键。
class Employee:
'所有员工的基类'
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 displayEmployee(self):
print "Name : ", self.name, ", Salary: ", self.salary
必须有一个额外的第一个参数名称, 按照惯例它的名称是 self,self 不是 python 关键字,换成其他词语也行。
emp1 = Employee("Zara", 2000)
emp1.displayEmployee()
通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
继承语法 class 派生类名(基类名)
调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数
,这点在代码上的区别如下:
class Base:
def base_method(self):
print("Base method")
class Derived(Base):
def derived_method(self):
# 调用基类方法要加类名前缀
Base.base_method(self)
# 调用普通函数
print("Hello")
d = Derived()
d.derived_method()
# 输出
Base method
Hello
在Derived类中:
区别在于:
- 调用基类方法需要指明方法所属的基类
- 基类方法需要传入self,指代实例自己
而对于普通函数,只需要直接调用即可,不需要self参数。
这与Python的名称空间和面向对象实现有关,是理解Python类继承的关键点。
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__len__: 获得长度
__cmp__: 比较运算
__call__: 函数调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__truediv__: 除运算
__mod__: 求余运算
__pow__: 乘方
PyTorch 中如果继承torch.nn.Module,执行__call__会转接到forward方法
torch.nn.Module 的 __call__ 方法会调用 forward 方法,并且在调用 forward 之前和之后还会执行一些其他的操作,比如设置钩子(hooks)和调用 _check_forward_hooks 等。
以下是 torch.nn.Module 中 __call__ 方法的简化实现逻辑:
class Module:
def __call__(self, *input, **kwargs):
# 在调用 forward 之前执行一些操作
# 例如,调用 forward pre-hooks
for hook in self._forward_pre_hooks.values():
result = hook(self, input)
if result is not None:
if not isinstance(result, tuple):
result = (result,)
input = result
# 调用 forward 方法
result = self.forward(*input, **kwargs)
# 在调用 forward 之后执行一些操作
# 例如,调用 forward hooks
for hook in self._forward_hooks.values():
hook_result = hook(self, input, result)
if hook_result is not None:
result = hook_result
return result
__call__ 方法:当你实例化一个 Module 并调用它时(例如 model(input)),Python 会调用 __call__ 方法。
forward 方法:__call__ 方法内部会调用 forward 方法,这是你需要在子类中重写的方法,用于定义前向传播的逻辑。
- 钩子(Hooks):在调用
forward 之前和之后,__call__ 方法会处理一些钩子。这些钩子可以用于调试、可视化或其他目的。
在Python中可以通过特殊方法__iadd__来对+=符号进行重载。
__iadd__需要定义在类中,用于指定+=操作时的具体行为。例如:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v1 += v2
print(v1.x, v1.y) # 4, 6
这里我们定义了__iadd__方法,用于实现两个Vector对象使用+=时的相加操作。
__iadd__方法的参数是另一个要相加的对象,在方法内部我们实现了两个向量的分量相加,并返回self作为结果。这样就实现了+=的运算符重载。
此外,Python还提供了__add__特殊方法用于重载+符号,但是__iadd__和__add__有以下区别:
- __add__返回一个新的对象,不改变原有对象。
- __iadd__在原有对象的基础上进行操作,并返回对原对象的引用。
所以对可变对象进行+=操作时,通常需要实现__iadd__方法。
https://www.runoob.com/python/python-object.html