前言
类属性和实例变量区别比较大,本文均为举例说明,为例实现变量值的动态变化,采用redis数据库,不过读者不用担心,对于涉及到redis数据库的地方都会通俗到极致。
正文
首先,有这样一段代码。
import databases as db
class Test:
mydb = db.DatabaseRun() # 创建数据库实例
data = mydb.redis_conn.get('test') # 获取test的键值
res = mydb.redis_conn_excute()
def __init__(self, name):
self.name = name
def call(self):
print(self.res)
if __name__ == '__main__':
while True:
r = Test('test')
r.call() # 在此处添加一个断点
time.sleep(2) # 在此处添加一个断点
解释一下,这里我预先写了一个操作数据库的py文件为databases,跟本文没啥关系,只要知道是操作数据库的就行。
这段代码,定义了一个类变量res,变量值读取了redis数据库中test的键值。
接下来,我们进入redis中,新建一个test键,值为a。
然后我们 debug运行python代码,来到第一个断点,此时已经创建了一个实例。
我们来看一下实例里各变量的值。
看到此时res类变量值为['a'],是我们在redis中设置的值,接下来,来到下一个断点,即将休眠2秒,此时我们修改redis中test的键值为b。
接下来继续往下运行python,执行休眠2秒,循环完成一次,进入下一次循环,创建实例,来到断点
此时我们来看一下各变量的值。
神奇的事情出现了,res的值仍然为['a']而不是我们期待的['b']。
如果我们关闭程序,重新运行,则res的值为['b']
接下来我们加入实例变量,并将redis中test的值改回a。
更改代码:
import random
import databases as db
import pypinyin
import time
class Test:
mydb = db.DatabaseRun()
data = mydb.redis_conn.get('test')
res = mydb.redis_conn_excute()
def __init__(self, name):
self.name = name
self.mydb_2 = db.DatabaseRun() # 建立数据库对象
self.data_2 = self.mydb_2.redis_conn.get('test') #查找test值
self.res_2 = self.mydb_2.redis_conn_excute() # 获取返回值,此为被观察变量
def call(self):
print(self.res)
if __name__ == '__main__':
while True:
r = Test('test')
r.call()
time.sleep(2)
这里增加了一个res_2变量来观察。
重设redis中test值为a:
接下来dubug运行至第一个断点,看一下各变量值。
此时类变量res和实例变量res_2值均为['a'],为redis数据库内test的值
接下来我们修改redis中test的键值为c。
继续往下运行python,休眠2秒后进入第二次循环,创建实例,来到第一个断点,看一下各变量的值。
神奇的一幕又出现了,类变量res值没变,实例变量res_2值变为了['c']。
接下来我们结束运行,重新debug,来到第一个断点,查看变量值。
此时类变量与实例变量值均为['c']。
分析与结论
在python中类变量是在声明类时创建的,类声明完毕后即建立了变量,并存于内存之中,其值不会受任何实例的影响,变量为所有实例共用,在程序结束以前,不会因外界的改变而改变(例如本例中类变量获取途径为读取数据库,类变量的值不会因为数据库内值改变而改变),是静态的,用鞭子抽也不会动弹的变量,如果想修改类变量值,需在方法内运用global关键字(千万不要这么做,90%的风险被公司开除)。
而实例变量则在创建实例时,由__init__方法进行初始化,每创建一次实例都会重新定义实例变量的值(本例中重新读取一次数据库)。
所以,对于永远不会变的变量,还是写到类变量里面,例如某些常量(例如requests模块连接时用到的请求头字典),这样避免了多次实例化时浪费内存。