python 的设计和问题

python 语言的使用中,不得不说非常方便,但是对于从 java 系转 python 开发的学习者,还是存在多处难以理解的地方,这里我们讨论一下 python 的设计问题。

资料柜:
Design and History FAQ

为什么 python 使用缩进来分组?

设计者 ( Guido van Rossum ) 认为使用缩进来分组会很优雅这是一点,也不会出现下面 c++ 代码的歧义:

1
2
3
4
if (x <= y)
x++;
y--;
z++;

条件如果是真,那么只会执行 x++; ,但是缩进让我们觉得,下面一行也会执行,让机器和人的感受相同这是第二点,最后统一了编码风格。

为什么浮点运算很奇怪?

1
2
>>> 1.2 - 1.0
0.19999999999999996

这是因为 python 中的 float 即 c 的 double 存储格式,它的浮点运算和 c 和 java 是十分相似的,都依赖于处理器的硬件。它可以精确到小数点后17位。

比如 1.2 这个十进制数在机器中的二进制表示是:

1
1.0011001100110011001100110011001100110011001100110011

它所对应的十进制就是:

1
1.1999999999999999555910790149937383830547332763671875

对于必须的运算 ( 超过17位的精度 ) 可以采用 decimal 模块进行处理, decimal 默认精度到28位。

1
2
3
4
5
from decimal import Decimal
a = Decimal(repr(1.2))
b = Decimal(repr(1.0))
print(a - b)
# 0.2

当然也可以用 round() 四舍五入凑合。

1
2
3
4
5
#coding:utf8
a = 1.2
b = 1.0
print(round(a - b , 2))
# 0.2

为什么在方法中明确使用 self 参数?

我现在所能理解的原因还停留在 python 没有 声明 这一功能,使用 self 来保存实例的变量更加方便。

事实上这也是主要原因之二 (方便保存,区分实例变量和局部变量) ,别的有一点在于在派生类中覆盖基类的方法以及调用。

为什么有的方法用 “.” 调用,有的用 “()” ?

比如 list.index() 使用 . 来调用,有的比如 len(list) 使用 () 来调用。一个对象在前面,一个对象在括号里。

主要历史的原因导致,函数 () 对于一组类型通用的操作,比如 map()zip() ;而例如 len()max() 等内置的方法实际上它们已经存在了,所以无法再进行更改,就是这个理由…

为什么 python 没有 switch case ?

我们不讨论 if else 的实现,对于大量可能性中选择,可以创建一个字典,或者使用 getattr() 检索方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#coding:utf8

class A:
def visit_a(self):
print('hello')

def dispatch(self, value):
method_name = 'visit_' + str(value)
method = getattr(self, method_name)
method()

if __name__ == '__main__':
a = A()
a.dispatch('a')

在方法前加上统一的前缀是推荐的。

可以在解释器中实现线程而不依赖系统的线程吗?

我们通常使用的是 cpython 解释器,而这个解释器的线程需要 c 的线程支持,或者使用完全重新设计的解释器绕过 c 栈。

stackless-dev/stackless

python 的线程总是被人们诟病,因为 全局解释器锁 ( GIL ) 的存在, python 中只有一个线程在运行,对于 I/O 操作来说, GIL 会在 I/O 前释放,以允许其他线程在这个线程等待 I/O 的时候运行,而非 I/O 呢?它将等待。所以说: I/O密集型的Python程序比计算密集型的Python程序更能充分利用多线程

PS :
什么是 I/O 密集?涉及到网络、磁盘 I/O 的任务都是 I/O 密集型任务。(CPU 上的时间少)
什么是计算密集?消耗 CPU 资源,比如对视频进行解码等。

python 的线程执行必须要 GIL ,所以无论多少核,单位时间里,多个核只能跑一个线程。怎么办?

  • 使用多进程,每个进程一个 GIL ,所以多进程是没问题的。
  • 用不带 GIL 的解释器。
  • 用其他语言扩展,比如 c ,不用 python 实现。

为什么 lambda 表达式不能包含语句?

因为 python 的句法框架无法处理嵌套在表达式中的语句。

使用 lambda 表达式的唯一好处是不再需要创建一个新的函数,并且 lambda 表达只是一个局部变量,而函数相当于 lambda 表达式生成的对象。python 中的 lambda 表达式冒号前是参数,冒号后面是返回值。

1
2
3
4
5
#coding:utf8

