❶ 利用python进行数据分析笔记:3.1数据结构
元组是一种固定长度、不可变的Python对象序列。创建元组最简单的办法是用逗号分隔序列值:
tuple 函数将任意序列或迭代器转换为元组:
中括号 [] 可以获取元组的元素, Python中序列索引从0开始 :
元组一旦创建,各个位置上的对象是无法被修改的,如果元组的一个对象是可变的,例如列表,你可以在它内部进行修改:
可以使用 + 号连接元组来生成更长的元组:
元组乘以整数,则会和列表一样,生成含有多份拷贝的元组:
将元组型的表达式赋值给变量,Python会对等号右边的值进行拆包:
拆包的一个常用场景就是遍历元组或列表组成的序列:
*rest 用于在函数调用时获取任意长度的位置参数列表:
count 用于计量某个数值在元组中出现的次数:
列表的长度可变,内容可以修改。可以使用 [] 或者 list 类型函数来定义列表:
append 方法将元素添加到列表尾部:
insert 方法可以将元素插入到指定列表位置:
( 插入位置范围在0到列表长度之间 )
pop 是 insert 的反操作,将特定位置的元素移除并返回:
remove 方法会定位第一个符合要求的值并移除它:
in 关键字可以检查一个值是否在列表中;
not in 表示不在:
+ 号可以连接两个列表:
extend 方法可以向该列表添加多个元素:
使用 extend 将元素添加到已经存在的列表是更好的方式,比 + 快。
sort 方法可以对列表进行排序:
key 可以传递一个用于生成排序值的函数,例如通过字符串的长度进行排序:
bisect.bisect 找到元素应当被插入的位置,返回位置信息
bisect.insort 将元素插入到已排序列表的相应位置保持序列排序
bisect 模块的函数并不会检查列表是否已经排序,因此对未排序列表使用bisect不会报错,但是可能导致不正确结果
切片符号可以对大多数序列类型选取子集,基本形式是 [start:stop]
起始位置start索引包含,结束位置stop索引不包含
切片还可以将序列赋值给变量:
start和stop可以省略,默认传入起始位置或结束位置,负索引可以从序列尾部进行索引:
步进值 step 可以在第二个冒号后面使用, 意思是每隔多少个数取一个值:
对列表或元组进行翻转时,一种很聪明的用法时向步进值传值-1:
dict(字典)可能是Python内建数据结构中最重要的,它更为常用的名字是 哈希表 或者 关联数组 。
字典是键值对集合,其中键和值都是Python对象。
{} 是创建字典的一种方式,字典中用逗号将键值对分隔:
你可以访问、插入或设置字典中的元素,:
in 检查字典是否含有一个键:
del 或 pop 方法删除值, pop 方法会在删除的同时返回被删的值,并删除键:
update 方法将两个字典合并:
update方法改变了字典元素位置,对于字典中已经存在的键,如果传给update方法的数据也含有相同的键,则它的值将会被覆盖。
字典的值可以是任何Python对象,但键必须是不可变的对象,比如标量类型(整数、浮点数、字符串)或元组(且元组内对象也必须是不可变对象)。
通过 hash 函数可以检查一个对象是否可以哈希化(即是否可以用作字典的键):
集合是一种无序且元素唯一的容器。
set 函数或者是用字面值集与大括号,创建集合:
union 方法或 | 二元操作符获得两个集合的联合即两个集合中不同元素的并集:
intersection 方法或 & 操作符获得交集即两个集合中同时包含的元素:
常用的集合方法列表:
和字典类似,集合的元素必须是不可变的。如果想要包含列表型的元素,必须先转换为元组:
❷ Python对象
众所周知,Python是一门面向对象的语言,在Python无论是数值、字符串、函数亦或是类型、类,都是对象。
对象是在 堆 上分配的结构,我们定义的所有变量、函数等,都存储于堆内存,而变量名、函数名则是一个存储于 栈 中、指向堆中具体结构的引用。
要想深入学习Python,首先需要知道Python对象的定义。
我们通常说的Python都是指CPython,底层由C语言实现,源码地址: cpython [GitHub]
Python对象的定义位于 Include/object.h ,是一个名为 PyObject 的结构体:
Python中的所有对象都继承自PyObejct,PyObject包含一个用于垃圾回收的双向链表,一个引用计数变量 ob_refcnt 和 一个类型对象指针 ob_type
从PyObejct的注释中,我们可以看到这样一句:每个指向 可变大小Python对象 的指针也可以转换为 PyVarObject* (可变大小的Python对象会在下文中解释)。 PyVarObejct 就是在PyObject的基础上多了一个 ob_size 字段,用于存储元素个数:
在PyObject结构中,还有一个类型对象指针 ob_type ,用于表示Python对象是什么类型,定义Python对象类型的是一个 PyTypeObject 接口体
实际定义是位于 Include/cpython/object.h 的 _typeobject :
在这个类型对象中,不仅包含了对象的类型,还包含了如分配内存大小、对象标准操作等信息,主要分为:
以Python中的 int类型 为例,int类型对象的定义如下:
从PyObject的定义中我们知道,每个对象的 ob_type 都要指向一个具体的类型对象,比如一个数值型对象 100 ,它的ob_type会指向 int类型对象PyLong_Type 。
PyTypeObject结构体第一行是一个PyObject_VAR_HEAD宏,查看宏定义可知PyTypeObject是一个变长对象
也就是说,归根结底 类型对象也是一个对象 ,也有ob_type属性,那 PyLong_Type 的 ob_type 是什么呢?
回到PyLong_Type的定义,第一行 PyVarObject_HEAD_INIT(&PyType_Type, 0) ,查看对应的宏定义
由以上关系可以知道, PyVarObject_HEAD_INIT(&PyType_Type, 0) = { { _PyObject_EXTRA_INIT 1, &PyType_Type } 0} ,将其代入 PyObject_VAR_HEAD ,得到一个变长对象:
这样看就很明确了,PyLong_Type的类型就是PyType_Typ,同理可知, Python类型对象的类型就是PyType_Type ,而 PyType_Type对象的类型是它本身
从上述内容中,我们知道了对象和对象类型的定义,那么根据定义,对象可以有以下两种分类
Python对象定义有 PyObject 和 PyVarObject ,因此,根据对象大小是否可变的区别,Python对象可以划分为 可变对象(变长对象) 和 不可变对象(定长对象)
原本的对象a大小并没有改变,只是s引用的对象改变了。这里的对象a、对象b就是定长对象
可以看到,变量l仍然指向对象a,只是对象a的内容发生了改变,数据量变大了。这里的对象a就是变长对象
由于存在以上特性,所以使用这两种对象还会带来一种区别:
声明 s2 = s ,修改s的值: s = 'new string' ,s2的值不会一起改变,因为只是s指向了一个新的对象,s2指向的旧对象的值并没有发生改变
声明 l2 = l ,修改l的值: l.append(6) ,此时l2的值会一起改变,因为l和l2指向的是同一个对象,而该对象的内容被l修改了
此外,对于 字符串 对象,Python还有一套内存复用机制,如果两个字符串变量值相同,那它们将共用同一个对象:
对于 数值型 对象,Python会默认创建0~2 8 以内的整数对象,也就是 0 ~ 256 之间的数值对象是共用的:
按照Python数据类型,对象可分为以下几类:
Python创建对象有两种方式,泛型API和和类型相关的API
这类API通常以 PyObject_xxx 的形式命名,可以应用在任意Python对象上,如:
使用 PyObjecg_New 创建一个数值型对象:
这类API通常只能作用于一种类型的对象上,如:
使用 PyLong_FromLong 创建一个数值型对象:
在我们使用Python声明变量的时候,并不需要为变量指派类型,在给变量赋值的时候,可以赋值任意类型数据,如:
从Python对象的定义我们已经可以知晓造成这个特点的原因了,Python创建对象时,会分配内存进行初始化,然后Python内部通过 PyObject* 变量来维护这个对象,所以在Python内部各函数直接传递的都是一种泛型指针 PyObject* ,这个指针所指向的对象类型是不固定的,只能通过所指对象的 ob_type 属性动态进行判断,而Python正是通过 ob_type 实现了多态机制
Python在管理维护对象时,通过引用计数来判断内存中的对象是否需要被销毁,Python中所有事物都是对象,所有对象都有引用计数 ob_refcnt 。
当一个对象的引用计数减少到0之后,Python将会释放该对象所占用的内存和系统资源。
但这并不意味着最终一定会释放内存空间,因为频繁申请释放内存会大大降低Python的执行效率,因此Python中采用了内存对象池的技术,是的对象释放的空间会还给内存池,而不是直接释放,后续需要申请空间时,优先从内存对象池中获取。
❸ Python的类和对象入门
本文来说说Python中的类与对象,Python这门语言是无处不对象,如果你曾浅要了解过Python,你应该听过Python是一种面向对象编程的语言,所以你经常可能会看到面向“对象”编程这类段子,而面向对象编程的语言都会有三大特征:封装、继承、多态。
我们平时接触到的很多函数、方法的操作都具有这些性质,我们只是会用,但还没有去深入了解它的本质,下面就介绍一下关于类和对象的相关知识。
封装这个概念应该并不陌生,比如我们把一些数据封装成一个列表,这就属于数据封装,我们也可以将一些代码语句封装成一个函数方便调用,这就是代码的封装,我们也可以将数据和代码封装在一起。用术语表示的话,就是可以将属性和方法进行封装,从而得到对象。
首先我们可以定义一个类,这个类中有属性和方法,但有的伙伴会比较好奇,属性和方法不是会封装成对象嘛,为什么又变成类了?举个例子,类就好比是一个毛坯房,而对象是在毛坯房的基础上改造成的精装房。
在类定义完成时就创建了一个类对象,它是对类定义创建的命名空间进行了一个包装。类对象支持两种操作:属性引用和实例化。
属性引用的语法就是一般的标准语法:obj.name。比如XiaoMing.height和XiaoMing.run就是属性引用,前者会返回一条数据,而后者会返回一个方法对象。
这里也支持对类属性进行赋值操作,比如为类中的weight属性赋予一个新值。
而类的实例化可以将类对象看作成一个无参函数的赋值给一个局部变量,如下:
ming就是由类对象实例化后创建的一个实例对象,通过实例对象也可以调用类中的属性和方法。
类在实例化过程中并不都是像上面例子一样简单的,一般类都会倾向将实例对象创建为有初始状态的,所以在类中可能会定义一个__init__的魔法方法,这个方法就可以帮助接收、传入参数。
而一个类如果定义了__init__方法,那么在类对象实例化的过程中就会自动为新创建的实例化对象调用__init__方法,请看下面这个例子。
可以看到在__init__()中传入了参数x和y,然后在print_coor中需要接收参数x和y,接下来通过实例化这个类对象,验证一下参数是否能通过__init__()传递到类的实例化操作中。
所谓继承就是一个新类在另一个类的基础上构建而成,这个新类被称作子类或者派生类,而另一个类被称作父类、基类或者超类,而子类会继承父类中已有的一些属性和方法。
比如上面这个例子,我并没有将list_定义成一个列表,但它却能调用append方法。原因是类Mylist继承于list这个基类,而list_又是Mylist的一个实例化对象,所以list_也会拥有父类list拥有的方法。当然可以通过自定义类的形式实现两个类之间的继承关系,我们定义Parent和Child两个类,Child中没有任何属性和方法,只是继承于父类Parent。
当子类中定义了与父类中同名的方法或者属性,则会自动覆盖父类对应的方法或属性,还是用上面这个例子实现一下,方便理解。
可以看到子类Child中多了一个和父类Parent同名的方法,再实例化子类并调用这个方法时,最后调用的是子类中的方法。Python中继承也允许多重继承,也就是说一个子类可以继承多个父类中的属性和方法,但是这类操作会导致代码混乱,所以大多数情况下不推荐使用,这里就不过多介绍了。
多态比较简单,比如定义两个类,这两个类没有任何关系,只是两个类中有同名的方法,而当两个类的实例对象分别调用这个方法时,不同类的实例对象调用的方法也是不同的。
上面这两个类中都有introce方法,我们可以实例化一下两个类,利用实例对象调用这个方法实现一下多态。
判断一个类是否是另一个类的子类,如果是则返回True,反之则返回False。
需要注意的有两点:
判断一个对象是否为一个类的实例对象,如果是则返回True,反之则返回False。
需要注意的有两点:
判断一个实例对象中是否包含一个属性,如果是则返回True,反之则返回False。
需要注意的是第二个参数name必须为字符串形式传入,如果不是则会返回False。