在聊完类和对象之后,我们要理解一件事,无论是在Scala还是Python,每一个值都是对象,在某种程度上来说,这两门语言都是更加纯粹的面向对象的语言。两者也都支持函数是一等对象,区别在于Python并不认为自己是函数式编程语言,而Scala处处都在鼓励使用不可变的数据结构。今天要聊的是操作符,我们可以看到在Scala和Python里如何给一个自定义对象加上操作符。
首先从概念的开始: Scala的操作符并不算是重载,而是“操作符即方法”,在Scala里的操作符可以作为方法的名字,如前面的文章提到的,1+2实际上是1.+(2)的语法糖,之前的我们可能还不好理解,现在我们可以这么理解,1代表着一个Int类的实例,有个方法被命名为+,接受一个参数,返回Int结果。+符号这种方法,在Scala里被称为操作符表示法,这个表示法使我们可以不必用.号去调用对象的方法。+号可以理解为一种二元操作符,接受两个操作元,一个在左一个在右,还有一种叫做是一元操作符,用来表示正数和负数,例如-,+,!,~符号,不过不同于+符号直接使用+作为符号名,一元操作符使用'unary_'加上操作符表示。除了运算符外,还有一种符号叫做逻辑操作,例如<,>,>=返回比较的结果的Boolean值。
scala> 2.unary_-
res0: Int = -2
回过头来看看Python的操作符重载,这个重载是名副其实的重载,Python通过使用操作符重载使得用户定义的对象可以使用二元运算符和一元运算符。不过Python为了防止运算符重载的乱用,加了一些限制:例如不能重载内置类型的运算符(也就是说不能通过继承重载),不能新建运算符。Python重载+使用的是__add__方法,我们的1+1也实际上使用的也是__add__方法。只要用户在自定义的类里定义了__add__,+实际上就会调用用户定义的__add__方法。再来看一元操作符,例如-,也就可以是用__ne__来实现负数。
i = 1
i.__add__(1)
Out[2]: 2
总的来说,原则就一个:运算符的方法始终返回一个新的对象,也就是说,要创建并返回一个适合类型的新实例。
例子: 概念相对比较枯燥,还是从例子上来说明吧。我们可以简单的实现一个二元向量类做为示例(在Scala和Python都有更好的库去实现一个向量,例子中的向量仅仅只是为了展示如何重载运算符,而且在Python的实现也没有加上类型检查)
0.更好的显示 Scala:
class Vector(x:Int, y:Int){
override def toString = s"Vector($x,$y)"
}
scala> val a = new Vector(1,2)
a: Vector = Vector(1,2)
Python:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%s, %s)'%(self.x, self.y)
Vector(1,2)
Out[4]: Vector(1, 2)
我们看出来返回的结果是Vector(1,2), 具体的细节后面再说。
1.重载一元操作符 Scala:
class Vector(x:Int, y:Int){
override def toString = s"Vector($x,$y)"
val x1:Int = x
val y1:Int = y
def unary_-():Vector = new Vector(-x1, -y1)
def unary_+():Vector = new Vector(x1,y1)
}
scala> val a = new Vector(1,2)
a: Vector = Vector(1,2)
scala> -a
res2: Vector = Vector(-1,-2)
scala> +a
res3: Vector = Vector(1,2)
Python:
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return 'Vector(%s, %s)'%(self.x, self.y)
def __neg__(self):
return Vector(-self.x, -self.y)
def __pos__(self):
return Vector(self.x, self.y)
a = Vector(1,2)
-a
Out[8]: Vector(-1, -2)
+a
Out[9]: Vector(1, 2)
Scala里我们要注意x,y重新赋值给了x1,y1,这个是因为参数x,y是属于类的的作用域里面的,在后面实现二元操作符时,我们需要传入一个Vector的实例,而x,y在这个时候是不能使用的。这时候要访问x,y,必须要通过字段来访问。
2.二元操作符: Scala:
class Vector(x:Int, y:Int){
...
def +(that:Vector):Vector = new Vector(x + that.x1, y + that.y1)
def *(that:Vector):Vector = new Vector(x * that.x1, y * that.y1)
}
scala> val a = new Vector(1,2)
a: Vector = Vector(1,2)
scala> val b = new Vector(2,2)
b: Vector = Vector(2,2)
scala> a+b
res4: Vector = Vector(3,4)
scala> a*b
res5: Vector = Vector(2,4)
Python:
class Vector:
...
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __mul__(self, other):
return Vector(self.x * other.x, self.y * other.y)
a = Vector(1,2)
b = Vector(2,2)
a+b
Out[19]: Vector(3, 4)
a*b
Out[20]: Vector(2, 4)
3.比较运算符 Scala:
class Vector(x:Int, y:Int){
...
def ==(that:Vector):Boolean = x == that.x1 & y == that.y1
def !=(that:Vector):Boolean = {
val result = that == new Vector(x,y)
!result
}
}
scala> a == b
res7: Boolean = false
scala> a != b
res8: Boolean = true
Python:
class Vector:
...
def __eq__(self, other):
return (self.x == other.x) and (self.y == other.y)
def __ne__(self, other):
eq_result = self == other
return not eq_result
a == b
Out[21]: False
a != b
Out[22]: True