举例讲解Python中的迭代器、生成器与列表解析用法,python生成器

by admin on 2019年9月3日

举例讲解Python中装饰器的用法,python用法

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

>>> def now():
...   print '2013-12-25'
...
>>> f = now
>>> f()
2013-12-25

函数对象有一个__name__属性,可以拿到函数的名字:

>>> now.__name__
'now'
>>> f.__name__
'now'

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

def log(func):
  def wrapper(*args, **kw):
    print 'call %s():' % func.__name__
    return func(*args, **kw)
  return wrapper

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
  print '2013-12-25'

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2013-12-25

把@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args,
**kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:

def log(text):
  def decorator(func):
    def wrapper(*args, **kw):
      print '%s %s():' % (text, func.__name__)
      return func(*args, **kw)
    return wrapper
  return decorator

这个3层嵌套的decorator用法如下:

@log('execute')
def now():
  print '2013-12-25'

执行结果如下:

>>> now()
execute now():
2013-12-25

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('execute')(now)

我们来剖析上面的语句,首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数。

以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__等属性,但你去看经过decorator装饰之后的函数,它们的__name__已经从原来的’now’变成了’wrapper’:

>>> now.__name__
'wrapper'

因为返回的那个wrapper()函数名字就是’wrapper’,所以,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

不需要编写wrapper.__name__ =
func.__name__这样的代码,Python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
  @functools.wraps(func)
  def wrapper(*args, **kw):
    print 'call %s():' % func.__name__
    return func(*args, **kw)
  return wrapper

或者针对带参数的decorator:

import functools

def log(text):
  def decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
      print '%s %s():' % (text, func.__name__)
      return func(*args, **kw)
    return wrapper
  return decorator

import
functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。
小结

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

请编写一个decorator,能在函数调用的前后打印出’begin call’和’end
call’的日志。

再思考一下能否写出一个@log的decorator,使它既支持:

@log
def f():
  pass

又支持:

@log('execute')
def f():
  pass

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
d…

举例讲解Python中is和id的用法,pythonisid用法

(ob1 is ob2) 等价于 (id(ob1) == id(ob2))

首先id函数可以获得对象的内存地址,如果两个对象的内存地址是一样的,那么这两个对象肯定是一个对象。和is是等价的。Python源代码为证。
 

