Python 类、对象、数据分类、函数参数传递的理解

  最近在基于python写的接口自动化脚本,从Excel表中读取所有数据,每一行数据保存为字典,再将很多行的字典数据保存到一个列表里,运行时发现,列表中的字典均相同,且一直是excel最后一行的数据,情况类比如下:
dd = {"a":1,"b":10}
i = 2
list1 = []
while i< 5:
    dd["a"] = i
    i+=1
    list1.append(dd)
print("list1:{}".format(list1))

  运行结果如下图,打印结果并不是预期的 list1:[{'b': 10, 'a': 2}, {'b': 10, 'a': 3}, {'b': 10, 'a': 4}] ,为什么呢??

  问题的关键在于,数据分为可变及不可变类型,python中字典是可变类型,列表实际保存的是字典所指向的那片内存,而这片内存的内容,保存的是最后一次修改的值。
  为加深理解,重新温故下python关于类、对象、数据分类、函数传递参数的相关知识。

1、基本概念

1.1 类与对象的关系

对象:包含属性(特征)和方法(行为)。例如,狗作为一个对象,有年龄、眼睛等特征,有走路、觅食等行为。
类:即把有相同属性和方法的对象进行提取(抽象化),是对象的模板。例如,对狗这个对象进行抽象化,把具有觅食行为,具有年龄等相同特征的对象,抽象一个Animal类。

1.2 self的作用

class Animal():
    self.age = 0    #类属性
    def Eat(self):      #类方法
        print ("觅食")
        
dog = Animal()   #类的实例化,即对象
cat = Animal()   #类的实例化,即对象
dog.Eat()   #相当于Animal.Eat(dog)
  在python里,当对象调用类中的方法时,需要先把对象作为参数传入方法中,相当于告诉类,“老子来调用这个方法啦,留个名”。而对象把自己传入方法,就是通过self。
  如上图,dog对象调用Eat(self)方法,执行dog.Eat()时,先用self接收dog对象,等同于执行Animal.Eat(dog)。
  正是因为self参数接收的是对象本身,而self的英文翻译就是“自己,自个”,所以大家都约定俗成的用了这个单词,它并不是python的关键字,如果换成把self缓存that,here等,其实也一样。

1.3 对象的创建与引用

  在python中,一切都是对象。对象均具备三个属性:地址,类型,值。
  当"左边 = 右边"时,实际是创建、引用对象的过程。
  如a = 3, 3实际上是一个对象,且对象的值为3,对象创建后存储在内存中,被a所引用。
 
 
  如果对象中的值可以更改,该值属于可变类型;
  如果对象中的值不能更改,该值属于不可变类型;
  如果对象中又包含对其他对象的引用,该对象就是容器,如d={},list1[0]=d,list1就是容器。

2、数据的分类

  数据可变不可变,指的是存储在内存的内容,即对象的值,是否可以被修改,分为俩大类:
  • 不可变类型:例如整型,浮点型,字符串类型;
  • 可变类型:例如字典,列表。

2.1 不可变类型

  不可变类型,即对象本身的值不可以改变。
  python在引用不可变类型的对象时,会寻找该对象是否创建过,若该对象已创建,则变量会直接引用该对象,不会再申请新的内存空间。
a = 3
b = 3
print(id(a))
print(id(b))
a = 4
print(id(a)) 

>> 502853488
>> 502853488
>> 502853520 
  3这个对象创建后,a、b都引用了它,所以打印出来的地址是相同的。
  当a = 4后,因为3属于不可变类型,因此又创建了一个4的对象,将a指向这个新创建的对象。

2.2 可变类型

  可变类型,即在对象本身的值允许改变,而内存地址不需要改变,如 列表.append。
  python在引用不可变类型的对象时,会先申请新的内存空间,来存储这个对象,有别于不可变类型。
 
a = [1,2,3]
b = [1,2,3]
print(id(a))
print(id(b))
a.append(4)
print(id(a)) 

>> 48751048
>> 48751560
>> 48751048
  a、b创建了俩个相同内容的列表,但是其指向的内存地址不相同。当对a指向的可变对象增加元素后,a所引用的对象内容已改变,但地址依旧不变。

3、函数传递参数的方式

3.1 值传递

  主函数向调用函数传递的参数是不可变类型时,实际上只是将实参的拷贝(即临时副本)传递给了被调用函数,并不是实参本身,这样被调函数不能直接修改主调函数中变量的值,而只能修改其私有的临时副本的值。
def ChangeString(s):
    s = '我是不可变类型,传递给函数'

s = '我是不可变类型'
print (s)
ChangeString(s)
print (s)

>> 我是不可变类型
>> 我是不可变类型
  如代码所示,s是字符串,属于不可变类型,传递给ChangeString(s)时,是将s实际的值传入,s本身不会被改变。

3.2 引用传递

  主函数向调用函数传递的参数是可变类型时,实际上是将实参的引用传入了调用函数,对引用的操作等于对其指定的对象进行操作。
def ChangeList(list1):
    list1[1] = 5
li = [1,1,1]
print (li)
ChangeList(li)
print (li)

>> [1, 1, 1]
>> [1, 5, 1]
  如代码所示,li是列表,属于可变类型,传递给ChangeList(list1)时,list1也指向了li所引用的同一片内存。

小结

  1、类是对象的抽象化,对象是类的实例化,在python中一切都是对象。
  2、self代表的是对象本身,将对象作为一个参数传入方法中执行。
  3、内存中的内容按是否可以修改,分为可变类型和不可变类型,所对应的可变对象和不可变对象,创建和引用方式也不同。
  4、不可变类型参数被函数调用时,是值传递,可变类型参数被函数调用时,是引用传递。
 
 
 
 
 

 

posted @ 2018-12-20 20:02  牧羊神  阅读(13803)  评论(0编辑  收藏  举报