1. 不要在 for 循环后面写 else 块
In [40]: for i in range(3):
....: print i
....: else:
....: print "end"
....:
0
1
2
end
记住一点:for 循环里面有 break 时 else 不会执行,否则执行完 for 循环会继续执行 else 语句
2. 函数传递
Python函数参数传递注意点:
- 对于可变的对象的修改在函数外部和内部都可见,调用者和被调用者共享这个对象。
- 而对于不可变对象,由于并不能真正被修改,因为修改一般都是通过生成一个新的对象然后赋值来实现的。
3. 如何避开变量作用域的陷阱
全局变量简单说就是这3点:
- 全局变量是位于模块文件内部的顶层的变量名。
- 全局变量如果是在函数内被改变的话,一定要用global。
- 全局变量名在函数内部不经过声明也可以被引用。
4.
5. 全局解释器锁(GIL)
是什么原因导致多线程不快反慢的呢?
原因就在于 GIL ,在 Cpython 解释器(Python语言的主流解释器)中,有一把全局解释锁(Global Interpreter Lock),在解释器解释执行 Python 代码时,先要得到这把锁,意味着,任何时候只可能有一个线程在执行代码,其它线程要想获得 CPU 执行代码指令,就必须先获得这把锁,如果锁被其它线程占用了,那么该线程就只能等待,直到占有该锁的线程释放锁才有执行代码指令的可能。
因此,这也就是为什么两个线程一起执行反而更加慢的原因,因为同一时刻,只有一个线程在运行,其它线程只能等待,即使是多核CPU,也没办法让多个线程「并行」地同时执行代码,只能是交替执行,因为多线程涉及到上线文切换、锁机制处理(获取锁,释放锁等),所以,多线程执行不快反慢。
但是多线程有个问题,怎么解决共享数据的同步、一致性问题,因为,对于多个线程访问共享数据时,可能有两个线程同时修改一个数据情况,如果没有合适的机制保证数据的一致性,那么程序最终导致异常,所以,Python之父就搞了个全局的线程锁,不管你数据有没有同步问题,反正一刀切,上个全局锁,保证数据安全。这也就是多线程鸡肋的原因,因为它没有细粒度的控制数据的安全,而是用一种简单粗暴的方式来解决。
6. is 和 == 的区别
官方文档中说 is 表示的是对象标示符(object identity),而 == 表示的是相等(equality)。is 的作用是用来检查对象的标示符是否一致,也就是比较两个对象在内存中的地址是否一样,而 == 是用来检查两个对象是否相等。
我们在检查 a is b 的时候,其实相当于检查 id(a) == id(b)。而检查 a == b 的时候,实际是调用了对象 a 的 --eq()–方法,a == b 相当于 a.–eq–(b)。
总结一下,is 是检查两个对象是否指向同一块内存空间,而 == 是检查他们的值是否相等。可以看出,is 是比 == 更严格的检查,is 返回True表明这两个对象指向同一块内存,值也一定相同。
Python里和None比较时,为什么是 is None 而不是 == None 呢?
这是因为None在Python里是个单例对象,一个变量如果是None,它一定和None指向同一个内存地址。而 == None背后调用的是–eq–,而 eq 可以被重载,
7. 连接字符串用 join 还是 +
字符串是不可变对象,当用操作符+连接字符串的时候,每执行一次+都会申请一块新的内存,然后复制上一个+操作的结果和本次操作的右操作符到这块内存空间,因此用+连接字符串的时候会涉及好几次内存申请和复制。而join在连接字符串的时候,会先计算需要多大的内存存放结果,然后一次性申请所需内存并将字符串复制过去,这是为什么join的性能优于+的原因。所以在连接字符串数组的时候,我们应考虑优先使用join。
8. 函数可以在容器中使用
函数可以容器中使用,比如列表,字典里面象参数一样使用:
def show_apple(price):
print "The apple's price is {0}".format(price)
def show_orange(price):
print "The orange's price is {}".format(price)
func_dict = {'apple': show_apple, 'orange': show_orange}
def main(fruit, price):
func_dict.get(fruit)(price)
main('apple', 10)
main('orange', 8)
9. __new__和__init__的区别
#实际上,__init__函数并不是真正意义上的构造函数,__init__方法做的事情是在对象创建好
#之后初始化变量。真正创建实例的是__new__方法。
class Person(object):
def __init__(self, name, age):
print("in __init__")
self._name = name
self._age = age
def __new__(cls, *args, **kwargs):
print("in __new__")
instance = object.__new__(cls, *args, **kwargs)
return instance
p = Person("li", 20)
## 代码输出
in __new__
in __init__
'''上面的代码中实例化了一个Person对象,可以看到__new__和__init__都被调用了。
__new__方法用于创建对象并返回对象,当返回对象时会自动调用__init__方法进行初始化。
__new__方法是静态方法,而__init__是实例方法。'''
-
__new__至少要有一个参数 cls,代表当前类,此参数在实例化时由 Python 解释器自动识别。
-
__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以 return 父类(通过 super(当前类名, cls))__new__出来的实例,或者直接是 object 的__new__出来的实例。
-
__init__有一个参数 self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值。
-
如果__new__创建的是当前类的实例,会自动调用__init__函数,通过 return 语句里面调用的__new__函数的第一个参数是 cls 来保证是当前类实例,如果是其他类的类名;那么实际创建返回的就是其他类的实例,其实就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。
10. with 与上下文管理器
with open('test.txt', 'w') as f:
f.write('Hello world')
'''上面的with代码背后发生了些什么?我们来看下它的执行流程
首先执行open('output', 'w'),返回一个文件对象
调用这个文件对象的__enter__方法,并将__enter__方法的返回值赋值给变量f
执行with语句体,即with语句包裹起来的代码块
不管执行过程中是否发生了异常,执行文件对象的__exit__方法,在__exit__方法中关闭文件。
这里的关键在于open返回的文件对象实现了__enter__和__exit__方法。
一个实现了__enter__和__exit__方法的对象就称之为上下文管理器。
__enter__方法在语句体执行之前进入运行时上下文,__exit__在语句体执行完后从运行时上下文退出。
在实际应用中,__enter__一般用于资源分配,如打开文件、连接数据库、获取线程锁;
__exit__一般用于资源释放,如关闭文件、关闭数据库连接、释放线程锁。
我们先来看下__enter__和__exit__方法的定义:
__enter__() - 进入上下文管理器的运行时上下文,在语句体执行前调用。
如果有as子句,with语句将该方法的返回值赋值给 as 子句中的 target。
__exit__(exception_type, exception_value, traceback) - 退出与上下文管理器相关的
运行时上下文,
返回一个布尔值表示是否对发生的异常进行处理。如果with语句体中没有异常发生,
则__exit__的3个参数都为None,即调用 __exit__(None, None, None),
并且__exit__的返回值直接被忽略。如果有发生异常,则使用 sys.exc_info
得到的异常信息为参数调用__exit__(exception_type, exception_value, traceback)。
出现异常时,如果__exit__(exception_type, exception_value, traceback)返回 False,
则会重新抛出异常,让with之外的语句逻辑来处理异常;如果返回 True,则忽略异常,
不再对异常进行处理。'''
我们来自己定义一个简单的上下文管理器。这里不做实际的资源分配和释放,而用打印语句来表明当前的操作。
class ContextManager(object):
def __enter__(self):
print("[in __enter__] acquiring resources")
def __exit__(self, exception_type, exception_value, traceback):
print("[in __exit__] releasing resources")
if exception_type is None:
print("[in __exit__] Exited without exception")
else:
print("[in __exit__] Exited with exception: %s" % exception_value)
return False
with ContextManager():
print("[in with-body] Testing")
运行上面代码输出以下内容:
[in __enter__] acquiring resources
[in with-body] Testing
[in __exit__] releasing resources
[in __exit__] Exited without exception
11. 浅拷贝与深拷贝
Python中对象的赋值实际上是简单的对象引用,也就是说,当你创建一个对象,然后把它复制给另一个变量的时候,Python并没有拷贝这个对象,而是拷贝了这个对象的引用。
In [1]: a = [1,2,3,4]
In [2]: b = a
In [3]: id(a)
Out[3]: 140555756386496
In [4]: id(b)
Out[4]: 140555756386496
In [5]: id(a) == id(b)
Out[5]: True
浅拷贝
一般使用copy.copy(),可以进行对象的浅拷贝。它复制了对象但对于对象中的子对象,依然使用原始的引用。
In [7]: import copy
In [8]: a = [1, [1,2,3,4]]
In [9]: b = copy.copy(a)
In [10]: id(a)
Out[10]: 140555756497592
In [11]: id(b)
Out[11]: 140555772667448
In [12]: id(a) == id(b)
Out[12]: False
In [13]: id(a[1])
Out[13]: 140555756497376
In [14]: id(a[1]) == id(b[1])
Out[14]: True
# a[1] 和 b[1] 指向同一个对象
深拷贝
深度拷贝需要用copy.deepcopy()进行深拷贝。它会复制一个容器对象,以及它里面的所有元素(包含元素的子元素)
In [15]: a = [2, [3,4,5,6]]
In [16]: b = copy.deepcopy(a)
In [17]: id(a) == id(b)
Out[17]: False
In [18]: id(a[1]) == id(b[1])
Out[18]: False
# a[1] 和 b[1] 指向不同的对象
12. 字典排序
In [23]: dd = {'cc':1, 'bb':2, 'aa':3}
In [24]: dd
Out[24]: {'aa': 3, 'bb': 2, 'cc': 1}
In [25]: sorted(dd.iteritems(), key=lambda x:x[0]) #表示按照key排序
Out[25]: [('aa', 3), ('bb', 2), ('cc', 1)]
In [26]: sorted(dd.iteritems(), key=lambda x:x[1]) #表示按照value排序
Out[26]: [('cc', 1), ('bb', 2), ('aa', 3)]
13. 字典取值
建议:尽量用dict.get()来代替dict[key]
14. 字典中提取部分子集
In [27]: students_score={'jack':80,'james':91,'leo':100,'sam':60}
In [28]: good_score={name:score for name,score in students_score.items() if score>90}
In [29]: good_score
Out[29]: {'james': 91, 'leo': 100}
15. 字典的翻转
In [30]: a = {'a':1, 'b':2, 'c':3}
In [31]: invert_a = dict([(v, k) for k, v in a.iteritems()])
In [32]: invert_a
Out[32]: {1: 'a', 2: 'b', 3: 'c'}
16. 可变与不可变类型
将一个整数变量传递给函数,函数对它进行操作,但原整数变量a不发生变化。对于基本数据类型的变量,变量传递给函数后,函数会在内存中复制一个新的变量,从而不影响原来的变量。(我们称此为值传递)。
In [4]: a = 1
In [5]: def change_integer(a):
...: a = a + 1
...: return a
...:
In [6]: change_integer(a)
Out[6]: 2
In [7]: a
Out[7]: 1
对于表来说,表传递给函数的是一个指针,指针指向序列在内存中的位置,在函数中对表的操作将在原有内存中进行,从而影响原有变量。 (我们称此为指针传递)
In [1]: b = [1,2,3]
...: def change_list(b):
...: b[0] = b[0] + 1
...: return b
...:
In [2]: print change_list(b)
[2, 2, 3]
In [3]: b
Out[3]: [2, 2, 3]
注意:元组是不可变的,但是元组里面子元素如果含有列表,则列表子元素又是可变的。
>>> t = (1,2,[3,4], 5)
>>> id(t)
48192144
>>> t[2].append(10)
>>> t
(1, 2, [3, 4, 10], 5)
>>> id(t)
48192144