在 Python 中,类变量和实例变量有以下区别和联系:
区别:
定义位置:
- 类变量是在类的定义内部,但在任何方法之外定义的变量。
- 实例变量是在类的方法(如
__init__
方法)中通过self
关键字定义的变量。
作用范围:
- 类变量属于类本身,被类的所有实例共享。
- 实例变量属于类的某个特定实例,每个实例都有自己独立的实例变量副本。
访问方式:
- 类变量可以通过类名直接访问,也可以通过实例访问。
- 实例变量只能通过实例访问。
修改影响:
- 对类变量的修改会影响到所有实例。
- 对实例变量的修改只影响当前实例。
联系:
都用于存储与类或实例相关的数据。
实例变量的查找会先在实例自身的属性中查找,如果没有找到,会去类中查找类变量。
下面是一个示例代码,展示了类变量和实例变量的区别:
class MyClass:
class_variable = "这是类变量" # 定义类变量
def __init__(self, instance_variable_value):
self.instance_variable = instance_variable_value # 定义实例变量
# 创建实例
instance1 = MyClass("实例 1 的值")
instance2 = MyClass("实例 2 的值")
# 访问类变量
print(MyClass.class_variable)
print(instance1.class_variable)
print(instance2.class_variable)
# 访问实例变量
print(instance1.instance_variable)
print(instance2.instance_variable)
# 修改类变量
MyClass.class_variable = "修改后的类变量"
# 此时所有实例访问到的类变量都被修改
print(instance1.class_variable)
print(instance2.class_variable)
# 修改实例变量
instance1.instance_variable = "修改后的实例 1 的变量"
# 只有 instance1 的实例变量被修改
print(instance1.instance_variable)
print(instance2.instance_variable)
延申:类属性和实例属性
类属性和实例属性在 Python 中有以下区别和联系:
区别:
定义位置:
- 类属性在类的定义中直接定义,通常在方法之外。
- 实例属性在类的方法中通过
self
关键字进行定义,常见于__init__
方法中。
归属对象:
- 类属性属于类本身,为类的所有实例所共享。
- 实例属性属于类的具体实例,每个实例都有自己独立的实例属性值。
访问方式:
- 类属性可以通过类名直接访问,也能通过实例访问。
- 实例属性只能通过实例来访问。
修改影响:
- 对类属性的修改,会影响到所有通过该类创建的实例。
- 对实例属性的修改,只影响当前实例,不会影响其他实例和类属性。
联系:
都是用于存储与类或实例相关的数据。
当通过实例访问一个属性时,如果实例中没有该属性,会自动到类中查找对应的类属性。
下面是一个示例来说明类属性和实例属性的区别和联系:
class MyClass:
class_attribute = "这是类属性" # 定义类属性
def __init__(self, instance_attribute_value):
self.instance_attribute = instance_attribute_value # 定义实例属性
# 创建实例
obj1 = MyClass("实例 1 的值")
obj2 = MyClass("实例 2 的值")
# 访问类属性
print(MyClass.class_attribute)
print(obj1.class_attribute)
print(obj2.class_attribute)
# 访问实例属性
print(obj1.instance_attribute)
print(obj2.instance_attribute)
# 修改类属性
MyClass.class_attribute = "修改后的类属性"
# 此时所有实例访问到的类属性都被修改
print(obj1.class_attribute)
print(obj2.class_attribute)
# 修改实例属性
obj1.instance_attribute = "修改后的实例 1 的属性"
# 只有 obj1 的实例属性被修改
print(obj1.instance_attribute)
print(obj2.instance_attribute)
访问顺序(MRO):
在 Python 中,当访问属性时,会先查找实例属性,如果实例中没有该属性,再去查找类属性。
也就是说,实例属性的优先级高于类属性。当实例属性和类属性同名时,实例属性会屏蔽掉类属性。但是,当删除实例属性后,再使用相同的名称访问,将访问到类属性。
例如,以下代码中定义了一个类MyClass
,其中有一个类属性class_attribute
:
class MyClass:
class_attribute = "这是类属性"
obj = MyClass()
print(obj.class_attribute)
在上述代码中,通过实例obj
访问class_attribute
属性时,由于实例obj
本身没有该实例属性,所以会找到类中的class_attribute
类属性并输出。
如果给实例添加了与类属性同名的实例属性:
obj.instance_attribute = "这是实例属性"
print(obj.instance_attribute)
此时再访问instance_attribute
属性,将输出实例属性的值,类属性被屏蔽。
若删除实例属性:
del obj.instance_attribute
print(obj.class_attribute)
再次访问instance_attribute
属性时,由于实例的该属性不存在了,就会显示出类属性的值。
这里又涉及到一个多继承的问题:
在 Python 中,多继承的查找顺序可以通过方法解析顺序(Method Resolution Order,简称 MRO)来确定。Python 使用 C3 算法来计算多继承的顺序。
Python 3 中,多继承的查找顺序是广度优先,即从左到右,先在同一级的父类中查找,然后再向上一级查找,直到找到所需的方法或属性,或者到达object
类。如果在当前类中找到了方法,就直接执行,不再继续搜索。如果没有找到,就按照 MRO 顺序查找下一个类中是否有对应的方法。
要查看某个类的 MRO 顺序,可以使用该类的__mro__
属性,它返回一个元组,展示了方法的搜索顺序。
例如,对于以下类的定义:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
可以通过D.__mro__
来查看D
类的方法解析顺序。
需要注意的是,Python 2 中的经典类(不继承自object
的类)采用深度优先的查找顺序,而新式类(继承自object
的类)采用广度优先的查找顺序。为了保证代码在 Python 2 和 Python 3 中都能正确运行,建议在定义类时,如果没有父类,统一继承自object
。
另外,在多继承中,如果多个父类有相同的方法名,可能会导致方法的调用具有一定的不确定性,并且可能出现一些复杂的情况。为了避免潜在的问题,建议尽量减少多继承的使用,或者确保类的设计清晰合理,避免方法名的冲突。如果确实需要多继承,并且存在同名方法,可以通过明确指定调用某个父类的方法来避免歧义。例如使用super(父类名, self).方法名()
的方式来指定调用特定父类的方法。