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

迭代器协议:

  1. iter()
  2. next()
  3. 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有两层含义

  1. 在编译时,a是一个名称“a” 仅编译期间
  2. a也是一个Int类型的对象
    而变量b的含义是:b和a有两个相同的值,但是两个不同的对象
# in Python
a = 10
b = a

在Python中

  1. a是一个名称,存在命名空间中 运行时都有意义
  2. 10是一个整数类型的对象,在对象空间中
  3. 在执行语句a = 10时,实际上是把命名空间的a和对象空间中的10建立了一个引用关系 Reference
  4. b也是一个名称,存在命名空间中,b=a指的是b与a引用了同一个对象

Namespace

namespace实际是从名字到对象的一个映射,许多命名空间都是以dict字典形式实现的

  1. function:有自己的namespace ——local namespace
  2. module: 有自己的namespace ——global namespace
  3. "__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

  1. 检查m是否在sys.modules
  2. 若存在,将m加入当前的namespace里,并让它引用到sys.modules["m"]
  3. 若不存在,load module m,并创建module对象

load module的环节:

  1. 打开m对应的载体
  2. 创建一个空的module对象m,把它加到sys.module中去
  3. 在空的module的namespace里,顺序执行所有语句

建议

  1. 不要在module的namespace里做大量操作(python的性能问题)
  2. 注意区分python的import和C/C++中的include
  3. 不要把所有的import语句直接写在module的最前面(脚本量大时import耗时大)

from/import的语义

from m import b

  1. 检查m是否在sys.modules
  2. 搜索b是否在当前module的namespace内
  3. 若存在,将b加入当前的namespace里,并建立起对象的映射关系
  4. 若不存在,报出异常
#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
posted @ 2020-08-07 16:22  湘南  阅读(74)  评论(0)    收藏  举报