001-有了列表这种数据结构,为什么还需要元组这样的类型呢?

  • 元组中的元素是无法修改的,事实上我们在项目中尤其是多线程环境(后面会讲到)中可能更喜欢使用的是那些不变对象(一方面因为对象状态不能修改,所以可以避免由此引起的不必要的程序错误,简单的说就是一个不变的对象要比可变的对象更加容易维护;另一方面因为没有任何一个线程能够修改不变对象的内部状态,一个不变对象自动就是线程安全的,这样就可以省掉处理同步化的开销。一个不变对象可以方便的被共享访问)。所以结论就是:如果不需要对元素进行添加、删除、修改的时候,可以考虑使用元组,当然如果一个方法要返回多个值,使用元组也是不错的选择。
  • 元组在创建时间和占用的空间上面都优于列表。我们可以使用sys模块的getsizeof函数来检查存储同样的元素的元组和列表各自占用了多少内存空间,这个很容易做到。我们也可以在ipython中使用魔法指令%timeit来分析创建同样内容的元组和列表所花费的时间。

    002-整数比较的坑

    在 Python 中一切都是对象,整数也是对象,在比较两个整数时有两个运算符==is,它们的区别是:

  • is比较的是两个整数对象的id值是否相等,也就是比较两个引用是否代表了内存中同一个地址。

  • ==比较的是两个整数对象的内容是否相等,使用==时其实是调用了对象的__eq__()方法。

知道了is==的区别之后,我们可以来看看下面的代码,了解Python中整数比较有哪些坑,以CPython解释器为例,大家先看看下面的代码。

  1. def main():
  2. x = y = -1
  3. while True:
  4. x += 1
  5. y += 1
  6. if x is y:
  7. print('%d is %d' % (x, y))
  8. else:
  9. print('Attention! %d is not %d' % (x, y))
  10. break
  11. x = y = 0
  12. while True:
  13. x -= 1
  14. y -= 1
  15. if x is y:
  16. print('%d is %d' % (x, y))
  17. else:
  18. print('Attention! %d is not %d' % (x, y))
  19. break
  20. if __name__ == '__main__':
  21. main()

因为CPython出于性能优化的考虑,把频繁使用的整数对象用一个叫small_ints的对象池缓存起来造成的。small_ints缓存的整数值被设定为[-5, 256]这个区间,也就是说,如果使用CPython解释器,在任何引用这些整数的地方,都不需要重新创建int对象,而是直接引用缓存池中的对象。如果整数不在该范围内,那么即便两个整数的值相同,它们也是不同的对象。

003-args 和 *kwargs

后面的参数并不重要,重要的是一*和两个**的区别和作用,主要用于函数不定参数的定义。**args是用来发送一个非键值对的可变数量的参数列表给一个函数。

  1. def test_var_args(f_arg, *args):
  2. print("first normal arg:", f_arg)
  3. for arg in args:
  4. print("another arg through *args:", arg)
  5. # 测试
  6. test_var_args('php', 'java', 'python', 'c++')
  7. # output
  8. # first normal arg: php
  9. # another arg through *args: java
  10. # another arg through *args: python
  11. # another arg through *args: c++

*args 允许使用不定长度的键值对,作为参数传递给函数。

  1. def greet_me(**args):
  2. for key, value in args.items():
  3. print("{0}=={1}".format(key, value))
  4. # 测试
  5. greet_me(name="waldeincheng")
  6. # output
  7. # name==waldeincheng

标准参数与*args**kwargs在使⽤时的顺序 那么如果你想在函数⾥同时使⽤所有这三种参数, 顺序是这样的:

  1. some_func(fargs, *args, kwargs)

004-调试Debug

使用任何开发语言中,也在实际工作中,调试都是必不可少的一步。
从命令行进入

  1. python -m pdb my_script.py

从脚本内部运⾏

  1. import pdb
  2. def make_bread():
  3. pdb.set_trace() # 从此处断点
  4. return "I don't have time"
  5. print(make_bread())

debugger模式下的常用命令:

  • c: 继续执⾏ -
  • w: 显⽰当前正在执⾏的代码⾏的上下⽂信息 -
  • a: 打印当前函数的参数列表 s: 执⾏当前代码⾏,并停在第⼀个能停的地⽅(相当于单步进⼊)
  • n: 继续执⾏到当前函数的下⼀⾏,或者当前⾏直接返回(单步跳过)

单步跳过(next)和单步进⼊(step)的区别在于, 单步进⼊会进⼊当前⾏调⽤的函数内 部并停在⾥⾯, ⽽单步跳过会(⼏乎)全速执⾏完当前⾏调⽤的函数,并停在当前函数的 下⼀⾏。

