老齐教室

Python 中有关数字必知的三件事

如果用 Python 写过代码,肯定遇到了数字,比如整数作为列表的索引,用浮点数表示当前财富的数量,等等。

但是,Python 中关于数字的知识远多于这些。

1. 数字有方法

Python 中万物皆对象,正如在很多教材中都会看到的那样,几乎是作为第一个对象讲授的字符串对象 str ,一定查看过它的方法,比如将所有字母变为小写的 .lower() 方法。

1
2
>>> "HELLO".lower()
'hello'

毋庸置疑,数字也是 Python对象,那么它们必然也有自己的方法,比如可以用 .to_bytes() 方法将整数转化为字节字符串。

1
2
3
>>> n = 255
>>> n.to_bytes(length=2, byteorder="big")
b'\x00\xff'

其中参数 length 用来指定字节长度,参数 byteorder 定义自己的顺序,在上面的示例中,byteorder="big" 则在返回的字节字符串中,重要的字节排在前面,反之,则可以 byteorder="little"

255 是 8 位整数中最大的,所以,也可以将 length 设置为 1

1
2
>>> n.to_bytes(length=1, byteorder="big")
b'\xff'

但是,如果对于 256 ,也使用 length=1 ,就会出现 OverflowError

1
2
3
4
5
>>> 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() 将字节字符串转化为整数:

1
2
>>> int.from_bytes(b'\x06\xc1', byteorder="big")
1729

除了整数,浮点数也有方法,例如最常用的 .is_integer() ,用于判断浮点数是否有非零的小数部分。

1
2
3
4
5
6
7
>>> n = 2.0
>>> n.is_integer()
True

>>> n = 3.14
>>> n.is_integer()
False

还有一个有趣的浮点数方法 .as_integer_ratio() ,它返回一个元组,其中包括浮点数所对应的分数的分子和分母,或者你可以理解为,用这个方法把浮点数转化为了分数。

1
2
3
>>> m = 2.5
>>> m.as_integer_ratio()
(5, 2)

当然,由于二进制转化的时候有误差(详见《Python大学实用教程》中的解释),也会出现下面的结果。

1
2
3
>>> m = 0.1
>>> m.as_integer_ratio()
(3602879701896397, 36028797018963968)

特别要注意,上面在使用整数对象的方法时,都是使用了变量引用该整数对象,如果这样:

1
2
3
4
5
>>> 255.to_bytes(length=1, byteorder="big")
File "<stdin>", line 1
255.to_bytes(length=1, byteorder="big")
^
SyntaxError: invalid syntax

解决方法是:

1
2
>>> (255).to_bytes(length=1, byteorder="big")
b'\xff'

但是,对于浮点数,则又不同:

1
2
3
4
>>> 3.14.is_integer()
False
>>> (3.14).is_integer()
False

注意区分。

2. 数字有层级

数学上的数字都有曾经,比如所有的自然数都是整数,所有的整数都是有理数,所有的有理数都是实数,所有的实数都是复数。

Python 中的数字,也有类似的金字塔结构。

层级继承

Python 中的所有数字,都是 Number 类的实例:

1
2
3
4
5
6
7
>>> from numbers import Number
>>> isinstance(1992, Number)
True
>>> isinstance(7.28, Number)
True
>>> isinstance(1j, Number)
True

如果只关心某个对象是否是数字,而不在乎它是哪类数值,就可以使用 isinstance(value, Number) 进行检验。

numbers 模块中,有四个类:ComplexRealRationalTntegral ,它们分别对应 Python 内置对象中的相应类型:

  • Complex 类用于表示复数。内置对象的 Complex 类型:complex
  • Real 类用于表示实数。内置对象的 Real 类型:float
  • Rotional 类用于表示有理数。内置对象的 Roational 类型:Fraction
  • Integral 类用于表示整数。内置独享的 Integral 类型:intbool

没错,bool 的值也是数字。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
>>> 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 中四种数字层级:ComplexRealRationalTntegral ,其中不包含 Decimal 类型,它比较特殊,算是第五级。

数学上,小数是实数,但是在 Python 中,不要认为 Decimal 类型的数是 Real 类型。

1
2
3
4
5
>>> from decimal import Decimal
>>> import numbers

>>> isinstance(Decimal("3.14159"), numbers.Real)
False

实际上,Decimal 类型的对象,仅继承了 Number 类。

1
2
3
4
5
6
7
8
9
10
11
>>> 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 类型,并表示实数,但是,由于内存限制,浮点数只是实数的近似值,例如:

1
2
>>> 0.1 + 0.1 + 0.1 == 0.3
False

此外,在 Python 中,float("inf")float("nan") 也都是比较特殊的浮点数对象——不是数字的数。

3. 数字可扩展

利用 Python 中关于数字的类型,比如 numbers 中的类型,可以定义其他有特殊属性和方法的数字对象。

例如,下面所定义的 ExtendedInteger 类,就代表了形如 $a+b\sqrt{p}$ 的数字对象,其中 $a$ 和 $b$ 是整数,$p$ 不强制,默认为 $2$ 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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

上面所定义的数字类型,可以这样使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> 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 + 22

>>> a * b
ExtendedInteger(14, 7, 2)

>>> print(a * b)
14 + 72

>>> float(a)
3.8284271247461903

Python 中的继承功能,让我们能灵活地定义各种对象。

结论

由上面所述,可知 Python 中的数字还是可以深入研究一番的。

参考资料

[1]. David Amos, 3 Things You Might Not Know About Numbers in Python

[2]. 齐伟,Python大学实用教程,北京:电子工业出版社

使用支付宝打赏
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

关注微信公众号,读文章、听课程,提升技能