if __name__ == '__main__':
add = lambda x,y:x+y
print(add(1, 2))

python 如何管理内存?

python 根据解释器的不同,对于内存的管理也不相同。比如 cpython 使用引用计数来检测不可访问的对象;用另一种收集引用周期的机制:周期性地执行循环检测算法,寻找不可访问的循环并删除其中涉及的对象。 gc 模块提供垃圾收集、获取调试统计信息和调整收集器参数的功能。

而其他实现,比如 jython 或 pypy ,可以采用不同的机制,比如非模块提供的垃圾收集器。

在传统的 cpython 下,使用 gc 模块提供的垃圾回收机制,而对于一段开启文件的代码,我们最好的做法是明确关闭文件 ( with ) ,否则将以不同长的时间被收集和关闭。

1
2
with open(file) as f:
...

为什么 cpython 不使用更传统的垃圾回收机制?

在 c 中,垃圾回收也不是其标准功能,因此无法移植。

其次,当 python 嵌入到其他程序中时,它们可能有自己更好的内存垃圾回收方案。

为什么当 cpython 退出时不会释放所有的内存?

python 在退出的时候会试图删除每一个对象,但是循环引用可能出现退出的时候无法删除。

1
2
3
a, b = Sth(), Sth()
a.attr = b
b.attr = a

如果想要强制摧毁,可以使用 atexit 模块,这个模块可以在退出的时候调用函数,只要在其中写摧毁的方法即可。

为什么需要单独的元组和列表类型?

列表和元组类似,但是通常以不同的方式使用。元组被认为是 c 的结构体,它们可以是不同类型作为一组的集合,例如笛卡尔坐标被认为是两个或三个数字的集合;列表更像数组,它倾向于数量不同的相同类型对象。元组不可变,列表可变,只有不可变元素可以作为字典的键值,所以只有元组可以作为键。

如何实现字典?

python 中的字典是可调整大小的散列表,用内置 hash() 方法计算键值,可以在 O(1) 的时间里找到目标,这也意味着他没有维护键的排序顺序。

为什么字典的键必须是不可变的?

字典哈希表的实现是根据键的 hash 值来查找,如果 key 可变,那么值也在变化,所以对象就找不到了。

为什么 list.sort() 不返回排序的列表?

我们总会记错, list.sort() 在 list 上进行排序,而不会创建新的副本,如果想得到一个副本,可以使用 sorted 内置方法。

如何在Python中指定并强制执行接口规范?

首先对于一个类可以用 issubclass() 判断子类,用 isinstance() 判断实例。

python 的接口规范可以使用测试用例来限制, doctest 和 unittest 模块或第三方测试框架可用于构建详尽的测试套件,以便在模块中执行每行代码,并且推荐在测试中限制。

为什么没有 goto ?

goto 确实不好,但是怎么实现呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding:utf8

class label(Exception):
print('hello2')

if __name__=='__main__':
try:
print('hello3')
if 1<2:
print('hello1')
raise label()
except label:
print('bye')

# 输出结果是:231
# 为什么?解释器先执行了类啊...

为什么 python 不能在 with 中直接进行属性赋值?

这句话需要解释一下,看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#coding:utf8

def foo(a):
with a:
print(a.x)

class A:
x = 1
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
pass

if __name__ == '__main__':
a = A()
foo(a)

它是对的,这里的 x 需要它的前缀,而不能直接用 x 代替,这是因为, python 作为动态语言,它不能像静态语言那样,永远知道变量的范围,所以现在我们也不知道这个 x 是全局的还是成员变量亦或是局部变量。

动态语言的好处比如想要实现下面的代码:

1
2
3
function(args).mydict[index][index].a = 21
function(args).mydict[index][index].b = 42
function(args).mydict[index][index].c = 63

不需要这么麻烦啦!

1
2
3
4
ref = function(args).mydict[index][index]
ref.a = 21
ref.b = 42
ref.c = 63

有些问题这里没有讨论,比如:

  • 为什么不在表达式中赋值?
  • 为什么字符串不可变?
  • 为什么 join 作用在字符串而不是列表和元组?
  • 异常有多快?
  • python 除了编译成机器码还能编译成什么语言?
  • 如何实现列表?
  • 为什么字符串不能以奇数个反斜杠结尾?
  • 为什么 if/while/def/class 语句后面要冒号?
  • 为什么 python 允许在列表和元组的结尾加逗号?

我认为它们没有意义,或者我还没有意识到它们的重要性。