static PyObject *
 cmp_outcome(int op, register PyObject *v, register PyObject *w)
{
 int res = 0;
 switch (op) {
 case PyCmp_IS:
 res = (v == w);
 break;
 case PyCmp_IS_NOT:
res = (v != w);
 break;

但是请看下边代码的这种情况怎么会出现呢?

In [1]: def bar(self, x):
...:   return self.x + y
...:

In [2]: class Foo(object):
...:   x = 9
...:   def __init__(self ,x):
...:     self.x = x
...:   bar = bar
...:  

In [3]: foo = Foo(5)

In [4]: foo.bar is Foo.bar
Out[4]: False

In [5]: id(foo.bar) == id(Foo.bar)
Out[5]: True

两个对象用is判断是False,用id判断却是True,这与我们已知的事实不符啊,这种现象该如何解释呢?遇到这种情况最好的解决方法就是调用dis模块去看下两个比较语句到底做了什么。

In [7]: dis.dis("id(foo.bar) == id(Foo.bar)")
     0 BUILD_MAP    10340
     3 BUILD_TUPLE   28527
     6 <46>     
     7 DELETE_GLOBAL  29281 (29281)
     10 STORE_SLICE+1 
     11 SLICE+2    
     12 DELETE_SUBSCR 
     13 DELETE_SUBSCR 
     14 SLICE+2    
     15 BUILD_MAP    10340
     18 PRINT_EXPR  
     19 JUMP_IF_FALSE_OR_POP 11887
     22 DELETE_GLOBAL  29281 (29281)
     25 STORE_SLICE+1 

In [8]: dis.dis("foo.bar is Foo.bar")
     0 BUILD_TUPLE   28527
     3 <46>     
     4 DELETE_GLOBAL  29281 (29281)
     7 SLICE+2    
     8 BUILD_MAP    8307
     11 PRINT_EXPR  
     12 JUMP_IF_FALSE_OR_POP 11887
     15 DELETE_GLOBAL  29281 (29281)

真实情况是当执行.操作符的时候,实际是生成了一个proxy对象,foo.bar is

举例讲解Python中的迭代器、生成器与列表解析用法,python生成器

迭代器:初探

上一章曾经提到过,其实for循环是可用于任何可迭代的对象上的。实际上,对Python中所有会从左至右扫描对象的迭代工具而言都是如此,这些迭代工具包括了for循环、列表解析、in成员关系测试以及map内置函数等。

“可迭代对象”的概念在Python中是相当新颖的,基本这就是序列观念的通用化:如果对象时实际保存的序列,或者可以再迭代工具环境中一次产生一个结果的对象,那就看做是可迭代的。

>>文件迭代器
作为内置数据类型的文件也是可迭代的,它有一个名为__next__的方法,每次调用时,就会返回文件中的下一行。当到达文件末尾时,__next__会引发内置的StopIteration异常,而不是返回空字符串。

这个接口就是Python中所谓的迭代协议:有__next__方法的对象会前进到下一个结果,而在一系列结果的末尾时,则会引发StopIteration。任何这类对象都认为是可迭代的。任何这类对象也能以for循环或其他迭代工具遍历,因为所有迭代工具内部工作起来都是在每次迭代中调用__next__,并且捕捉StopIteratin异常来确定何时离开。

for line in open('script.py'):
 print(line.upper(),end='')

上面的代码就是文件迭代的一个例子,并且这种用法是最高效的文件读取方法,主要有三个优点:这是最简单的写法,运行快,并且从内存使用情况来说也是最好的。

替代的写法是:

for line in open('script.py').readlines():
 print(line.upper(),end='')

这种调用方法会把文件一次性读到内存中,如果文件太大,那么内存会被消耗光的。

>>手动迭代:iter和next
为了支持手动迭代代码(用较少的录入),Python3.0还提供了一个内置函数next,它会自动调用一个对象的__next__方法。给定一个对象X,调用next(X)等同于X.__next__(),但前者简单很多。

从技术角度来讲,迭代协议还有一点值得注意。当for循环开始时,会通过它传给iter内置函数,以便从可迭代对象中获得一个迭代器,返回的对象含有需要的next方法。调用iter的步骤对于文件来说不是必须的,因为文件对象就是自己的迭代器,但是对于其他的一些内置数据类型来说,就不一定了。

列表以及很多其他的内置对象,不是自身的迭代器,因为它们支持多次打开迭代器。对这样的对象,我们必须调用iter来启动迭代:

L=[1,2,3]
iter(L) is L #return false
L.__next__() #会报错

I = iter(L)
I.__next__()
I.__next__()

虽然Python迭代工具自动调用这些(iter,__next__)函数,我们也可以使用它们来手动地应用迭代协议。

列表解析:初探

>>列表解析基础知识

L=[1,2,3,4,5]
L = [x+10 for x in L]

列表解析写在一个方括号中,因为它们最终是构建一个新的列表的一种方式。它们以我们所组成的一个任意的表达式开始,该表达式使用我们所组成的一个循环变量(x+10)。这后面跟着我们现在应该看作是一个for循环头部的部分,它申明了循环变量,以及一个可迭代对象(for
x in L)

要运行该表达式,Python在解释器内部执行一个遍历L的迭代,按照顺序把x赋给每个元素,并且收集对各元素运行左边的表达式的结果。我们得到的结果列表就是列表解析所表达的内容——针对L中的每个x,包含了x+10的一个新列表。

其实列表解析式并不是必须的,因为它能完成的工作都能够通过for循环完成,但是列表解析式比手动的for循环语句运行得更快(往往速度快一倍),因为它们的迭代在解释器内部是以C语言的速度执行的,而不是以手动的Python代码执行的,特别是对于较大的数据集合,这是使用列表解析的一个主要的性能优点。

当我们考虑在一个序列中的每个项上执行一个操作时,都可以考虑使用列表解析。

>>扩展的列表解析语法
实际上,列表解析可以有更高级的应用。作为一个特别有用的扩展,表达式中嵌套的for循环可以有一个相关的if子句,来过滤那些测试不为真的结果项。

lines = [line.rstrip() for line in open('script.py') if line[0]='p']

这条if子句检查从文件读取的每一行,看它的第一个字符是否是p;如果不是,从结果列表中省略改行。

事实上,如果我们愿意的话,列表解析可以变得更加复杂——它们的完整语法允许任意数目的for子句,每个子句有一个可选的相关的if子句。

Python3.0中的新的可迭代对象

Pyton3.0中的一个基本的改变是,它比Python2.x更强调迭代。除了与文件和字典这样的内置类型相关的迭代,字典方法keys、values和items都在Python3.0中返回可迭代对象。
返回一个可迭代对象而不是返回一个结果列表的好处在于节省了内存的空间。

>>多个迭代器VS单个迭代器
多个迭代器:在它们的结果中能保持不同位置的多个迭代器
单个迭代器:只能保持一个迭代器,在遍历其结果之后,它们就用尽了。
通常通过针对iter调用返回一个新的对象,来支持多个迭代器;单个迭代器一般意味着一个对象返回其自身。

>>字典视图迭代器
在Python3.0中,字典的keys、values和items方法返回可迭代的视图对象,它们一次产生一个结果项,而不是在内存中一次产生全部结果列表。视图项保持和字典中的那些项相同的物理顺序,并且反映对底层的字典做出的修改。

和所有迭代器一样,我们总可以通过把一个Python3.0字典视图传递到list内置函数中,从而强制构建一个真正的列表。然而,这通常不是必须的。

此外,Python3.0字典仍然有自己的迭代器,它返回连续的键。因此,无需直接在此环境中调用keys:

for key in D:print(key,end=”)

>>列表解析与map
列表解析在一个序列上的值应用一个任意表达式,将其结果搜集到一个新的列表中并返回。从语法上说,列表解析是由方括号封装起来的(为了提醒你它们构造了一个列表)。它们的简单形式是在方括号中编写一个表达式,Python之后将这个表达式的应用循环中每次迭代的结果搜集起来。例如,假如我们希望搜集字符串中的所有字符的ASCII码,可以这样做:

#循环的方法
res=[]
for x in 'spam':
 res.append(ord(x))

#map函数的方法
res=list(map(ord,'spam'))

#列表解析
res=[ord(x) for x in 'spam']

>>增加测试和嵌套循环
其实,列表解析还要比上面说的通用的多,我们可以在for之后编写一个if分支,用来增加选择逻辑。

#列表解析

[x ** 2 for x in range(10) if x % 2 == 0]


#map
list(map((lambda x:x**2),filter((lambda x: x % 2==0),range(10))))

上述的两行代码都是搜集了0~9中的偶数的平方和,可以很明显的看到,完成同样的功能,列表解析的语句简单地多。

实际上,列表解析还能够更加通用。你可以在一个列表解析中编写任意数量的嵌套的for循环,并且每一个都有可选的关联的if测试。通用结构如下所示:

expression for target1 in iterable1 [if comdition1]
      for target2 in iterable2 [if condition2] ...
      for targetN in iterableN [if conditionN]

当for分句嵌套在列表解析中时,它们工作起来就像等效的嵌套的for循环语句。例如,如下的代码:

res=[x+y for x in [0,1,2] for y in [100,200,300]]

与下面如此冗长的代码有相同的效果:

res=[]
for x in [0,1,2]:
 for y in [100,200,30]:
  res.append(x+y)

>>列表解析和矩阵
使用Python编写矩阵的一个基本的方法就是使用嵌套的列表结构,例如,下面的代码定义了两个3×3的矩阵:

M=[[1,2,3],
  [4,5,6],
  [7,8,9]]

N=[[2,2,2],
  [3,3,3],
  [4,4,4]]

列表解析也是处理这样结构的强大工具,它们能够自动扫描行和列。

取出第二列的所有元素:

[row[1] for row in M]   #[2,5,8]

[M[row][1] for row in (0,1,2)]  #[2,5,8]

取出对角线上的元素:

[M[i][i] for i in range(len(M))] #[1,5,9]

混合多个矩阵,下面的代码创建了一个单层的列表,其中包含了矩阵对元素的乘积。

复制代码 代码如下:

[M[row][col] * N[row][col] for row in range(3) for col in
range(3)]   #[2,4,6,12,15,18,28,32,36]

下面的代码再复杂一点,构造一个嵌套的列表,其中的值与上面的一样:

复制代码 代码如下:

[[M[row][col] * N[row][col] for col in range(3)] for row in
range(3)]   #[[2,4,6],[12,15,18],[28,32,36]]

上面的最后一个比较难于理解,它等同于如下基于语句的代码:

res=[]
for row in range(3):
 tmp=[]
 for col in range(3):
  tmp.append(M[row][col]*N[row][col])
 res.append(tmp)

>>理解列表解析
基于对运行在当前Python版本下的测试,map调用比等效的for循环要快两倍,而列表解析往往比map调用要稍快一些。速度上的差距来自底层实现,map和列表解析是在解释器中以C语言的速度来运行的,比Python的for循环在PVM中步进要快得多。

重访迭代器:生成器

如今的Python对延迟提供更多的支持——它提供了工具在需要的时候才产生结果,而不是立即产生结果。特别地,有两种语言结构尽可能地拖延结果创建。

生成器函数:编写为常规的def语句,但是是使用yield语句一次返回一个结果,在每个结果之间挂起和继续它们的状态。
生成器表达式:生成器表达式类似于上一小节的列表解析,但是,它们返回按需产生结果的一个对象,而不是构建一个结果列表。
由于上面二者都不会一次性构建一个列表,它们节省了内存空间,并且允许计算时间分散到各个结果请求。

>>生成器函数:yield VS return
之前我们写的函数都是接受输入参数并立即返回单个结果的常规函数,然而,也有肯能来编写可以送回一个值并随后从其退出的地方继续的函数。这样的函数叫做生成器函数,因为它们随着时间产生值的一个序列。

一般来说,生成器函数和常规函数一样,并且,实际上也是用常规的def语句编写的。然后,当创建时,它们自动实现迭代协议,以便可以出现在迭代背景中。

状态挂起

和返回一个值并退出的函数不同,生成器函数自动在生成值的时刻挂起并继续函数的执行。因此,它们对于提前计算整个一系列值以及在类中手动保存和恢复状态都很有用。由于生成器函数在挂起时保存的状态包含了它们的整个本地作用域,当函数恢复时,它们的本地变量保持了信息并且使其可用。

生成器函和常规函数之间的主要不同之处在于,生成器yield一个值,而不是return一个值。yield语句挂起该函数并向调用者发送一个值,但是,保留足够的状态以使得函数从它离开的地方继续。当继续时,函数在上一个yield返回立即继续执行。从函数角度来看,这允许其代码随着时间产生一系列的值,而不是一次计算它们并在诸如列表的内容中送回它们。

迭代协议整合

可迭代对象定义了一个__next__方法,它要么返回迭代中的下一项,要么引发一个特殊的StopIteration异常来终止迭代。一个对象的迭代器用iter内置函数接受。

如果支持该协议的话,Python的for循环以及其他的迭代技术,使用这种迭代协议来便利一个序列或值生成器;如果不支持,跌打返回去重复索引序列。

要支持这一协议,函数包含一条yield语句,该语句特别编译为生成器。当调用时,它们返回一个迭代器对象,该对象支持用一个名为__next__的自动创建的方法来继续执行的接口。生成器函数也可能有一条return语句,总是在def语句块的末尾,直接终止值的生成。

生成器函数应用

def gensquares(N):
 for i in range(N):
  yield i ** 2

这个函数在每次循环时都会产生一个值,之后将其返回给它的调用者。当它被暂定后,它的上一个状态保存了下来 并且在yield语句之后把控制器马上回收。它允许函数避免临时再做所有的工作,当结果的列表很大或者在处理每一个结果都需要很多时间时,这一点尤其重要。生成器将在loop迭代中处理一系列值的时间分布开来。

扩展生成器函数协议:send和next
在Python2.5中,生成器函数协议中增加了一个send方法。send方法生成一系列结果的下一个元素,这一点像__next__方法一样,但是它提供了一种调用者与生成器之间进行通信的方法,从而能够影响它的操作。

def gen():
 for i in range(10):
  X =yield i
  print(X)

G = gen()
next(G)     #0
G.send(77)    #77 1
G.send(88)    #88 2
next(G)     #None 3

上面的代码比较难于理解,而且书上的翻译比较劣质,没看懂。在网上查了一些资料,结合自己的理解,上述代码的运行过程应该是这样的:生成了一个函数对象,赋值给了G,然后调用了next()
函数,生成了生成器的第一个值0,所以返回值是0。此时函数运行到yield语句,碰到yield语句后立即挂起函数,保存状态,等待下一次迭代。程序中之后又调用了send()方法,将77传递给了yield语句,yield语句将send()传递过来的值(此处是77)赋值给X,然后打印出来。然后函数继续运行,直到再次碰到yield,此时是第二次碰到yield,所以返回了1,接着函数又被挂起,等待下一次迭代。接着又调用了send(),同上次调用一样,将传进的参数(此处是88)作为yield的返回值赋值给X,然后打印,接着继续运行函数,直到再次碰到yield,此时是第三次,因此输出2。最后又再次调用了next()函数,其实’next()’函数就是传递了一个None,因此,我们得到的结果是None和3。

此处需要注意的是,其实next()和send(None)是等价的。通过send()方法,我们就能够和生成器实现通信。

>>生成器表达式:迭代器遇到列表解析
在最新版的Python中,迭代器和列表解析的概念形成了这种语言的一个新特性——生成器表达式。从语法上讲,生成器表达式就像一般的列表解析一样,但是它们是扩在圆括号中而不是方括号中的。

[x ** 2 for x in range(4)]  #List comprehension:build a list
(x ** 2 for x in range(4))  #Genterator expression:make an iterable

从执行过程上来讲,生成器表达式很不相同:不是在内存中构建结果,而是返回一个生成器对象,这个对象将会支持迭代协议。

生成器表达式大体上可以认为是对内存空间的优化,他们不需要像方括号的列表解析一样,一次构造出整个结果列表。它们在实际中运行起来可能稍慢一些,所以它们可能只对于非常庞大的结果集合的运算来说是最优的选择。

>>生成器函数VS生成器表达式
生成器函数和生成器表达式自身都是迭代器,并由此只支持一次活跃迭代,我们无法有在结果集中位于不同位置的多个迭代器。

Python3.0解析语法概括

我们已经在本章中关注过列表解析和生成器,但是,别忘了,还有两种在Python3.0中可用的解析表达式形式:集合解析和字典解析。

[x*x for x in range(10)]   #List comprehension:build list
(x*x for x in range(10))   #Generator expression:produces items
{x*x for x in range(10)}   #Set comprehension:new in 3.0
{x:x*x for x in range(10)  #Directionary comprehension:new in 3.0

需要注意的是,上面最后两种表达方式都是一次性构建所有对象的,它们没有根据需要产生结果的概念。

总结

列表解析、集合解析、字典解析都是一次性构建对象的,直接返回的。
生成器函数和生成器表达式不是在内存中构建结果的,它们是返回一个生成器对象,这个对象支持迭代协议,根据调用者需要产生结果。
集合解析、字典解析都支持嵌套相关的if子句从结果中过滤元素。
函数陷阱

>>本地变量是静态检测的
Python定义的在一个函数中进行分配的变量名时默认为本地变量的,它们存在于函数的作用域并只在函数运行时存在。Python静态检测Python的本地变量,当编译def代码时,不是通过发现赋值语句在运行时进行检测的。被赋值的变量名在函数内部是当做本地变量来对待的,而不是仅仅在赋值以后的语句才被当做是本地变量。

>>没有return语句的函数
在Python函数中,return(以及yield)语句是可选的。从技术上说,所有的函数都会返回了一个值,如果没有提供return语句,函数将自动返回None对象。

取值和赋值

Iterator
定义

Foo.bar的时候,两个对象顺序生成,放在栈里相比较,由于地址不同肯定是False,但是id(foo.bar)

id(Foo.bar)的时候就不同了,首先生成foo.bar,然后计算foo.bar的地址,计算完之后foo.bar的地址之后,就没有任何对象指向foo.bar了,所以foo.bar对象就会被释放。然后生成Foo.bar对象,由于foo.bar和Foo.bar所占用的内存大小是一样的,所以又恰好重用了原先foo.bar的内存地址,所以id(foo.bar)
== id(Foo.bar)的结果是True。

下面内容由邮件Leo Jay大牛提供,他解释的更加通透。

用id(expression a) == id(expression
b)来判断两个表达式的结果是不是同一个对象的想法是有问题的。

foo.bar 这种形式叫 attribute reference
[1],它是表达式的一种。foo是一个instance
object,bar是一个方法,这个时候表达式foo.bar返回的结果叫method object
[2]。根据文档:

    When an instance attribute is referenced that isn’t a data
attribute,
    its class is searched. If the name denotes a valid class attribute
    that is a function object, a method object is created by packing
    (pointers to) the instance object and the function object just
found
    together in an abstract object: this is the method object.

foo.bar本身并不是简单的名字,而是表达式的计算结果,是一个 method
object,在id(foo.bar)这样的表达式里,method
object只是一个临时的中间变量而已,对临时的中间变量做id是没有意义的。
一个更明显的例子是,
 

print id(foo.bar) == id(foo.__init__)

输出的结果也是True

看 id 的文档[3]:

    Return the “identity” of an object. This is an integer (or long
    integer) which is guaranteed to be unique and constant for this
object
    during its lifetime. Two objects with non-overlapping lifetimes
may
    have the same id() value.
    CPython implementation detail: This is the address of the object in
memory.

只有你能保证对象不会被销毁的前提下,你才能用 id
来比较两个对象。所以,如果你非要比的话,得这样写:
 

fb = foo.bar
Fb = Foo.bar
print id(fb) == id(Fb)

即把两个表达式的结果绑定到名字上,再来比是不是同一个对象,你才能得到正确的结果。

is表达式 [4] 也是一样的,你现在得到了正确的结果,完全是因为 CPython
现在的实现细节决定的。现在的is的实现,是左右两边的对象都计算出来,然后再比较这两个对象的地址是否一样。万一哪天改成了,先算左边,保存地址,把左边释放掉,再算右边,再比较的话,你的is的结果可能就错了。官方文档里也提到了这个问题
[5]。我认为正确的方法也是像id那样,先把左右两边都计算下来,并显式绑定到各自的名字上,然后再用is判断。

(ob1
is ob2) 等价于 (id(ob1) == id(ob2))
首先id函数可以获得对象的内存地址,如果两个对象的内存地址…

您可能感兴趣的文章:

  • python迭代器与生成器详解
  • Python使用设计模式中的责任链模式与迭代器模式的示例
  • Python的迭代器和生成器
  • Python中的列表生成式与生成器学习教程
  • python生成器表达式和列表解析
  • 浅谈Python中列表生成式和生成器的区别
  • 用Python生成器实现微线程编程的教程

迭代器:初探
上一章曾经提到过,其实for循环是可用于任何可迭代的对象上…

class Actress():
  def __init__(self):
    self.name = 'TianXin'
    self.age = 5

A Ruby iterator is simple a method that can invoke a block of code.

类Actress中有两个成员变量name和age。在外部对类的成员变量的操作,主要包括取值和赋值。简单的取值操作是x=object.var,简单的赋值操作是object.var=value。

  •         Block 一般是跟着 method 出现的, 并且 block
    中的代码不一定会执行
  •         如果 method 中有 yield, 那么它的block 中的代码会被执行
  •         Block 可以接收参数,和返回 value
>>> actress = Actress()
>>> actress.name  #取值操作
'TianXin'
>>> actress.age    #取值操作
20
>>> actress.name = 'NoName'   #赋值操作
>>> actress.name
'NoName'
def two_times
  yield
  yield
end
two_times { puts "Hello" }
# Hello
# Hello

def fib_up_to(max)
 i1, i2 = 1. 1
 while i1 <= max
   yield i1
   i1, i2 = i2, i1 + i2
 end
end

fib_up_to(1000) { |f| print f, " " }

# 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

使用 Getter 和 Setter 上述简单的取值和赋值操作,在某些情况下是不能满足要求的。比如,如果要限制Actress的年龄范围,那么只使用上述简单的赋值操作就不能满足要求了。getter和setter实现这样的要求。

    上面代码中的 yield 之后的 i1 会作为 parameter 传入到 block 中,
赋值给 block 的 argument f。
    Block 中可以有多个 arguments.

class Actress():
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  def getAge(self):
    return self._age

  def setAge(self, age):
    if age > 30:
      raise ValueError
    self._age = age

常见的 iterator
each

调用setAge函数可以实现将变量_age的取值范围限制到小于30.

each is probable the simplest iterator – all it does is yield successive
elements of its collection.

>>> actress = Actress()
>>> actress.setAge(28)
>>> actress.getAge()
28
>>> actress.setAge(35)
ValueError
[1, 3, 5, 7, 9].each { |i| puts i }

# 1 
# 3
# 5
# 7
# 9

使用property property的定义是:
其中,fget是取值函数,fset是赋值函数,fdel是删除函数。使用property也实现上述对成员变量的取值限制。

find

class Actress():
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  def getAge(self):
    return self._age

  def setAge(self, age):
    if age > 30:
      raise ValueError
    self._age = age 

  age=property(getAge, setAge, None, 'age property')

A blocl may also return a value to the method. The value of the last
expression evaluated in the block is passed back to the method as the
value of the yield.

经过上面的定义后,可以像简单取值和赋值操作一样操作age。比如,

class Array
 def find
  each do |value|
    return value if yield(value)
  end
 end
end

[1,3,4,7,9].find { |v| V*V > 30 } # => 7
>>> actress = Actress()
>>> actress.age
20
>>> actress.age = 18
>>> actress.age = 55

ValueError

collect (also known as map)

使用@property 使用@property同样可以实现上述类的定义。

Which takes each element from the collection and passes it to the block.
The results returned by the block are used to construct a new array

class Actress():
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  @property
  def age(self):
    return self._age

  @age.setter
  def age(self, age):
    if age > 30:
      raise ValueError
    self._age = age
["H", "A", "L"].collect { |x| x.succ } # => ["I", "B", "M"]

使用时的示例:

inject

>>> actress = Actress()
>>> actress.age
20
>>> actress.age = 18
>>> actress.age = 45
ValueError

The inject method lets you accumulate a value across the members of a
collection.

Python2 和 Python3中使用property的区别 上述property示例在Python3的环境下有效。在Python2中,使用property时,类定义时需要继承object。否则,property的赋值操作不可使用。

[1,3,5,7].inject { |sum, element| sum + element } # => 16

# sum = 1, element = 3
# sum = 4, element = 5
# sum = 9, element = 7
# sum = 16

[1,3,5,6].inject { |product, element| product*element } # => 105

Python2下property的正确使用方式:

If inject is called with no parameter, it uses the first element of the
collections as the initial value and starts the iteration with the
second value.

class Actress(object):      #差别在这里
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  @property
  def age(self):
    return self._age

  @age.setter
  def age(self, age):
    if age > 30:
      raise ValueError
    self._age = age 

  def setName(self, name):
    self._name = name

  def getName(self):
    return self._name

  def delName(self):
    print('Goodbye...')
    del self._name

  name = property(getName, setName, delName, 'name property'

)

上面代码的另一种简便写法:

实例:快速进行代码重构 从前,Python程序员Alice要打算创建一个代表金钱的类。她的第一个实现形式大概是下面这样:

[1,3,5,7].inject(:+) # => 16
[1,3,5,7]/inject(:*) # => 105
# 以美元为基础货币的Money类的首个版本
class Money:
  def __init__(self, dollars, cents):
    self.dollars = dollars
    self.cents = cents
    # 还有其他一些方法,我们暂时不必理会

Iterator 和 I/O 系统的交互

这个类后来被打包到一个Python库里,并且慢慢地被许多不同的应用使用。举个例子,另一个团队中的Python程序员Bob是这样使用Money类的:

Iterators 不仅仅能够访问 Array 和 Hash 中的数据, 和可以和 I/O 系统交互

money = Money(27, 12)
message = "I have {:d} dollars and {:d} cents."
print(message.format(money.dollars, money.cents))
# "I have 27 dollars and 12 cents."
money.dollars += 2
money.cents += 20
print(message.format(money.dollars, money.cents))
# "I have 29 dollars and 32 cents."
f = File.open("testfile")
f.each do |line|
 puts "The line is: #{line}"
end
f.close

这样使用并没有错,但是却出现了代码可维护性的问题。你发现了吗?

produces:
The line is: This is line one
The line is: This is line two
The line is: This is line three

几个月或是几年之后。Alice想要重构Money类的内部实现,不再记录美元和美分,而是仅仅记录美分,因为这样做可以让某些操作简单很多。下面是她很可能会作的修改:

您可能感兴趣的文章:

  • 在Ruby on Rails中使用Rails Active
    Resource的教程
  • 举例理解Ruby on
    Rails的页面缓存机制
  • 在Ruby on
    Rails中优化ActiveRecord的方法
# Money类的第二个版本
class Money:
  def __init__(self, dollars, cents):
    self.total_cents = dollars * 100 + cents

这一修改带来一个后果:引用Money类的每一行代码都必须要调整。有时候很幸运,你就是所有这些代码的维护者,只需要自己直接重构即可。但是Alice的情况就没有这么好了;许多团队都复用了她的代码。因此,她需要协调他们的代码库与自己的修改保持一致,也许甚至要经历一段特别痛苦、漫长的正式弃用过程(deprecation
process)。

幸运的是,Alice知道一种更好的解决办法,可以避免这个令人头疼的局面出现:使用Python内建的property装饰器。@property一般应用在Python方法上,可以有效地将属性访问(attribute
access)变成方法调用(method
call)。举个例子,暂时将Money类抛至一边,假设有一个代表人类的Person类(class):

class Person:
  def __init__(self, first, last):
    self.first = first
    self.last = last
  @property
  def full_name(self):
    return '{} {}'.format(self.first, self.last)

代码样式不同,是因为之前用的工具出问题了。—EarlGrey

请注意full_name方法。除了在def语句上方装饰了@property之外,该方法的声明没有什么不同的地方。但是,这却改变了Person对象的运作方式:

>>> buddy = Person('Jonathan', 'Doe')
>>> buddy.full_name
'Jonathan Doe'

我们发现,尽管full_name被定义为一个方法,但却可以通过变量属性的方式访问。在最后一行代码中没有()操作符;我并没有调用full_name方法。我们所做的,可以说是创建了某种动态属性。

回到本文中的Money类,Alice对它作了如下修改:

# Money类的最终版本
class Money:
  def __init__(self, dollars, cents):
    self.total_cents = dollars * 100 + cents
  # Getter and setter for dollars...
  @property
  def dollars(self):
    return self.total_cents // 100;
  @dollars.setter
  def dollars(self, new_dollars):
    self.total_cents = 100 * new_dollars + self.cents
    # And the getter and setter for cents.
  @property
  def cents(self):
    return self.total_cents % 100;
  @cents.setter
  def cents(self, new_cents):
    self.total_cents = 100 * self.dollars + new_cents

除了使用@property装饰器定义了dollars属性的getter外,Alice还利用@dollars.setter创建了一个setter。Alice还对cents`属性作了类似处理。

那么现在,Bob的代码要做哪些相应的修改呢?根本不用改!

# 他的代码完全没有变动,但是却可以正常调用Money类。
money = Money(27, 12)
message = "I have {:d} dollars and {:d} cents."
print(message.format(money.dollars, money.cents))
# "I have 27 dollars and 12 cents."
money.dollars += 2
money.cents += 20
print(message.format(money.dollars, money.cents))
# "I have 29 dollars and 32 cents."# 代码逻辑也没有问题。
money.cents += 112
print(message.format(money.dollars, money.cents))
# "I have 30 dollars and 44 cents."

事实上,所有使用了Money类的代码都不需要进行修改。Bob不知道或根本不在乎Alice去除了类中的dollars和cents属性:他的代码还是和以前一样正常执行。唯一修改过的代码就是Money类本身。

正是由于Python中处理装饰器的方式,你可以在类中自由使用简单的属性。如果你所写的类改变了管理状态的方法,你可以自信地通过@property装饰器对这个类(且只有这个类)进行修改。这是一个共赢的方法!相反,在Java等语言中,程序员必须主动去定义访问属性的方法(例如getDollars或setCents)。

最后要提示大家:这种方法对于那些被其他程序员和团队复用的代码最为重要。假设仅仅是在你自己一个维护的应用中创建一个类似Money的类,那么如果你改变了Money的接口,你只需要重构自己的代码就可以。这种情况下,你没有必要像上面说的那样使用@property装饰器。

您可能感兴趣的文章:

  • python如何定义带参数的装饰器
  • Python装饰器(decorator)定义与用法详解
  • 介绍Python的@property装饰器的用法
  • Python中的各种装饰器详解
  • 深入理解python中的闭包和装饰器
  • Python装饰器的函数式编程详解
  • 详解Python中的装饰器、闭包和functools的教程
  • 巧用Python装饰器
    免去调用父类构造函数的麻烦
  • Python中的多重装饰器
  • python重试装饰器示例
  • Python自定义装饰器原理与用法实例分析

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图