python语言%运算符的含义_Python的运算符

初学者会对这样的所谓『语法』感到困惑:

"Hello%s" % 'World'

而实际上,『字符串能够用%拼接』这个功能并不是Python本身的语法规定。本文将借此对Python的运算符加以讨论。

==================================================================================================================================

我们知道在C语言中,计算两个数的加法使用运算符『+』,C语言的编译器会将其转换为机器指令,由CPU的运算电路直接完成加法。而在C++等面向对象的高级语言中,又出现了『运算符重载』,使得字面意义上的『+』可以支持自定义的任何操作。最典型的应用就是字符串的拼接:使用 string1 + string2 的写法要比 concat_string(string1,string2) 的写法简单直观。

而Python作为一种面向对象的语言,它的所有『变量』都是一个对象的引用,即便是简单的数字都代表了一个对象。比如说我想知道数字100的二进制的表示有多少位:

print(100 .bit_length()) #结果为7,注意.前面需要有一个空格

自然,Python的运算符便不那么简单了。概括的说,Python的运算符没有『实质功能』,它们仅仅是调用被操作变量的某些成员函数罢了。换句话说,变量能不能『运算』,和Python是否支持相应的语法无关,而是要问这变量自己是否支持对应的运算。

1. 一元操作符

先用一元操作符『~』举例。这个操作符在文档中的含义是『返回对被操作数字二进制逐位取反的值』,例如:

print(~7) #-8

但实际上,『按位取反』这个操作,并不是『~』的功能。这个功能之所以实现,是因为7这个数字『可以被按位取反』:

print(7 .__invert__()) #-8

7是一个int类型的实例(的引用),故『__invert__』实际上是int类型的功能。

print(int.__invert__(7)) #-8

在Python的源代码(Objects/intobject.c)中可以找到这样几行:

static PyObject *

int_invert(PyIntObject *v)

{

return PyInt_FromLong(~v->ob_ival);

}

(注:本文中使用Python源代码皆为官方实现CPython)

在这里,invert函数调用C运算符~,完成最终的按位取反操作。

在Python代码被编译、执行的时候,『~7』这样的代码会最终转换为『int.__invert__(7)』这样的操作。具体说来,『~7』会编译成字节码指令『UNARY_INVERT』,而在Python虚拟机执行字节码时,会对UNARY_INVERT调用Python API函数PyNumber_Invert。而PyNumber_Invert会寻找被操作变量的__invert__成员函数,如果找到了就加以执行,否则抛异常。

Python还有一个内建的模块operator,它有一个函数invert。这个内建模块是C实现的(源代码:Modules/operator.c),它的invert函数也是调用了PyNumber_Invert这个API。

这样说来,如果想让一个变量支持『~』操作,只需要实现__invert__函数即可。比如我们随便写一个类:

class X(object):

def __invert__(self):

return 42

x = X()

print(~x) #42

__invert__是一个成员函数,它的第一个参数self即指向对象本身:

class X(object):

def __invert__(self):

return self.name

x = X()

x.name = 'fake result'

print(~x) # fake result

2. 二元运算符

和一元运算符一样,每个二元运算符实际上也会执行『被运算』变量的对应成员函数。与一元运算符不同的是,有算数意义的二元运算符有左右之分。

以加法『+』为例,它对应的函数是『左侧加法』__add__和『右侧加法』__radd__之分。

必须加以指出的是,在Python官方实现CPython中,如果『+』两侧的操作数都是整数、或者都是字符串的时候,并不会调用int.__add__或者str.__add__,而是直接进行快速计算,以提升性能。

除了快速计算的情形之外,无论是使用运算符『+』,还是调用operator.add()函数,最终会落到API函数PyNumber_Add(x,y)之上。它会尝试调用 x.__add__(y),如果抛出了NotImplemented异常,即对象x没有实现__add__函数,它会尝试调用y.__radd__(x)并作为最终结果。

作为理解和练习,读者可尝试运行下面的例子。

class A(object):

def __add__(self,other):

print ('A.__add__')

print (id(other))

class B(object):

def __radd__(self,other):

print ('B.__radd__')

print (id(other))

a = A()

b = B()

c = object()

print (id(a),id(b),id(c))

a + c

c + b

a + b

对于有算数含义的二元运算符/内建运算函数(+, -, *, /, %, divmod(), pow(), **, <>, &, ^, |) ,都有左侧、右侧运算之分。除了pow()(对应__pow__()和__rpow__())外,这些操作符在执行的时候都进行『先左后右』的尝试。其他的二元运算符,例如[](对应__setitem__)没有左右之分。

对于运算-赋值操作符(+=, -=, *=, /=, //=, %=, **=, <<=, >>=, &=, ^=, |=),以x += y 为例,在进行『+』之前,x.__iadd__(y) 这个函数会优先调用(在非快速计算的前提下)。如果__ixxx__出现了未实现异常,恢复为 x = x + y。

3. str.__mod__

回到开头的问题,之所以可以有 "Hello %s" % 'World' 这样的用法,是因为str这个类型『重载』了__mod__:

print ("Hello%s".__mod__('World'))

当然,利用运算符重载,你自己也可以写出(只有你自己才知道的)类型运算。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值