python 入门 第3篇: 数据类型1 - 数字、布尔类型
在计算机中, CPU只能识别0、1这两个数, 甚至它都不知道数是什么, 它只知道要么是要么不是, 恰好用0、1来表示这两种状态. 计算机只能处理0、1这两个数, 所以让计算机识别某个事物只能用0、1这两个数来定义, 早期的程序员就是在使用01进行编码的, 使用01进行编码简直是一种折磨, 后来人们为了解决使用01编码的问题, 就定义了一套指令格式, 称之为指令集. CPU不认识指令名, 指令名是编译器用来给人看的, 为的是方便人来编程, CPU只认识01机器码, 最终还是会由编译器将指令转换成CPU认识的01机器码. 而这里所说的指令集通常指的是汇编语言
使用汇编语言在日常的软件开发中对我们普通程序员来说也挺"折磨"的, 通常汇编语言都是用于操作底层硬件时才会用到. 随着技术的发展, 有许多的大牛们在汇编语言之上又开发了新的编程语言, 像C/C++/Java/Python/Perl等等, 这些新的编程语言也被人们称为高级语言. 之所以高级, 就是因为在使用上更接近于人的思维, 便于我们在日常中快速的完成开发. 我们使用高级语言开发完成的软件最终会由编译器先转换成汇编语言, 然后再将汇编语言转换成最终CPU认识的01机器码, 最终才交由CPU去执行
我们现在使用的高级语言, 以python为例, 在源代码文件中编写的python代码最终并不会直接交给CPU去执行, 因为源代码文件中代码本质上就是一串一串的文本信息(字符串), 而CPU并不认识这些文本信息, 最终的执行都是由python解释器去解释执行的. 所以说我们现在编写的代码都是给python解释器看的, 那么为什么python解释器能看懂我们编写的代码(一串一串的文本信息)? 那是因为python解释器内部自己定义了一套规则, 这套规则中定义了说满足什么样的条件我就会把它当作是一个变量, 满足什么样的条件我就会把它当作是一个函数等等. 这套规则就叫做语法规则. 我们在学习使用高级语言做开发时, 其实最终学习的就是这套规则
我们开发完成的程序, 在未被运行前都是存放在磁盘上的. 当启动程序时, 操作系统会首先将程序从磁盘加载到内存, 然后再交由CPU去执行. 所以大家要有一个内存的概念, 内存本质上就是一块很大的格子(像Excel表格那样), 每个正在被运行的程序包括操作系统在内, 都是会先被加载到这个大格子中的. 有的格子被占用了, 有的格子则没有被占用, 被占用的格子不能再被二次使用, 直到格子被标记为未使用时才能被程序使用, 而负责标记格子是否被占用的工作正是由操作系统来完成的
1. 什么是变量, 如何定义一个某种数据类型的变量
相信很多人都听过"程序 = 数据结构 + 算法", 其中数据结构就是用来存储程序中所用到的数据, 而存储的地方正是在内存中, 而算法当中则包括了对程序中所存储的数据应该如何处理的方法. 这些程序中所用到的数据就是由变量来存储的, 并通过赋予一个变量名让我们在程序中可以访问到它. 当然也有常量, 常量与变量的区别就是常量是不可改变的, 而变量是可以被改变的
在python中定义一个变量非常简单, 只需要使用=号即可, =号的左边为变量名, =号的右边为具体的变量值(这种操作通常也被称为变量赋值), python解释器可以根据=号右边的值自动推导出变量的数据类型, 而无需像c语言那样在定义变量时需要指明数据类型
#定义一个int整形变量
number = 1
#定义一个str字符串变量
s = 'Hello World'
# 多重赋值
# x, y, z都被赋值为1
x = y = z = 1
# 多元赋值
# 将10赋给变量x, 20赋给变量y
x, y = 10, 20
# x, y内容交换
x, y = y, x
在定义变量时, 变量的名字需要遵循一定的规则, 不能随便命名:
- 不能使用python中的关键字作为变量名
- 变量名的第一个字符不能是数字, 必须是字母或下划线
- 变量名中的字符必须是字母、数字、或者下划线, 而不能使用空格、连字符、标点符号、引号或其他字符
实际上一个程序中会有各种各样类型的数据需要存储, 比如整数、小数、字符串等等, 那么针对不同的数据就需要使用不同类型的变量去存储, 下面我们了解一下在python中都有哪些类型的数据
2. python中数据(变量)的类型
在python中, 有以下几种数据类型:
- 数字
- 布尔
- 字符串
- 列表
- 元组
- 字典
- 集合
当上面几种类型的数据被存储到内存(也就是存储到那些未被使用的格子中)时, 我们是可以知道它被存储到哪个格子中的, 每个格子都有自己的唯一标识, 这个唯一标识就是内存地址, 我们可以使用python中的内置方法id来查看存储数据的这个变量内存地址是多少
"Everything is an Object"是用来描述python的, 在python中一切皆对象, 查看一个对象中都包含了哪些方法可以使用python的内置方法dir来实现
当对某个方法不熟悉, 不知道干嘛用的时间, 可以使用python的内置方法help来查看
在python中还有另外一个内置方法type, 使用它可以知道当前变量的数据类型, 也就是说这个变量当前是存储了什么类型的数据? 整数? 小数还是字符串?
3. 数字类型
数字类型包括:
- 整形 (int)
- 就是我们现实生活中的整数, 可正可负
- 长整型 (long)
- 与整形一样, 只不过范围比int更大, 在python3中已经不存在长整型了, 都统一为整形
- 浮点型 (float)
- 就是我们现实生活中的小数, 可正可负
- 复数类型 (complex)
- 在一般的开发中用不到这个类型, 在数学领域会用到
3.1 整形 (int)
上面介绍了id、type、dir、help这四个python内置方法, 我们来试一下到底灵不灵
# -*- coding: utf-8 -*-
#首先我们定义一个python的整形变量
x = 1000
#输出变量x的唯一标识(内存地址)
print id(x)
#输出: 30905256, 不同的计算机或每次输出可能地址都会不一样
#输出变量x的数据类型
print type(x)
#输出: <type 'int'
#输出x对象中的所有方法
print dir(x)
#输出: ['__abs__', '__add__', '__and__', '__class__', '__cmp__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__float__', '__floordiv__', '__format__', '__getattribute__', '__getnewargs__', '__hash__', '__hex__', '__index__', '__init__', '__int__', '__invert__', '__long__', '__lshift__', '__mod__', '__mul__', '__neg__', '__new__', '__nonzero__', '__oct__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'imag', 'numerator', 'real']
#输出x对象中bit_length方法的作用描述
print help(x.bit_length)
'''
输出:
Help on built-in function bit_length:
bit_length(...)
int.bit_length() -int
Number of bits necessary to represent self in binary.
bin(37)
'0b100101'
(37).bit_length()
6
None
'''
通常我们在使用整形时, 不太会用到它里面的对象方法, 所以我们这里主要介绍一下, 当我们在定义一个变量时, python解释器在内部都做了什么?
定义一个变量时python解释器在内部都做了什么1所以我们会发现, 不同的变量最终输出的内存地址都不同
x = 1000
print id(x)
#输出: 39481576
y = 1001
print id(y)
#输出: 39471472
z = 1002
print id(z)
#输出: 39481408
但有一种情况, 就是在特定的数值范围内(默认情况下范围是: -5 ~ 257), 输出的内存地址是相同的, 这是因为在python解释器内部的有小整数缓存池存在的原因
x = 20
print id(x)
#输出: 31887840
y = 20
print id(y)
#输出: 31887840
z = 20
print id(z)
#输出: 31887840
当然上面说到的小整数缓存池的范围是python默认情况下的, 可以通过修改python源代码来调整这个小整数缓存池的范围. 为什么要有小整数缓存池的存在呢? 这其实很好理解的, 当python解释器在执行到我们定义变量的代码时, 它就会为其申请一块内存, 频繁的操作内存, 是会影响程序执行效率的. 而像0 ~ 100这个范围的整数, 我们其实会经常用到, 比如在循环中. 所以python为了提高执行效率, 就有了小整数缓存池的存在
然而你会发现, 在这种情况下, 输出的变量内存地址也是一样的
x = 1000
print id(x)
#输出: 39874792
y = 1000
print id(y)
#输出: 39874792
z = 1000
print id(z)
#输出: 39874792
出现这种情况,也是python为了提高执行效率而产生的, 当我们在为一个变量赋值(定义一个变量)时, python解释器首先检查在之前已分配过的内存块中是否已经有这个值了, 如果有就不需要再分配额外的内存块了, 直接拿过来用就可以了, 如果没有, 才为其分配内存块, 也就是下面图中所描述的
定义多个相同值变量时python解释器在内部都做了什么1python解释器在内部实现"如果之前已分配过的内存块中已经有这个值了就直接拿来用"的方案时, 用到了引用计数技术, 这个技术其实很简单, 默认情况下, 我们引用计数值为1, 当又有一个变量引用我的时间, 引用计数值就+1变为2, 当有一个变量解除了对我的引用时, 引用计数就-1此时就又变回了1, 而当我的引用计数变为0时, 就说明这个之前被我占用的内存块被python解释器回收掉了(置为了未被使用的状态), 这也是python解释器中最基本的垃圾(内存)回收方案
3.2 长整形 (long)
长整型与整形一样, 只不过范围比int更大, 在python3中已经不存在长整型了, 都统一为整形
在python中定义长整型有两种方法:
- 当数值的范围超过了int类型所能表示的最大范围时, python解释器自动将其转换为长整型
- 在定义变量赋值时默认加L
# -*- coding: utf-8 -*-
#1. 当数值的范围超过了int类型所能表示的最大范围时, python解释器自动将其转换为长整型
x = 99999999 * 99999999
print type(x)
#输出: <type 'long'
#2. 在定义变量赋值时默认加L
y = 1000L
print type(y)
#输出: <type 'long'
同样的我们也可以通过dir方法来查看长整对象中的所有方法
3.3 浮点型 (float)
浮点型变量的定义也非常简单, 直接将小数赋值给变量即可
# -*- coding: utf-8 -*-
x = 1.2
print type(x)
#输出: <type 'float'
print id(x)
#输出: 40269112
print dir(x)
#输出: ['__abs__', '__add__', '__class__', '__coerce__', '__delattr__', '__div__', '__divmod__', '__doc__', '__eq__', '__float__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getformat__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__int__', '__le__', '__long__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__nonzero__', '__pos__', '__pow__', '__radd__', '__rdiv__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rmod__', '__rmul__', '__rpow__', '__rsub__', '__rtruediv__', '__setattr__', '__setformat__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', 'as_integer_ratio', 'conjugate', 'fromhex', 'hex', 'imag', 'is_integer', 'real']
3.4 复数类型 (complex)
这个在我们平常开发中也基本不会用到, 了解一下就好
# -*- coding: utf-8 -*-
x = 123-12j
print type(x)
#输出: <type 'complex'
print id(x)
#输出: 44928176
3.5 不同类型之间相互转换
在python中允许不同类型之间的相互转换(有些类型不允许相互转换, 如后面要介绍的列表等), 但可能会存在一些问题, 比如float转int会丢失精度等. 如何转换? 通过相应数据类型的构造方法即可: int、long、float等等
# -*- coding: utf-8 -*-
x = 1000
print type(x)
#输出: <type 'int'
y = 9999999999 * 9999999999
print type(y)
#输出: <type 'long'
z = 1.2
print type(z)
#输出: <type 'float'
#int转long
y1 = long(x)
print type(y1)
#输出: <type 'long'
#float转int
x1 = int(z)
print type(x1)
#输出: <type 'int'
print x1
#输出: 1, 可以看到已经丢失了精度
#long转int, 这个比较有趣
x2 = int(y)
print type(x2)
#输出: <type 'long'
#按照上面float转int我们一般会推测说最终x2的类型是int, 但是最终输出的结果还是long
#这就是上面介绍过的当数值超过了int类型所能表达的最大范围时会被python自动转换为long类型, 这也就解释了为什么最终的类型还是long而不是int
4. 布尔类型
该类型只有两种值: 真 和 假, 通常和条件语句一起使用
在python中, 不同的数据类型表达真或假的方式不一样, 这里我们先了解一下
- 数字: 非0即真
- 布尔: True为真, False为假
- 字符串、列表、元组、字典、集合: 非空即真
- python中还有一个关键字None, 在逻辑判断时表示为假
布尔类型的变量定义也非常简单:
x = True
print type(x)
#输出: <type 'bool'