如果用 Python 写过代码,肯定遇到了数字,比如整数作为列表的索引,用浮点数表示当前财富的数量,等等。
但是,Python 中关于数字的知识远多于这些。
Python 中万物皆对象,正如在很多教材中都会看到的那样,几乎是作为第一个对象讲授的字符串对象 str ,一定查看过它的方法,比如将所有字母变为小写的 .lower() 方法。
>>> "HELLO".lower()
'hello'
毋庸置疑,数字也是 Python对象,那么它们必然也有自己的方法,比如可以用 .to_bytes() 方法将整数转化为字节字符串。
>>> n = 255
>>> n.to_bytes(length=2, byteorder="big")
b'\x00\xff'
其中参数 length 用来指定字节长度,参数 byteorder 定义自己的顺序,在上面的示例中,byteorder="big" 则在返回的字节字符串中,重要的字节排在前面,反之,则可以 byteorder="little" 。
255 是 8 位整数中最大的,所以,也可以将 length 设置为 1 :
>>> n.to_bytes(length=1, byteorder="big")
b'\xff'
但是,如果对于 256 ,也使用 length=1 ,就会出现 OverflowError 。
>>> n = 256
>>> n.to_bytes(length=1, byteorder="big")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
OverflowError: int too big to convert
也可以使用 int 的类方法 .from_bytes() 将字节字符串转化为整数:
>>> int.from_bytes(b'\x06\xc1', byteorder="big")
1729
除了整数,浮点数也有方法,例如最常用的 .is_integer() ,用于判断浮点数是否有非零的小数部分。
>>> n = 2.0
>>> n.is_integer()
True
>>> n = 3.14
>>> n.is_integer()
False
还有一个有趣的浮点数方法 .as_integer_ratio() ,它返回一个元组,其中包括浮点数所对应的分数的分子和分母,或者你可以理解为,用这个方法把浮点数转化为了分数。
>>> m = 2.5
>>> m.as_integer_ratio()
(5, 2)
当然,由于二进制转化的时候有误差(详见《Python大学实用教程》中的解释),也会出现下面的结果。
>>> m = 0.1
>>> m.as_integer_ratio()
(3602879701896397, 36028797018963968)
特别要注意,上面在使用整数对象的方法时,都是使用了变量引用该整数对象,如果这样:
>>> 255.to_bytes(length=1, byteorder="big")
File "<stdin>", line 1
255.to_bytes(length=1, byteorder="big")
^
SyntaxError: invalid syntax
解决方法是:
>>> (255).to_bytes(length=1, byteorder="big")
b'\xff'
但是,对于浮点数,则又不同:
>>> 3.14.is_integer()
False
>>> (3.14).is_integer()
False
注意区分。
数学上的数字都有层级,比如所有的自然数都是整数,所有的整数都是有理数,所有的有理数都是实数,所有的实数都是复数。
Python 中的数字,也有类似的金字塔结构。
Python 中的所有数字,都是 Number 类的实例:
>>> from numbers import Number
>>> isinstance(1992, Number)
True
>>> isinstance(7.28, Number)
True
>>> isinstance(1j, Number)
True
如果只关心某个对象是否是数字,而不在乎它是哪类数值,就可以使用 isinstance(value, Number) 进行检验。
在 numbers 模块中,有四个类:Complex、Real、Rational、Tntegral ,它们分别对应 Python 内置对象中的相应类型:
Complex 类用于表示复数。内置对象的 Complex 类型:complexReal 类用于表示实数。内置对象的 Real 类型:floatRotional 类用于表示有理数。内置对象的 Roational 类型:FractionIntegral 类用于表示整数。内置独享的 Integral 类型:int 和 bool没错,bool 的值也是数字。
>>> import numbers
# 复数继承自 Complex
>>> isinstance(1j, numbers.Complex)
True
# 复数不是 Real
>>> isinstance(1j, numbers.Real)
False
# 浮点数是 Real
>>> isinstance(3.14, numbers.Real)
True
# 浮点数不是 Rational
>>> isinstance(3.14, numbers.Rational)
False
# 分数是 Rational
>>> from fractions import Fraction
>>> isinstance(Fraction(1, 2), numbers.Rational)
True
# 分数不是 Integral
>>> isinstance(Fraction(1, 2), numbers.Integral)
False
# 整数是 Integral
>>> isinstance(1729, numbers.Integral)
True
# 布尔值是 Integral
>>> isinstance(True, numbers.Integral)
True
>>> True == 1
True
>>> False == 0
True
在上面所说的 Python 中四种数字层级:Complex、Real、Rational、Tntegral ,其中不包含 Decimal 类型,它比较特殊,算是第五级。
数学上,小数是实数,但是在 Python 中,不要认为 Decimal 类型的数是 Real 类型。
>>> from decimal import Decimal
>>> import numbers
>>> isinstance(Decimal("3.14159"), numbers.Real)
False
实际上,Decimal 类型的对象,仅继承了 Number 类。
>>> isinstance(Decimal("3.14159"), numbers.Complex)
False
>>> isinstance(Decimal("3.14159"), numbers.Rational)
False
>>> isinstance(Decimal("3.14159"), numbers.Integral)
False
>>> isinstance(Decimal("3.14159"), numbers.Number)
True
浮点数是 Real 类型,并表示实数,但是,由于内存限制,浮点数只是实数的近似值,例如:
>>> 0.1 + 0.1 + 0.1 == 0.3
False
此外,在 Python 中,float("inf") 和 float("nan") 也都是比较特殊的浮点数对象——不是数字的数。
利用 Python 中关于数字的类型,比如 numbers 中的类型,可以定义其他有特殊属性和方法的数字对象。
例如,下面所定义的 ExtendedInteger 类,就代表了形如 a+b\sqrt{p} 的数字对象,其中 a 和 b 是整数,p 不强制,默认为 2 。
import math
import numbers
class ExtendedInteger(numbers.Real):
def __init__(self, a, b, p = 2) -> None:
self.a = a
self.b = b
self.p = p
self._val = a + (b * math.sqrt(p))
def __repr__(self):
return f"{self.__class__.__name__}({self.a}, {self.b}, {self.p})"
def __str__(self):
return f"{self.a} + {self.b}√{self.p}"
def __trunc__(self):
return int(self._val)
def __float__(self):
return float(self._val)
def __hash__(self):
return hash(float(self._val))
def __floor__(self):
return math.floot(self._val)
def __ceil__(self):
return math.ceil(self._val)
def __round__(self, ndigits=None):
return round(self._val, ndigits=ndigits)
def __abs__(self):
return abs(self._val)
def __floordiv__(self, other):
return self._val // other
def __rfloordiv__(self, other):
return other // self._val
def __truediv__(self, other):
return self._val / other
def __rtruediv__(self, other):
return other / self._val
def __mod__(self, other):
return self._val % other
def __rmod__(self, other):
return other % self._val
def __lt__(self, other):
return self._val < other
def __le__(self, other):
return self._val <= other
def __eq__(self, other):
return float(self) == float(other)
def __neg__(self):
return ExtendedInteger(-self.a, -self.b, self.p)
def __pos__(self):
return ExtendedInteger(+self.a, +self.b, self.p)
def __add__(self, other):
if isinstance(other, ExtendedInteger):
# If both instances have the same p value,
# return a new ExtendedInteger instance
if self.p == other.p:
new_a = self.a + other.a
new_b = self.b + other.b
return ExtendedInteger(new_a, new_b, self.p)
# Otherwise return a float
else:
return self._val + other._val
# If other is integral, add other to self's a value
elif isinstance(other, numbers.Integral):
new_a = self.a + other
return ExtendedInteger(new_a, self.b, self.p)
# If other is real, return a float
elif isinstance(other, numbers.Real):
return self._val + other._val
# If other is of unknown type, let other determine
# what to do
else:
return NotImplemented
def __radd__(self, other):
# Addition is commutative so defer to __add__
return self.__add__(other)
def __mul__(self, other):
if isinstance(other, ExtendedInteger):
# If both instances have the same p value,
# return a new ExtendedInteger instance
if self.p == other.p:
new_a = (self.a * other.a) + (self.b * other.b * self.p)
new_b = (self.a * other.b) + (self.b * other.a)
return ExtendedInteger(new_a, new_b, self.p)
# Otherwise, return a float
else:
return self._val * other._val
# If other is integral, multiply self's a and b by other
elif isinstance(other, numbers.Integral):
new_a = self.a * other
new_b = self.b * other
return ExtendedInteger(new_a, new_b, self.p)
# If other is real, return a float
elif isinstance(other, numbers.Real):
return self._val * other
# If other is of unknown type, let other determine
# what to do
else:
return NotImplemented
def __rmul__(self, other):
# Multiplication is commutative so defer to __mul__
return self.__mul__(other)
def __pow__(self, exponent):
return self._val ** exponent
def __rpow__(self, base):
return base ** self._val
上面所定义的数字类型,可以这样使用:
>>> a = ExtendedInteger(1, 2)
>>> b = ExtendedInteger(2, 3)
>>> a
ExtendedInteger(1, 2, 2)
>>> # Check that a is a Number
>>> isinstance(a, numbers.Number)
True
>>> # Check that a is Real
>>> isinstance(a, numbers.Real)
True
>>> print(a)
1 + 2√2
>>> a * b
ExtendedInteger(14, 7, 2)
>>> print(a * b)
14 + 7√2
>>> float(a)
3.8284271247461903
Python 中的继承功能,让我们能灵活地定义各种对象。
由上面所述,可知 Python 中的数字还是可以深入研究一番的。
[1]. David Amos, 3 Things You Might Not Know About Numbers in Python
[2]. 齐伟,Python大学实用教程,北京:电子工业出版社