python 的设计和问题
python 语言的使用中,不得不说非常方便,但是对于从 java 系转 python 开发的学习者,还是存在多处难以理解的地方,这里我们讨论一下 python 的设计问题。
为什么 python 使用缩进来分组?
设计者 ( Guido van Rossum ) 认为使用缩进来分组会很优雅这是一点,也不会出现下面 c++ 代码的歧义:
1 | if (x <= y) |
条件如果是真,那么只会执行 x++;
,但是缩进让我们觉得,下面一行也会执行,让机器和人的感受相同这是第二点,最后统一了编码风格。
为什么浮点运算很奇怪?
1 | 1.2 - 1.0 |
这是因为 python 中的 float 即 c 的 double 存储格式,它的浮点运算和 c 和 java 是十分相似的,都依赖于处理器的硬件。它可以精确到小数点后17位。
比如 1.2
这个十进制数在机器中的二进制表示是:
1 | 1.0011001100110011001100110011001100110011001100110011 |
它所对应的十进制就是:
1 | 1.1999999999999999555910790149937383830547332763671875 |
对于必须的运算 ( 超过17位的精度 ) 可以采用 decimal
模块进行处理, decimal 默认精度到28位。
1 | from decimal import Decimal |
当然也可以用 round()
四舍五入凑合。
1 | #coding:utf8 |
为什么在方法中明确使用 self 参数?
我现在所能理解的原因还停留在 python 没有 声明 这一功能,使用 self 来保存实例的变量更加方便。
事实上这也是主要原因之二 (方便保存,区分实例变量和局部变量) ,别的有一点在于在派生类中覆盖基类的方法以及调用。
为什么有的方法用 “.” 调用,有的用 “()” ?
比如 list.index()
使用 .
来调用,有的比如 len(list)
使用 ()
来调用。一个对象在前面,一个对象在括号里。
主要历史的原因导致,函数 ()
对于一组类型通用的操作,比如 map()
和 zip()
;而例如 len()
、 max()
等内置的方法实际上它们已经存在了,所以无法再进行更改,就是这个理由…
为什么 python 没有 switch case ?
我们不讨论 if else
的实现,对于大量可能性中选择,可以创建一个字典,或者使用 getattr()
检索方法。
1 | #coding:utf8 |
在方法前加上统一的前缀是推荐的。
可以在解释器中实现线程而不依赖系统的线程吗?
我们通常使用的是 cpython 解释器,而这个解释器的线程需要 c 的线程支持,或者使用完全重新设计的解释器绕过 c 栈。
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 | #coding:utf8 |
python 如何管理内存?
python 根据解释器的不同,对于内存的管理也不相同。比如 cpython 使用引用计数来检测不可访问的对象;用另一种收集引用周期的机制:周期性地执行循环检测算法,寻找不可访问的循环并删除其中涉及的对象。 gc 模块提供垃圾收集、获取调试统计信息和调整收集器参数的功能。
而其他实现,比如 jython 或 pypy ,可以采用不同的机制,比如非模块提供的垃圾收集器。
在传统的 cpython 下,使用 gc 模块提供的垃圾回收机制,而对于一段开启文件的代码,我们最好的做法是明确关闭文件 ( with
) ,否则将以不同长的时间被收集和关闭。
1 | with open(file) as f: |
为什么 cpython 不使用更传统的垃圾回收机制?
在 c 中,垃圾回收也不是其标准功能,因此无法移植。
其次,当 python 嵌入到其他程序中时,它们可能有自己更好的内存垃圾回收方案。
为什么当 cpython 退出时不会释放所有的内存?
python 在退出的时候会试图删除每一个对象,但是循环引用可能出现退出的时候无法删除。
1 | a, b = Sth(), Sth() |
如果想要强制摧毁,可以使用 atexit 模块,这个模块可以在退出的时候调用函数,只要在其中写摧毁的方法即可。
为什么需要单独的元组和列表类型?
列表和元组类似,但是通常以不同的方式使用。元组被认为是 c 的结构体,它们可以是不同类型作为一组的集合,例如笛卡尔坐标被认为是两个或三个数字的集合;列表更像数组,它倾向于数量不同的相同类型对象。元组不可变,列表可变,只有不可变元素可以作为字典的键值,所以只有元组可以作为键。
如何实现字典?
python 中的字典是可调整大小的散列表,用内置 hash()
方法计算键值,可以在 O(1) 的时间里找到目标,这也意味着他没有维护键的排序顺序。
为什么字典的键必须是不可变的?
字典哈希表的实现是根据键的 hash 值来查找,如果 key 可变,那么值也在变化,所以对象就找不到了。
为什么 list.sort() 不返回排序的列表?
我们总会记错, list.sort()
在 list 上进行排序,而不会创建新的副本,如果想得到一个副本,可以使用 sorted
内置方法。
如何在Python中指定并强制执行接口规范?
首先对于一个类可以用 issubclass()
判断子类,用 isinstance()
判断实例。
python 的接口规范可以使用测试用例来限制, doctest 和 unittest 模块或第三方测试框架可用于构建详尽的测试套件,以便在模块中执行每行代码,并且推荐在测试中限制。
为什么没有 goto ?
goto 确实不好,但是怎么实现呢?
1 | #coding:utf8 |
为什么 python 不能在 with 中直接进行属性赋值?
这句话需要解释一下,看下面的代码:
1 | #coding:utf8 |
它是对的,这里的 x 需要它的前缀,而不能直接用 x 代替,这是因为, python 作为动态语言,它不能像静态语言那样,永远知道变量的范围,所以现在我们也不知道这个 x 是全局的还是成员变量亦或是局部变量。
动态语言的好处比如想要实现下面的代码:
1 | function(args).mydict[index][index].a = 21 |
不需要这么麻烦啦!
1 | ref = function(args).mydict[index][index] |
有些问题这里没有讨论,比如:
- 为什么不在表达式中赋值?
- 为什么字符串不可变?
- 为什么 join 作用在字符串而不是列表和元组?
- 异常有多快?
- python 除了编译成机器码还能编译成什么语言?
- 如何实现列表?
- 为什么字符串不能以奇数个反斜杠结尾?
- 为什么 if/while/def/class 语句后面要冒号?
- 为什么 python 允许在列表和元组的结尾加逗号?
我认为它们没有意义,或者我还没有意识到它们的重要性。