Python 典型错误及关键知识点

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wohu007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值