005-read,readline和readlines

  • read 读取整个文件
  • readline 读取下一行,使用生成器方法
  • readlines 读取整个文件到一个迭代器以供我们遍

    006-自省机制

    自省就是面向对象的语言所写的程序在运行时,所能知道对象的类型.简单一句就是运行时能够获得对象的类型.比如type(),dir(),getattr(),hasattr(),isinstance().
    1. a = [1,2,3]
    2. b = {'a':1,'b':2,'c':3}
    3. c = True
    4. print type(a),type(b),type(c) # <type 'list'> <type 'dict'> <type 'bool'>
    5. print isinstance(a,list) # True

    007-参数引用

    看两个例子:
  1. a = 1
  2. def fun(a):
  3. a = 2
  4. fun(a)
  5. print a # 1
  1. a = []
  2. def fun(a):
  3. a.append(1)
  4. fun(a)
  5. print a # [1]

所有的变量都可以理解是内存中一个对象的“引用”,或者,也可以看似c中void*的感觉。
通过id来看引用a的内存地址可以比较理解:

  1. a = 1
  2. def fun(a):
  3. print "func_in",id(a) # func_in 41322472
  4. a = 2
  5. print "re-point",id(a), id(2) # re-point 41322448 41322448
  6. print "func_out",id(a), id(1) # func_out 41322472 41322472
  7. fun(a)
  8. print a # 1

注:具体的值在不同电脑上运行时可能不同。
可以看到,在执行完a = 2之后,a引用中保存的值,即内存地址发生变化,由原来1对象的所在的地址变成了2这个实体对象的内存地址。
而第2个例子a引用保存的内存值就不会发生变化:

  1. a = []
  2. def fun(a):
  3. print "func_in",id(a) # func_in 53629256
  4. a.append(1)
  5. print "func_out",id(a) # func_out 53629256
  6. fun(a)
  7. print a # [1]

这里记住的是类型是属于对象的,而不是变量。而对象有两种,“可更改”(mutable)与“不可更改”(immutable)对象。在python中,strings, tuples, 和numbers是不可更改的对象,而 list, dict, set 等则是可以修改的对象。(这就是这个问题的重点)
当一个引用传递给函数的时候,函数自动复制一份引用,这个函数里的引用和外边的引用没有半毛关系了.所以第一个例子里函数把引用指向了一个不可变对象,当函数返回的时候,外面的引用没半毛感觉.而第二个例子就不一样了,函数内的引用指向的是可变对象,对它的操作就和定位了指针地址一样,在内存里进行修改.

008-@staticmethod和@classmethod

Python其实有3个方法,即静态方法(staticmethod),类方法(classmethod)和实例方法,如下:

  1. def foo(x):
  2. print "executing foo(%s)"%(x)
  3. class A(object):
  4. def foo(self,x):
  5. print "executing foo(%s,%s)"%(self,x)
  6. @classmethod
  7. def class_foo(cls,x):
  8. print "executing class_foo(%s,%s)"%(cls,x)
  9. @staticmethod
  10. def static_foo(x):
  11. print "executing static_foo(%s)"%x
  12. a=A()

这里先理解下函数参数里面的self和cls.这个self和cls是对类或者实例的绑定,对于一般的函数来说我们可以这么调用foo(x),这个函数就是最常用的,它的工作跟任何东西(类,实例)无关.对于实例方法,我们知道在类里每次定义方法的时候都需要绑定这个实例,就是foo(self, x),为什么要这么做呢?因为实例方法的调用离不开实例,我们需要把实例自己传给函数,调用的时候是这样的a.foo(x)(其实是foo(a, x)).类方法一样,只不过它传递的是类而不是实例,A.class_foo(x).注意这里的self和cls可以替换别的参数,但是python的约定是这俩,还是不要改的好.
对于静态方法其实和普通的方法一样,不需要对谁进行绑定,唯一的区别是调用的时候需要使用a.static_foo(x)或者A.static_foo(x)来调用.

\ 实例方法 类方法 静态方法
a = A() a.foo(x) a.class_foo(x) a.static_foo(x)
A 不可用 A.class_foo(x) A.static_foo(x)

009-类变量和实例变量

类变量:

是可在类的所有实例之间共享的值(也就是说,它们不是单独分配给每个实例的)。例如下例中,num_of_instance 就是类变量,用于跟踪存在着多少个Test 的实例。

实例变量:

实例化之后,每个实例单独拥有的变量。

  1. class Test(object):
  2. num_of_instance = 0
  3. def __init__(self, name):
  4. self.name = name
  5. Test.num_of_instance += 1
  6. if __name__ == '__main__':
  7. print Test.num_of_instance # 0
  8. t1 = Test('jack')
  9. print Test.num_of_instance # 1
  10. t2 = Test('lucy')
  11. print t1.name , t1.num_of_instance # jack 2
  12. print t2.name , t2.num_of_instance # lucy 2

补充的例子

  1. class Person:
  2. name="aaa"
  3. p1=Person()
  4. p2=Person()
  5. p1.name="bbb"
  6. print p1.name # bbb
  7. print p2.name # aaa
  8. print Person.name # aaa

