Python-类实例化后的内存空间问题
从数据结构和计算机基础知识的角度出发,一个新定义的类在编译阶段会创建一个类的命名空间,同时一个对象一旦被类实例化,也会开辟属于自己的内存空间;那它们之间的联系是什么,有哪些细节需要注意,下面跟着小涩一起去梳理。
类、实例化对象命名空间
直接上例子:
class A:
Country = '中国'
Area = 960
def __init__(self,name,age,country): # 这里多传一个参数也无所谓,只要没有赋值给self.属性就不开辟内存空间
self.name = name
self.age = age
def func1(self,x): # 实例对象绑定方法
print(self)
self.age += 1
def func2(cls, n): # 类方法/静态方法
cls.Area += n
def power(a,b): # 一般方法
return a ** b
if __name__ == "__main__":
print(A.__dict__)
# 打印:{'__module__': '__main__', 'Country': '中国', 'Area': 1060, '__init__': <function A.__init__ at 0x00000160F53077B8>, 'func1': <function A.func1 at 0x00000160F5307840>, 'func2': <function A.func2 at 0x00000160F53078C8>, 'func3': <function A.func3 at 0x00000160F5307950>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
a = A('alex',83,'印度')
print(a.__dict_)
# 打印:{'name': 'alex', 'age': 83}
rst1 = A.func3(2, 5) # 一般方法
print(rst1) # 32
A.func2(A, 100) # 类方法
print(A.Area) # 1060
显而易见,类的命名空间内存有除实例对象的属性外的所有内容,只要在类中有所定义,就会开辟空间存;即,
类的命名空间包括:静态变量/属性,定义到类中的所有方法(绑定方法、类方法、一般方法)
而目前看来实例对象,只包含其绑定的属性。
那就有一个疑问,实例对象如何访问到它的绑定方法,因为绑定方法确实是放在类的命名空间。
类与实例对象的关联
python的底层源码大多基于C语言,查阅源码,实例对象之所以能访问他的绑定方法,其实用到了间接访问的原理,即C中的指针的概念;也就是说,
实例对象的命名空间包括:绑定的属性,类指针(用以指向类首元素的地址)。
如图所示,它们之间的关系:
调用习惯与索引顺序
索引顺序
-
对于属性/变量
对象中的变量只属于对象本身,每个对象有属于自己的空间来存储对象的变量;如果自己没有就引用类的,如果类也没有就报错。对于类来说,类中的变量所有的对象都是可以读取的,并且读取的是同一份变量。
-
对于方法(略,主要体现在子父类的继承上)
说明一点,绑定方法虽然是存储在类的命名空间,但是对象能间接访问到,不存在这里所说的索引顺序。
调用的习惯
-
方法
- 类方法
- 类名.类方法(类名,xx) # 类名作为参数不能丢
- 对象.类方法(xx) # 无类名做参数,这种调用极少使用,容易出问题也没有实际意义,不建议。
- 绑定方法:对象.绑定方法() # <==> 类名.绑定方法(对象)
- 一般方法:类名.一般方法()
- 类方法
-
属性/变量
-
绑定属性: 对象.绑定属性
-
静态属性
-
类名.静态变量
-
对象.静态变量
针对能不能进行重新赋值的问题,很多书本上没有说明清楚,在此进行重点说明,见下一节说明
-
-
注意细节:类的静态属性
结论: 只要以对象打头对静态属性/变量进行写操作(赋值修改),不论是以对象.类方法的方式,还是对象.静态变量的方式;都要分两种情况具体分析
-
静态属性属于可变对象:列表,字典。
可以进行写操作,真正意义上的对静态属性进行了写操作,与“类名.静态变量”这种调用方式等效。示例如下
# 1 class A: Country = ['中国'] # 静态变量/静态属性 存储在类的命名空间里的 def __init__(self,name,age,country): # 绑定方法 存储在类的命名空间里的 self.name = name self.age = age def func1(self): print(self) a = A('alex',83,'印度') b = A('wusir',74,'泰国') a.Country[0] = '日本' print(a.__dict__) # {'name': 'alex', 'age': 83},说明没有创建内存空间, print(a.Country) # ['日本'] print(b.Country) # ['日本'] print(A.Country) # ['日本'] 静态变量被修改
-
静态属性属于不可变对象: 数值型、字符串、元祖、空None
也可以进行写操作,符合语法,但是写了个寂寞,它是在对象命名空间上重新开辟了与静态属性同名的绑定属性并对其赋值。示例如下
#2-1 class A: Country = '中国' # 静态变量/静态属性 存储在类的命名空间里的 def __init__(self,name,age,country): # 绑定方法 存储在类的命名空间里的 self.name = name self.age = age def func1(self): print(self) a = A('alex',83,'印度') b = A('wusir',74,'泰国') a.Country = '日本' print(a.Country) # 日本, 45行代码,使得对象a在自己空间中创建了Country同名的属性,实则上对类的静态变量没有修改成功 print(b.Country) # 中国,对象b没创建Country的属性空间,所以调用的还是类的静态变量Country print(A.Country) # 中国 #2-2 class A: Country = ('中国', '香港') # 静态变量/静态属性 存储在类的命名空间里的 def __init__(self,name,age,country): # 绑定方法 存储在类的命名空间里的 self.name = name self.age = age def func1(self): print(self) a = A('alex',83,'印度') b = A('wusir',74,'泰国') # a.Country = ('日本','东京') print(a.__dict__) # {'name': 'alex', 'age': 83, 'Country': ('日本', '东京')},说明在对象里创建内存空间,因为类的静态对象属于不可变对象-元祖 print(a.Country) # ('日本', '东京') print(b.Country) # ('日本', '东京') print(A.Country) # ('日本', '东京')