python位运算符怎么用?用Python位运算符对数据进行最精尖的操作
python位运算符怎么用?用Python位运算符对数据进行最精尖的操作在Python中,位运算符有6个:位与、位或、位非、位异或、左移、右移。其概览如下:本教程是讲Python的,下文开始讲述Python中的位运算符,不要被名字吓倒,你初觉可能很难,但其实很简单,比我们小学学过的加减乘除还要简单!因为它的运算规则和处理的数据都更少。困难的是,你很难找到一篇优秀有趣的教程,来恰如其分地教导你——现在你运气不错,你眼前刷到读的这篇文章正是鄙下精心打磨的灵光喷涌的产物。在计算机的内部,CPU运行的指令、内存地址中储存的数据单元,以及保存在磁盘中的数据文件,这些数据都是使用二进制来表示的。二进制是计算机数据表示的最小单元——你不能再将它分割成更小的单位了。一个二进制数据能表示的信息量称为1比特,比特(bit)也称二进制位,简称位,位运算符便是用来操作这些二进制数据的,所以正如本文标题中所称的那样:对数据的控制,位运算符最为精微!
“世界上只有10种人,一种是懂二进制的,另一种是不懂二进制的。”
——流行于某虚空国度的佚名箴言
众所周知,我们现在使用的计算机都是二进制的,历史上苏联曾经研制过三进制计算机,但被历史淘汰了,实践证明二进制是目前最合理的架构。在可预见的将来,也并不会有四进制等更多进制的计算机了。
当然,量子、生物、光子等未来社会可能流行的计算机,与现在主流形态的电子计算机实现原理有所不同,如果可以实现更多状态的稳定电压,也许会采用其他进制。不过未来尚未到来,计算机技术日新月异,谁也无法预料将来具体如何。所以还是着眼于已经成为现实的眼前吧。
位运算符简介在计算机的内部,CPU运行的指令、内存地址中储存的数据单元,以及保存在磁盘中的数据文件,这些数据都是使用二进制来表示的。
二进制是计算机数据表示的最小单元——你不能再将它分割成更小的单位了。
一个二进制数据能表示的信息量称为1比特,比特(bit)也称二进制位,简称位,位运算符便是用来操作这些二进制数据的,所以正如本文标题中所称的那样:对数据的控制,位运算符最为精微!
本教程是讲Python的,下文开始讲述Python中的位运算符,不要被名字吓倒,你初觉可能很难,但其实很简单,比我们小学学过的加减乘除还要简单!因为它的运算规则和处理的数据都更少。困难的是,你很难找到一篇优秀有趣的教程,来恰如其分地教导你——现在你运气不错,你眼前刷到读的这篇文章正是鄙下精心打磨的灵光喷涌的产物。
在Python中,位运算符有6个:位与、位或、位非、位异或、左移、右移。其概览如下:
位运算符符号 |
功能说明 |
使用格式 |
& |
位与 |
a & b |
| |
位或 |
a | b |
^ |
位异或 |
a ^ b |
~ |
位非 |
~ a |
<< |
左移 |
a << n |
>> |
右移 |
a >> n |
其中,除了位非,其余位运算符都有两个操作数,a和b都是参与运算的等价操作数,两者位置互换不影响运算结果,n表示左移或右移的位数,不能与另一个操作数互换位置。
位与也称按位与,位或也称按位或,我觉得这个按字有点多余,所以根据奥卡姆剃刀原理,采用位与的称法,但是位字不能再省略了,否则与逻辑运算符中的逻辑与等混淆,而逻辑与是更常用的逻辑运算,简称与是有必要的。相对次要的位与就只好采用全称了。
位与Python中的位与运算符用&(and符号)表示。其语法如下:
操作数1 & 操作数2
其功能为对两个操作数的每个比特位进行与操作,每个比特位中,两者都为1才为1,其余情况为0,四种可能的值的运算规则见下表所示:
比特位值1 |
比特位值2 |
位与运算结果 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
0 |
0 |
1 |
1 |
1 |
很显然,交换两个操作数的位置并不会改变运算结果。
下面是数值0和1的四种可能的组合的位与运行结果:
当然,通常参与运算的数据具有多个比特位,下面是两个比特位的所有可能的组合,为了运算结果的一目了然,数据采用二进制表示(在Python中数值前缀0b或0B可表示二进制数,而bin函数可将结果转换成二进制形式):
其中,0b10等于十进制数值2,0b11等于3,它们之间的位与运算与上例相同,你也可以实际运行以下代码验证一下:
对于所有的整数数值,其位与运算规则都是如此,如果感到难以理解,这是很正常的,不过这仅仅只是因为数据不是以二进制形式显示的,对于更大的数值的位与运算,将其转换成二进制形式就非常浅显易懂了,比如7&8为0,是因为7的二进制形式是3个1,而8为1后跟4个0,每一位都有一个0,所以位与运算的结果也是每一位都为0,结果当然为0:
下面是更大的数值1314(一生一世)分别与520({男}我爱你[洞])和521({女}我爱你[棍])的参与位与运算的结果示例,两者都返回0,当然这并不能证明“世界上根本没有爱情,只有生殖冲动”:
最后需要注意的是,位与运算从低位往高位开始逐位运算,如果其中一个数值位数较少,不存在的高位视为0,这一点,不仅位与如此,下文提到的位或、位异或也是如此。
位或Python中的位或运算符用|(竖线,Enter键的上方)表示。其语法如下:
操作数1 | 操作数2
其功能为对两个操作数的每个比特位进行或操作,每个比特位中,两者只要有一个为1,结果即为1,两者均为0才为0,四种可能的值的运算规则见下表所示:
比特位值1 |
比特位值2 |
位或运算结果 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
和位与一样,交换两个操作数的位置不会改变运算结果。
下面是数值0和1的四种可能的组合的位或运行结果:
2或3:
一生一世的爱情:
Python中的位异或运算符用 ^(键盘数值6键上面)表示。其语法如下:
操作数1 ^ 操作数2
其功能为对两个操作数的每个比特位进行异或操作,具体为:每个比特位中,两者相同则为0,两者不同则为1,四种可能的值的运算规则见下表所示:
比特位值1 |
比特位值2 |
位异或运算结果 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
和位与、位或一样,交换两个操作数的位置结果相同。
注意是相同为0,不同为1,这很容易正好弄反,下面是数值0和1的四种可能的组合的位与运行结果:
根据位异或的运算规则,我们可以得出这样的结论:
1、任何一个整数与其自身位异或,结果为0;
2、任何一个整数与0位异或,结果为其本身。
这个结论有什么用处呢?考虑有这样一个整数值列表,只有一个数值出现了一次,其余值出现了两次,可以将所有成员进行位异或运算,其结果就是这个只出现了一次的数值:
尤其是在数据量很大的情况下,它可以显著地减少运算量。它的运算原理就是出现两次的整数异或运算结果为0,而0再与其他值运算不改变其他值的结果,相当于出现两次的数值被抵消了,只有只出现一次的整数值没有被抵消。
位非Python中的位非运算符用 ~ (波浪号,数字键1的左边)表示。位非也称(按)位取反,为了与逻辑运算符的与或非统一,我采用位非的叫法,其语法如下:
~ 操作数
其功能为对操作数的每个比特位进行取反操作,每个比特位中,1变为1,1变为1,两种可能的值的运算规则见下表所示:
比特位值 |
位非运算结果 |
0 |
1 |
1 |
0 |
下面是数值0和1的按位取反运行结果:
这和我们一般认为的不同,要理解这点,必须了解计算机是如何表示负数的。负数一般采用补码表示,它的规则是这样的:首先将负数的绝对值(即去掉负号的正数)按位取反,然后再加1。比如使用1个字节(8比特位)表示的正负数,正1表示为前面有7个0的1:00000001,那么负1就是1按位取反,结果是0前面有7个1:11111110,最后再加1,最终是8个1:11111111。
用多少字节表示正负数,对于这一点,不同编程语言中的不同数据类型,具体也是不同的,在以前,内存是昂贵的资源,所以许多语言都提供了占用1、2、4、8等内存字节的整数数据类型。Python3中整数的占用字节是很大的,一般较小的数值占用28字节:
这意味着-1是用224(28乘以8)个1来表示的,这显然有点太长了,不太方便用作展示。但是我们可以得知-1的最后一位是1,而-2的最后一位是0。
Python中没有什么现成的显示补码的函数,bin函数或字符串的格式化方法都显示为负号加一个正数的二进制表示:
所以我写了一个根据位与原理显示整数第1个字节的补码的函数,可以看到负数确实是正数按位取反加1:
Python中的左移运算符用 << 表示。其语法如下:
操作数 << N
其功能为对操作数的所有比特位向左移动N位,其中N为正整数。相当于乘以2的N次方。
注意,不要将操作数和要移动的位数弄反了,和上面的位运算符不同,交换两个操作数的位置表示的含义不同。
下面是1左移的运算,它相当于1乘以2的某次方:
Python中的右移运算符用 >> 表示。其语法如下:
操作数 >> N
其功能为对操作数的所有比特位向右移动N位,其中N为正整数。相当于除以2的N次方,较低位的数据会被丢弃,所以,对于小于2的N次方的数值,其右移N位,结果都为0。
下面是右移的一些运算示例:
注意,左移和右移要移动的位数必须是正整数,如果是负数,会产生语法错误:
也不能是小数:
6个Python位运算符优先级的排列顺序如下:
优先级 |
运算符 |
说明 |
5 |
~ |
(按)位取反、位非 |
4 |
<< >> |
左移 右移。合称位移 |
3 |
& |
(按)位与 |
2 |
^ |
(按)位异或 |
1 |
| |
(按)位或 |
下面的示例可以证明(异或)^优先级高于|(位或):
另一个需要注意的是,位运算符仅支持整数(包括正负整数)数值的操作,但不支持浮点数:
更不支持复数:
不能操作字符串,也无法使用bin将字符串转换成二进制形式,即使只是一个ASCII字符:
再次重申一遍,仅支持正整数与负整数的的位运算。
位运算符的用途为了讲解位运算符的运算原理,本文主要使用数值进行位运算的操作。
但在实际应用中,位运算符的目的并不是为了对数值进行位运算。而是实现一些更高级的功能,下面是几个可能的应用场景:
1、判断数值的奇偶性:只需要将数值与1进行位与操作即可判断其第1位是否是1,如果是1肯定是奇数,如果为0肯定是偶数,另外在Python中,1也可以表示逻辑值True,所以也可以将运算结果直接做为判断是否为奇数的条件表达式。
下面是使用这种方式输出1到20中的奇数的示例:
这比使用%取余运算符判断除以2的余数是否为0来进行判断的做法,稍微不太容易理解。我原本以为其运算速度远胜于求余运算,尤其是大数值的运算,但是从运行来看,情况并非如此,反而求余运算更快,可能Python对求余运算的原理是有优化的。下面是测试程序:
下面是运行三次的运行结果:
2、用左移代替乘以2的指数倍,右移除以2的指数倍。下面是个示例:
从运行结果来看,左移确实比乘法运算快一些:
其他用法还有交换两个变量的值、计算平均数,以及上文提到的判断数值列表中出现一次的数值。
如何显示数值的二进制形式本节是高级内容,仅做展示,不做解释。可以直接应用于实际。
1、bin函数:
2、字符串的format方法:
3、递归取余:
4、bin函数扩展:
较短的代码希望大家自己亲自敲出来运行,下面是文章中出现的较长的代码,如果大家想运行可以直接复制。
1、位与运算符与求余运算符判断奇偶数速度比拼:
import time
print('位与运算符与求余运算符判断奇偶数速度比拼')
r = range(10**7 10**8)
t = time.perf_counter()
for i in r:
if i & 1:
pass
t1 = time.perf_counter() - t
print(f'位与耗时 {t1} 秒')
t = time.perf_counter()
for i in r:
if i % 2 != 0:
pass
t2 = time.perf_counter() - t
print(f'求余耗时 {t2} 秒')
print(f'位与是求余运算速度的 {t2/t1} 倍')
2、左移运算符与乘法运算符运行速度比拼:
import time
print('左移运算符与乘法运算符运行速度比拼')
n = 123456789**10
r = range(10**7)
t = time.perf_counter()
for i in r:
n << 10
t1 = time.perf_counter() - t
print(f'左移耗时 {t1} 秒')
t = time.perf_counter()
for i in r:
n * 1024
t2 = time.perf_counter() - t
print(f'乘法耗时 {t2} 秒')
print(f'左移是乘法运算速度的 {t2/t1} 倍')
结语
本文本来想昨天发表的,结果很多代码难以理解,想了很久,直到现在才完成。
关注我的粉丝可能感觉本文与以前的文章风格有些不同。你的感觉是对的,从本文开始,我将尽可能将文章写的有趣些。尽量做到色香味俱全。我目前的写作能力做到这些可能会很吃力,但我会竭尽全力,如果大家有什么好的风格指导,也可评论指出。感谢关注与阅读。
最佳文章选集截至2022年05月21日 14:10:58
收藏最多(42):两大费力工作:和女人讲道理,和电脑谈感情——Python逻辑运算符(原标题 Python教程:第19篇 逻辑运算符)
点赞最多(15):Python教程:第3篇 安装Python开发环境