这里p1.name="bbb"是实例调用了类变量,这其实和上面第一个问题一样,就是函数传参的问题,p1.name一开始是指向的类变量name="aaa",但是在实例的作用域里把类变量的引用改变了,就变成了一个实例变量,self.name不再引用Person的类变量name了.
可以看看下面的例子:

  1. class Person:
  2. name=[]
  3. p1=Person()
  4. p2=Person()
  5. p1.name.append(1)
  6. print p1.name # [1]
  7. print p2.name # [1]
  8. print Person.name # [1]

010-Python中单下划线和双下划线

  1. >>> class MyClass():
  2. ... def __init__(self):
  3. ... self.__superprivate = "Hello"
  4. ... self._semiprivate = ", world!"
  5. ...
  6. >>> mc = MyClass()
  7. >>> print mc.__superprivate
  8. Traceback (most recent call last):
  9. File "<stdin>", line 1, in <module>
  10. AttributeError: myClass instance has no attribute '__superprivate'
  11. >>> print mc._semiprivate
  12. , world!
  13. >>> print mc.__dict__
  14. {'_MyClass__superprivate': 'Hello', '_semiprivate': ', world!'}

__foo__:一种约定,Python内部的名字,用来区别其他用户自定义的命名,以防冲突,就是例如__init__(),__del__(),__call__()这些特殊方法
_foo:一种约定,用来指定变量私有.程序员用来指定私有变量的一种方式.不能用from module import * 导入,其他方面和公有一样访问;
__foo:这个有真正的意义:解析器用_classname__foo来代替这个名字,以区别和其他类相同的命名,它无法直接像公有成员一样随便访问,通过对象名._类名__xxx这样的方式可以访问.

012-字符串格式化:%和.format(ToDo)

//TODO

013-迭代器和生成器(Todo)

这个是stackoverflow里python排名第一的问题,值得一看: http://stackoverflow.com/questions/231767/what-does-the-yield-keyword-do-in-python
这是中文版: http://taizilongxu.gitbooks.io/stackoverflow-about-python/content/1/README.html
这里有个关于生成器的创建问题面试官有考:
问: 将列表生成式中[]改成() 之后数据结构是否改变?
答案:是,从列表变为生成器

  1. >>> L = [x*x for x in range(10)]
  2. >>> L
  3. [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
  4. >>> g = (x*x for x in range(10))
  5. >>> g
  6. <generator object <genexpr> at 0x0000028F8B774200>

通过列表生成式,可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含百万元素的列表,不仅是占用很大的内存空间,如:我们只需要访问前面的几个元素,后面大部分元素所占的空间都是浪费的。因此,没有必要创建完整的列表(节省大量内存空间)。在Python中,我们可以采用生成器:边循环,边计算的机制—>generator

014-Python垃圾回收机制

Python GC主要使用引用计数(reference counting)来跟踪和回收垃圾。在引用计数的基础上,通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用问题,通过“分代回收”(generation collection)以空间换时间的方法提高垃圾回收效率。

1 引用计数

PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少.引用计数为0时,该对象生命就结束了。
优点:

  1. 简单
  2. 实时性

缺点:

  1. 维护引用计数消耗资源
  2. 循环引用

    2 标记-清除机制

    基本思路是先按需分配,等到没有空闲内存的时候从寄存器和程序栈上的引用出发,遍历以对象为节点、以引用为边构成的图,把所有可以访问到的对象打上标记,然后清扫一遍内存空间,把所有没标记的对象释放。

    3 分代技术

    分代回收的整体思想是:将系统中的所有内存块根据其存活时间划分为不同的集合,每个集合就成为一个“代”,垃圾收集频率随着“代”的存活时间的增大而减小,存活时间通常利用经过几次垃圾回收来度量。
    Python默认定义了三代对象集合,索引数越大,对象存活时间越长。
    举例:
    当某些内存块M经过了3次垃圾收集的清洗之后还存活时,我们就将内存块M划到一个集合A中去,而新分配的内存都划分到集合B中去。当垃圾收集开始工作时,大多数情况都只对集合B进行垃圾回收,而对集合A进行垃圾回收要隔相当长一段时间后才进行,这就使得垃圾收集机制需要处理的内存少了,效率自然就提高了。在这个过程中,集合B中的某些内存块由于存活时间长而会被转移到集合A中,当然,集合A中实际上也存在一些垃圾,这些垃圾的回收会因为这种分代的机制而被延迟。

    015-Cookie和Session的区别

    | | Cookie | Session | | —- | —- | —- | | 储存位置 | 客户端 | 服务器端 | | 目的 | 跟踪会话,也可以保存用户偏好设置或者保存用户名密码等 | 跟踪会话 | | 安全性 | 不安全 | 安全 |

session技术是要使用到cookie的,之所以出现session技术,主要是为了安全。