python中的对象与引用

在python中万物皆为对象,包括变量、函数、类以及其实例化的对象。

python中所谓的赋值其实传的是对象指向的引用,也就是说传的是地址。

1. 可变变量和不可变变量

python中存在两大类型的对象,不可变对象(如基本类型中的元组、字符串、数值)和可变对象(如基本类型中的列表、字典、集合)。

对于不可变对象,我们可以理解为存在于连续内存空间的统一整体,其同生共死,不可修改。

而对于可变对象,我们可以理解为其每个元素实质上为对应不可变对象的引用,如果存在可变对象的嵌套,则重复引用直至最深层的不可变对象。 修改可变对象的某个元素,实质上是在内存中创建新的数据,同时将原可变对象对应位置的引用指向该内存。

a = [1, 2]
b = a     # 传址,b和a指向同一内存地址
a[0] = 100   # 创建新的内存存放100,同时列表的第一个元素指向新的内存地址。注意:并非将原内存地址上的0修改为100
print(b)    # [100,2] ,因为都是地址引用,所以同步更新         
a = (1,2, [3,4])
a[2].append(5)
print(a)   # (1, 2, [3, 4, 5]), 不可变对象a所指向的内存地址并没有变化

python中,在创建数值和字符串类型变量时,会检测原有内存空间内是否存在该数值/字符串,若存在则会将变量直接指向该内存,否则先创建内存存放数值/字符串,再将变量指向该内存。

a = 1
b = 1
print(id(a)==id(b))       # True

而创建元组、列表、字典、集合时,即使内部元素指向的内存地址完全一样,其数据结构本身也指向不同的引用(本质上是这些数据结构的实例化对象,因此不同)。

a = (1,2,3)
b = (1,2,3)
print(a[0] is b[0])   # True
print(a is b)         # False
2. 函数对象

函数作为一种对象,其在生存后期内只会被创建和初始化一次。

def func(*args, **kwargs):
    pass

if __name__ == '__main__':

    a = func()
    b = func()
    print(a is b)

函数中的参数,可视为函数对象的属性。其一个潜在风险为默认参数值为可变变量。

def func(ls=[]):
    ls.append(1)
    print(ls)
    
if __name__ == '__main__':
    print(id(func))
    func()     # [1]
    print(id(func))    # 仍是同一个函数对象
    func()     # [1, 1]

我们可以这样理解上述过程:
(1)func()只被创建和初始化一次,属性ls指向一个空列表的引用;
(2)第一次func()调用的时,属性ls添加了一个元素1,变为[1];
(3)第二次func调用的时,属性ls再添加了一个元素1,变为[1,1]。

观察下函数对象的__default__,我们可以明显的观察到属性ls默认值的变化情况:

if __name__ == '__main__':
    print(func.__defaults__)       # ([],)
    func()
    print(func.__defaults__)       # ([1],)
    func()
    print(func.__defaults__)       # ([1, 1],)

而对于不可变变量,则不会出现这个问题:

def func(ls=()):
    ls += (1, )   # 注意此处的ls为函数内部临时变量,而非函数对象的属性,所以属性ls并未发生变化
    print(ls)
3. 类及其实例化对象

类对象在生存周期为唯一对象(可视为单例,仅初始化一次),而类的每个实例化对象为不同的对象。

class Cat(object):
    def __init__(self, *args, **kwargs):
        pass

if __name__ == '__main__':
    Cat1 = Cat
    Cat2 = Cat
    print(Cat1 is Cat2)      # True

    cat1 = Cat1()
    cat2 = Cat2()
    print(cat1 is cat2)      # False
  • 类中的辅助也为地址引用
class Cat(object):
    def __init__(self):
        self.start = 0
        self.end = 0
        self.time = (self.start, self.end)   # 并不会同步更新

    def setStart(self, value):
        self.start = value

    def setEnd(self, value):
        self.end = value


if __name__ == '__main__':
    cat = Cat()
    cat.setStart(1)
    cat.setStart(10)
    print(cat.time)        # (0, 0)

  • 类对象只实例化一次
class Cat(object):
    def __init__(self, *args, **kwargs):
        x = []
	    print(x)
	    x.append(1)

if __name__ == '__main__':
    Cat1 = Cat   # 只初始化一次,因此只会打印一次 []
    Cat2 = Cat   
    print(Cat2.x)  #  只初始化一次,因此为 [1]

对于类属性,同样存在可变变量的问题。

class Cat(object):
    x = []
    def __init__(self):
        self.x.append(1)
        print(self.x)

if __name__ == '__main__':
    cat1 = Cat()    # [1]
    cat2 = Cat()    # [1,1]
    cat3 = Cat()    # [1,1,1]

综上,在python的函数定义和类对象定义中,尽量不要使用可变变量,以免发生潜在风险。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值