Python学习笔记
学习笔记——Python
Python学习记录(Python 2.x)
Everything is Object
python里的所有东西都是对象,除了关键字(Keywords)如print
Iterators
迭代器在处理for循环中非常常见,但盲目使用时容易出错
# python iterators
for i in xrange(n):pass
for i in dict_obj.iterkeys():pass
for v in dict_obj.itervalues():pass
for index,value in enumerate(seq_obj):pass
for v1,v2 in zip(seq1,seq2): pass
for line in open("bigfile.txt","rt"):pass
Iterator-Protocal
迭代器协议:
- iter()
- next()
- Exception of StopIteration
如写for循环时,迭代器协议实现是这样的:
# python code
for v in lst:
print v
# python code in iteration protocal
it = iter(lst)
try:
while True:
v = it.next()
print v
except StopIteration:
pass
Iterator-yield
迭代器-yield
def foo()
lst = [1,2,3]
for v in lst:
yield v
g = foo()
print g.next() #1
print g.next() #2
print g.next() #3
print g.next() #Error
print foo().next() #1
print foo().next() #1
print foo().next() #1
print foo().next() #1
print foo() #<generator object at XXXXXXX>同一个内存地址
print foo() #<generator object at XXXXXXX>同一个内存地址
print foo() #<generator object at XXXXXXX>同一个内存地址
print foo() #<generator object at XXXXXXX>同一个内存地址
g就是一个生成器,通过调用next方法可以迭代
print g.next() 第四次的时候会抛出异常,因为python的对象生命周期是靠引用计数来管理的,当执行到第三次时,对象就被释放掉了,所以第四次执行的时候回抛出异常。
而如果通过print foo().next()则会打印出四个1,因为每次调用foo()生成的是同一个迭代器
why?
python的内存机制,也就是Python对象的生命周期决定的。
Python靠引用计数来管理对象的生命周期,如果对象没有引用则会立即销毁
所以foo()函数返回的这个generator,如果没有人引用,那么在完成了foo().next()之后就会销毁
而再次调用foo().next()时,将会给新返回的generator分配内存,由于内存管理机制,将极大概率在上次内存地址的地方新建generator。所以虽然是在同一个内存地址,然而generator却是全新的。
Iterator-Closure
迭代器闭包
闭包-包含局部状态的函数
class Foo(object):
def __init__(self):
self._list = [1,2,3,4,5]
def __iter__(self):
counter = 0
def get():
if(counter >= len(self._list)):
return None
res = self._list
counter +=1
return res
return iter(get,None) # see tips
a = Foo()
for a1 in a:
for b1 in a:
print [a1,b1] # UnboundLocalError: local variable 'counter' referenced before assignment
tips:
如果iter有两个参数的话,那么第一个必须是可调用的对象(如函数)
这种情况下创建的迭代器在调用next时将会调用这个可调用对象并返回它的值
如果得到的第一个可调用对象返回值与第二个参数相等,则会抛出StopIteration的异常
Why?
为什么会出现UnboundLocalError呢?
根据namespace的搜索顺序,counter的搜索顺序首先是在get()函数里找的。然而之前在扫描代码,建立搜索对象时,由于函数内部有count+=1的操作,会创造一个空的counter引用在get()函数的命名空间里(但不是正确的映射)。根据搜索顺序,一旦在get()里找到,就不会再往parent-local namespace里找。
Namespace vs. Object-space
命名空间和对象空间
// in C/C++
int a = 10
int b = a
在C/C++中,a有两层含义
- 在编译时,a是一个名称“a” 仅编译期间
- a也是一个Int类型的对象
而变量b的含义是:b和a有两个相同的值,但是两个不同的对象
# in Python
a = 10
b = a
在Python中
- a是一个名称,存在命名空间中 运行时都有意义
- 10是一个整数类型的对象,在对象空间中
- 在执行语句
a = 10时,实际上是把命名空间的a和对象空间中的10建立了一个引用关系 Reference - b也是一个名称,存在命名空间中,
b=a指的是b与a引用了同一个对象
Namespace
namespace实际是从名字到对象的一个映射,许多命名空间都是以dict字典形式实现的
- function:有自己的namespace ——local namespace
- module: 有自己的namespace ——global namespace
"__buildin__"module:模块的global namespace——build-in namespace
**
搜索顺序:
local -> parent local -> ...-> global -> build-in
注意:进行write操作时将打断外层搜索顺序的过程
Reflection
python的反射
dir(obj)返回一系列的名字,可以通过obj.xxx获取obj.__dict__返回一些列的(name,object)对,可以通过object的local namespace获取
class A(object):
def fox(self):
print "fox"
def dog(self):
print "dog"
def cat(self):
print "cat"
def duck(self):
print"duck"
a1 = A()
a2 = A()
a1.fox() #fox
a2.fox() #fox
A.fox = dog
a1.fox() #dog
a2.fox() #dog
import new
a2.fox = new.instancemethod(cat,a2,A)
a1.fox() #dog
a2.fox() #cat
print dir(a1) #['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fox']
print dir(a2) #['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'fox']
print a1.__dict__ #{}
print a2.__dict__ #{'fox': <bound method A.cat of <__main__.A object at 0x00000000038EEC18>>}
A.fox = duck
a1.fox() #duck
a2.fox() #cat
print a1.__dict__ #{}
print a2.__dict__ #{'fox': <bound method A.cat of <__main__.A object at 0x00000000038EEC18>>}
del a2.__dict__['fox']
a1.fox() #duck
a2.fox() #duck
可以看到,如果通过dir()方法,a1和a2得到的结果,即可以访问的名字都是一致的,没有区别。而通过obj.__dict__查看命名空间的话,则不一样,a1的local namespace是空的,而a2的local namespace中fox()的bound method为cat,因此如果再次执行A.fox = duck操作,a1的输出将变化,而a2的输出仍为cat。一旦执行del a2.__dict__['fox'],将删除掉bound method,因此a2.fox的输出变为了duck。
Object Lifecycle
对象的生命周期管理:引用计数Reference Count
- 当一个对象创建的时候,引用计数为0
- 当一个名字引用了这个对象的时候,对象的引用计数+1
- 当不再引用时,引用计数-1
- 一旦对象的引用计数减到0时,对象被释放(release)
python的GC(垃圾回收)
python的垃圾回收只处理循环引用(a引用b,b又引用a),这时候如果把a的外部引用都dereference了,但是a和b的引用没有办法解掉,因为是循环引用,引用计数不为0。这时候引入了垃圾回收机制,去内存中搜索扫描这种不被外部引用同时内部循环引用的情况,然后拆环释放掉。
Mutable vs. immutable
可变对象和非可变对象
- mutable object:int,float,str,tuple,frozenset
- immutable object:list,dict,set,custom type
def foo(a,items= [],added = True):
if added:
items.append(a)
print items
foo(1) #[1]
foo(2) #[1,2]
foo(3,added=False) #[1,2]
因为函数foo()的参数items属于list是可变对象,所以会随着函数的执行而发生改变
尽量避免在函数参数中使用可变对象
如何避免此种情况?使用非可变对象None
def foo(a,items= None,added = True):
if added:
if items is None:
items=[]
items.append(a)
print items
foo(1) #[1]
foo(2) #[2]
foo(3,added=False) #None
== vs. is
- ==的语义:两个对象如果值相等,则成立。
- is的语义:两个对象引用的对象是同一个对象,则成立。且
id(a)==id(b),对象地址一样。
举很多例子:
a = "abcdefgh"
b = ''.join([chr(ord('a')+i) for i in xrange(8)])
print a == b #True
print a is b #False
a = "a"
b = "abcdefgh"[0]
print a == b #True
print a is b #True
#-------------------------why?
def foo():pass
a = None
b = foo()
print a == b #True
print a is b #True
a = True
b = 1==1
print a == b #True
print a is b #True
a = 2
b = 1+1
print a == b #True
print a is b #True
a = 10**9 #10的9次方
b = 10**8*10 #10的9次方
print a == b #True
print a is b #False
#-------------------------why?
a = [1,2]
b = [1]
b.append(2)
print a == b #True
print a is b #False
why?上述代码为什么会出现不同的情况?
是因为python加入了小对象池管理对象。有一些类型,比如int和str类型每次去访问的时候,访问的都是小整数对象池里的同一个对象。因此2和1+1是相同的对象。而10的9次方则每次生成了新的对象,因此is关系不成立。
另外,python中的NoneType只有一个实例对象None,所以每次判断None的时候都是都一个对象;
而bool类型也只有两个实例,True/False;
Semantic of import
import的语义
import m
- 检查m是否在sys.modules
- 若存在,将m加入当前的namespace里,并让它引用到sys.modules["m"]
- 若不存在,load module m,并创建module对象
load module的环节:
- 打开m对应的载体
- 创建一个空的module对象m,把它加到sys.module中去
- 在空的module的namespace里,顺序执行所有语句
建议
- 不要在module的namespace里做大量操作(python的性能问题)
- 注意区分python的import和C/C++中的include
- 不要把所有的import语句直接写在module的最前面(脚本量大时import耗时大)
from/import的语义
from m import b
- 检查m是否在sys.modules
- 搜索b是否在当前module的namespace内
- 若存在,将b加入当前的namespace里,并建立起对象的映射关系
- 若不存在,报出异常
#file test
MaxHp = 100
a = [1, 2]
from test import MaxHp, a
import test
print MaxHp #100
print test.MaxHp #100
print a #[1,2]
print test.a #[1,2]
test.MaxHp = 200
a = [1, 2, 3]
print MaxHp #100
print test.MaxHp #200
print a #[1,2,3]
print test.a #[1,2]
why?
取决于名称的映射关系是否发生改变
建议:不要from xx import 变量,尤其是需要修改的变量
另外,不要用 from xx import *,会将整个module里的所有名字都写到当前的namespace里造成严重污染
Summary
- Python is not C/C++
C/C++是基于值拷贝的,而Python是基于引用关系的
C/C++的对象的生命周期是基于作用域的,而Python是基于引用计数的
C/C++的include是执行期的指令,而Python的import则是运行期的 - Everything is Object
- Pythonic way of coding

浙公网安备 33010602011771号