python高级1:面向对象进阶

来源:互联网 发布:查看矢量图的软件 编辑:程序博客网 时间:2024/05/29 19:08

第一章 面向对象进阶

1.1. 元类1.2. python是动态语言1.3. __slots__1.4. 生成器1.5. 迭代器1.6. 闭包1.7. 装饰器


1.1. 元类



1. 类也是对象

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:

>>> class ObjectCreator(object):pass>>> my_object = ObjectCreator()>>> print my_object<__main__.ObjectCreator object at 0x8974f2c>

但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。

下面的代码段:

>>> class ObjectCreator(object):pass…将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:你可以将它赋值给一个变量你可以拷贝它你可以为它增加属性你可以将它作为函数参数进行传递下面是示例:```python>>> print ObjectCreator     # 你可以打印一个类,因为它其实也是一个对象<class '__main__.ObjectCreator'>>>> def echo(o):print o…>>> echo(ObjectCreator)                 # 你可以将类做为参数传给函数<class '__main__.ObjectCreator'>>>> print hasattr(ObjectCreator, 'new_attribute')Fasle>>> ObjectCreator.new_attribute = 'foo' #  你可以为类增加属性>>> print hasattr(ObjectCreator, 'new_attribute')True>>> print ObjectCreator.new_attributefoo>>> ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量>>> print ObjectCreatorMirror()<__main__.ObjectCreator object at 0x8997b4c>

2. 动态地创建类

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。

>>> def choose_class(name):if name == 'foo':…           class Foo(object):passreturn Foo     # 返回的是类,不是类的实例else:…           class Bar(object):passreturn Bar…>>> MyClass = choose_class('foo')>>> print MyClass              # 函数返回的是类,不是类的实例<class '__main__'.Foo>>>> print MyClass()            # 你可以通过这个类创建类实例,也就是对象<__main__.Foo object at 0x89c6d4c>

但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。

还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,就像这样:

>>> print type(1) #数值的类型<type 'int'>>>> print type("1") #字符串的类型<type 'str'>>>> print type(ObjectCreator()) #实例对象的类型<class '__main__.ObjectCreator'>>>> print type(ObjectCreator) #类的类型<type 'type'>

仔细观察上面的运行结果,发现使用type对ObjectCreator查看类型是,答案为type, 是不是有些惊讶。。。看下面

3. 使用type创建类

type还有一种完全不同的功能,动态的创建类。

type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type可以像这样工作:

type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

In [2]: class Test: #定义了一个Test类  ...:     pass  ...:In [3]: Test() #创建了一个Test类的实例对象Out[3]: <__main__.Test at 0x10d3f8438>

可以手动像这样创建:

Test2 = type("Test2",(),{}) #定了一个Test2类In [5]: Test2() #创建了一个Test2类的实例对象Out[5]: <__main__.Test2 at 0x10d406b38>

我们使用”Test2”作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。即type函数中第1个实参,也可以叫做其他的名字,这个名字表示类的名字

In [23]: MyDogClass = type('MyDog', (), {})In [24]: print MyDogClass<class '__main__.MyDog'>

使用help来测试这2个类

In [10]: help(Test) #用help查看Test类Help on class Test in module __main__:class Test(builtins.object)|  Data descriptors defined here:||  __dict__|      dictionary for instance variables (if defined)||  __weakref__|      list of weak references to the object (if defined)In [8]: help(Test2) #用help查看Test2类Help on class Test2 in module __main__:class Test2(builtins.object)|  Data descriptors defined here:||  __dict__|      dictionary for instance variables (if defined)||  __weakref__|      list of weak references to the object (if defined)

4. 使用type创建带有属性的类

type 接受一个字典来为类定义属性,因此

>>> Foo = type('Foo', (), {'bar':True})

可以翻译为:

>>> class Foo(object):…       bar = True

并且可以将Foo当成一个普通的类一样使用:

>>> print Foo<class '__main__.Foo'>>>> print Foo.barTrue>>> f = Foo()>>> print f<__main__.Foo object at 0x8a9b84c>>>> print f.barTrue

当然,你可以向这个类继承,所以,如下的代码:

>>> class FooChild(Foo):pass

就可以写成:

>>> FooChild = type('FooChild', (Foo,),{})>>> print FooChild<class '__main__.FooChild'>>>> print FooChild.bar   # bar属性是由Foo继承而来True

注意:

type的第2个参数,元组中是父类的名字,而不是字符串
添加的属性是类属性,并不是实例属性

5. 使用type创建带有方法的类

最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。

添加实例方法

In [46]: def echo_bar(self): #定义了一个普通的函数   ...:     print(self.bar)   ...:In [47]: FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) #让FooChild类中的echo_bar属性,指向了上面定义的函数In [48]: hasattr(Foo, 'echo_bar') #判断Foo类中,是否有echo_bar这个属性Out[48]: FalseIn [49]:In [49]: hasattr(FooChild, 'echo_bar') #判断FooChild类中,是否有echo_bar这个属性Out[49]: TrueIn [50]: my_foo = FooChild()In [51]: my_foo.echo_bar()True

添加静态方法

In [36]: @staticmethod   ...: def testStatic():   ...:     print("static method ....")   ...:In [37]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":   ...: testStatic})In [38]: fooclid = Foochild()In [39]: fooclid.testStaticOut[39]: <function __main__.testStatic>In [40]: fooclid.testStatic()static method ....In [41]: fooclid.echo_bar()True

添加类方法

In [42]: @classmethod   ...: def testClass(cls):   ...:     print(cls.bar)   ...:In [43]:In [43]: Foochild = type('Foochild', (Foo,), {"echo_bar":echo_bar, "testStatic":   ...: testStatic, "testClass":testClass})In [44]:In [44]: fooclid = Foochild()In [45]: fooclid.testClass()True

你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。

6. 到底什么是元类(终于到主题了)

元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。

元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

MyClass = MetaClass() #使用元类创建出一个对象,这个对象称为“类”MyObject = MyClass() #使用“类”来创建出实例对象

你已经看到了type可以让你像这样做:

MyClass = type('MyClass', (), {})

这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。你可以通过检查class属性来看到这一点。Python中所有的东西,注意,我是指所有的东西——都是对象。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type。

>>> age = 35>>> age.__class__<type 'int'>>>> name = 'bob'>>> name.__class__<type 'str'>>>> def foo(): pass>>>foo.__class__<type 'function'>>>> class Bar(object): pass>>> b = Bar()>>> b.__class__<class '__main__.Bar'>现在,对于任何一个__class____class__属性又是什么呢?>>> a.__class__.__class__<type 'type'>>>> age.__class__.__class__<type 'type'>>>> foo.__class__.__class__<type 'type'>>>> b.__class__.__class__<type 'type'>

因此,元类就是创建类这种对象的东西。type就是Python的内建元类,当然了,你也可以创建自己的元类。

8. 自定义元类

元类的主要目的就是为了当创建类时能够自动地改变类。通常,你会为API做这样的事情,你希望可以创建符合当前上下文的类。

假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

python2中

#-*- coding:utf-8 -*-def upper_attr(future_class_name, future_class_parents, future_class_attr):   #遍历属性字典,把不是__开头的属性名字变为大写   newAttr = {}   for name,value in future_class_attr.items():       if not name.startswith("__"):           newAttr[name.upper()] = value   #调用type来创建一个类   return type(future_class_name, future_class_parents, newAttr)class Foo(object):   __metaclass__ = upper_attr #设置Foo类的元类为upper_attr   bar = 'bip'print(hasattr(Foo, 'bar'))print(hasattr(Foo, 'BAR'))f = Foo()print(f.BAR)

python3中

#-*- coding:utf-8 -*-def upper_attr(future_class_name, future_class_parents, future_class_attr):   #遍历属性字典,把不是__开头的属性名字变为大写   newAttr = {}   for name,value in future_class_attr.items():       if not name.startswith("__"):           newAttr[name.upper()] = value   #调用type来创建一个类   return type(future_class_name, future_class_parents, newAttr)class Foo(object, metaclass=upper_attr):   bar = 'bip'print(hasattr(Foo, 'bar'))print(hasattr(Foo, 'BAR'))f = Foo()print(f.BAR)

现在让我们再做一次,这一次用一个真正的class来当做元类。

#coding=utf-8class UpperAttrMetaClass(type):   # __new__ 是在__init__之前被调用的特殊方法   # __new__是用来创建对象并返回之的方法   # 而__init__只是用来将传入的参数初始化给对象   # 你很少用到__new__,除非你希望能够控制对象的创建   # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__   # 如果你希望的话,你也可以在__init__中做些事情   # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用   def __new__(cls, future_class_name, future_class_parents, future_class_attr):       #遍历属性字典,把不是__开头的属性名字变为大写       newAttr = {}       for name,value in future_class_attr.items():           if not name.startswith("__"):               newAttr[name.upper()] = value       # 方法1:通过'type'来做类对象的创建       # return type(future_class_name, future_class_parents, newAttr)       # 方法2:复用type.__new__方法       # 这就是基本的OOP编程,没什么魔法       # return type.__new__(cls, future_class_name, future_class_parents, newAttr)       # 方法3:使用super方法       return super(UpperAttrMetaClass, cls).__new__(cls, future_class_name, future_class_parents, newAttr)#python2的用法class Foo(object):   __metaclass__ = UpperAttrMetaClass   bar = 'bip'# python3的用法# class Foo(object, metaclass = UpperAttrMetaClass):#     bar = 'bip'print(hasattr(Foo, 'bar'))# 输出: Falseprint(hasattr(Foo, 'BAR'))# 输出:Truef = Foo()print(f.BAR)# 输出:'bip'

就是这样,除此之外,关于元类真的没有别的可说的了。但就元类本身而言,它们其实是很简单的:

拦截类的创建修改类返回修改之后的类究竟为什么要使用元类?

现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?好吧,一般来说,你根本就用不上它:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters


1.2. python是动态语言



1. 动态语言的定义

动态编程语言 是 高级程序设计语言 的一个类别,在计算机科学领域已被广泛应用。它是一类 在运行时可以改变其结构的语言 :例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。动态语言目前非常具有活力。例如JavaScript便是一个动态语言,除此之外如 PHP 、 Ruby 、 Python 等也都属于动态语言,而 C 、 C++ 等语言则不属于动态语言。—-来自 维基百科

2. 运行的过程中给对象绑定(添加)属性

>>> class Person(object):   def __init__(self, name = None, age = None):       self.name = name       self.age = age>>> P = Person("小明", "24")>>>在这里,我们定义了1个类Person,在这个类里,定义了两个初始属性name和age,但是人还有性别啊!如果这个类不是你写的是不是你会尝试访问性别这个属性呢?>>> P.sex = "male">>> P.sex'male'>>>这时候就发现问题了,我们定义的类里面没有sex这个属性啊!怎么回事呢? 这就是动态语言的魅力和坑! 这里 实际上就是 动态给实例绑定属性!

3. 运行的过程中给类绑定(添加)属性

>>> P1 = Person("小丽", "25")>>> P1.sexTraceback (most recent call last): File "<pyshell#21>", line 1, in <module>   P1.sexAttributeError: Person instance has no attribute 'sex'>>>我们尝试打印P1.sex,发现报错,P1没有sex这个属性!---- 给P这个实例绑定属性对P1这个实例不起作用! 那我们要给所有的Person的实例加上 sex属性怎么办呢? 答案就是直接给Person绑定属性!>>>> Person.sex = None #给类Person添加一个属性>>> P1 = Person("小丽", "25")>>> print(P1.sex) #如果P1这个实例对象中没有sex属性的话,那么就会访问它的类属性None #可以看到没有出现异常>>>

4. 运行的过程中给类绑定(添加)方法

我们直接给Person绑定sex这个属性,重新实例化P1后,P1就有sex这个属性了! 那么function呢?怎么绑定?

>>> class Person(object):   def __init__(self, name = None, age = None):       self.name = name       self.age = age   def eat(self):       print("eat food")>>> def run(self, speed):   print("%s在移动, 速度是 %d km/h"%(self.name, speed))>>> P = Person("老王", 24)>>> P.eat()eat food>>> >>> P.run()Traceback (most recent call last): File "<pyshell#5>", line 1, in <module>   P.run()AttributeError: Person instance has no attribute 'run'>>>>>>>>> import types>>> P.run = types.MethodType(run, P)>>> P.run(180)

老王在移动,速度是 180 km/h
既然给类添加方法,是使用类名.方法名 = xxxx,那么给对象添加一个方法也是类似的对象.方法名 = xxxx

完整的代码如下:

import types#定义了一个类class Person(object):   num = 0   def __init__(self, name = None, age = None):       self.name = name       self.age = age   def eat(self):       print("eat food")#定义一个实例方法def run(self, speed):   print("%s在移动, 速度是 %d km/h"%(self.name, speed))#定义一个类方法@classmethoddef testClass(cls):   cls.num = 100#定义一个静态方法@staticmethoddef testStatic():   print("---static method----")#创建一个实例对象P = Person("老王", 24)#调用在class中的方法P.eat()#给这个对象添加实例方法P.run = types.MethodType(run, P)#调用实例方法P.run(180)#给Person类绑定类方法Person.testClass = testClass#调用类方法print(Person.num)Person.testClass()print(Person.num)#给Person类绑定静态方法Person.testStatic = testStatic#调用静态方法Person.testStatic()

5. 运行的过程中删除属性、方法

删除的方法:

del 对象.属性名delattr(对象, "属性名")

通过以上例子可以得出一个结论:相对于动态语言,静态语言具有严谨性!所以,玩动态语言的时候,小心动态的坑!

那么怎么避免这种情况呢? 请使用__slots__,



1.3. __slots__



现在我们终于明白了,动态语言与静态语言的不同

动态语言:可以在运行的过程中,修改代码

静态语言:编译时已经确定好代码,运行过程中不能修改

如果我们想要限制实例的属性怎么办?比如,只允许对Person实例添加name和age属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:

>>> class Person(object):   __slots__ = ("name", "age")>>> P = Person()>>> P.name = "老王">>> P.age = 20>>> P.score = 100Traceback (most recent call last): File "<pyshell#3>", line 1, in <module>AttributeError: Person instance has no attribute 'score'>>>

注意:

使用slots要注意,slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用的

In [67]: class Test(Person):   ...:     pass   ...:In [68]: t = Test()In [69]: t.score = 100


1.4. 生成器



1. 什么是生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

2. 创建生成器方法1

要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的 [ ] 改成 ( )

In [15]: L = [ x*2 for x in range(5)]In [16]: LOut[16]: [0, 2, 4, 6, 8]In [17]: G = ( x*2 for x in range(5))In [18]: GOut[18]: <generator object <genexpr> at 0x7f626c132db0>In [19]:

创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出L的每一个元素,但我们怎么打印出G的每一个元素呢?如果要一个一个打印出来,可以通过 next() 函数获得生成器的下一个返回值:

In [19]: next(G)Out[19]: 0In [20]: next(G)Out[20]: 2In [21]: next(G)Out[21]: 4In [22]: next(G)Out[22]: 6In [23]: next(G)Out[23]: 8In [24]: next(G)---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last)<ipython-input-24-380e167d6934> in <module>()----> 1 next(G)StopIteration: In [25]:In [26]: G = ( x*2 for x in range(5))In [27]: for x in G:  ....:     print(x)  ....:     -0.41.63.65.67.6In [28]:

生成器保存的是算法,每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。当然,这种不断调用 next() 实在是太变态了,正确的方法是使用 for 循环,因为生成器也是可迭代对象。所以,我们创建了一个生成器后,基本上永远不会调用 next() ,而是通过 for 循环来迭代它,并且不需要关心 StopIteration 异常。

3. 创建生成器方法2

generator非常强大。如果推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

0.6, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

In [28]: def fib(times):  ....:     n = 0  ....:     a,b = 0,1  ....:     while n<times:  ....:         print(b)  ....:         a,b = b,a+b  ....:         n+=1  ....:     return 'done'  ....: In [29]: fib(5)0.60.61.62.64.6Out[29]: 'done'

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

In [30]: def fib(times):  ....:     n = 0  ....:     a,b = 0,1  ....:     while n<times:  ....:         yield b  ....:         a,b = b,a+b  ....:         n+=1  ....:     return 'done'  ....: In [31]: F = fib(5)In [32]: next(F)Out[32]: 1In [33]: next(F)Out[33]: 1In [34]: next(F)Out[34]: 2In [35]: next(F)Out[35]: 3In [36]: next(F)Out[36]: 5In [37]: next(F)---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last)<ipython-input-37-8c2b02b4361a> in <module>()----> 1 next(F)StopIteration: done

在上面fib 的例子,我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:

In [38]: for n in fib(5):  ....:     print(n)  ....:     0.60.61.62.64.6In [39]:

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

In [39]: g = fib(5)In [40]: while True:  ....:     try:  ....:         x = next(g)  ....:         print("value:%d"%x)        ....:     except StopIteration as e:  ....:         print("生成器返回值:%s"%e.value)  ....:         break  ....:     value:1value:1value:2value:3value:5生成器返回值:doneIn [41]:3.6. send

例子:执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send(“python”),send发送过来的值,c.next()等价c.send(None)

In [10]: def gen():  ....:     i = 0  ....:     while i<5:  ....:         temp = yield i  ....:         print(temp)  ....:         i+=1  ....:

使用next函数

In [11]: f = gen()In [12]: next(f)Out[12]: 0In [13]: next(f)NoneOut[13]: 1In [14]: next(f)NoneOut[14]: 2In [15]: next(f)NoneOut[15]: 3In [16]: next(f)NoneOut[16]: 4In [17]: next(f)None---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last)<ipython-input-17-468f0afdf1b9> in <module>()----> 1 next(f)StopIteration:

使用__next__()方法

In [18]: f = gen()In [19]: f.__next__()Out[19]: 0In [20]: f.__next__()NoneOut[20]: 1In [21]: f.__next__()NoneOut[21]: 2In [22]: f.__next__()NoneOut[22]: 3In [23]: f.__next__()NoneOut[23]: 4In [24]: f.__next__()None---------------------------------------------------------------------------StopIteration                             Traceback (most recent call last)<ipython-input-24-39ec527346a9> in <module>()----> 1 f.__next__()StopIteration:

使用send

In [43]: f = gen()In [44]: f.__next__()Out[44]: 0In [45]: f.send('haha')hahaOut[45]: 1In [46]: f.__next__()NoneOut[46]: 2In [47]: f.send('haha')hahaOut[47]: 3In [48]:

总结

生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:

节约内存迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的


1.5. 迭代器



迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1. 可迭代对象

以直接作用于 for 循环的数据类型有以下几种:

一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等;一类是 generator ,包括生成器和带 yield 的generator function。

这些可以直接作用于 for 循环的对象统称为可迭代对象: Iterable 。

2. 判断是否可以迭代

可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

In [50]: from collections import IterableIn [51]: isinstance([], Iterable)Out[51]: TrueIn [52]: isinstance({}, Iterable)Out[52]: TrueIn [53]: isinstance('abc', Iterable)Out[53]: TrueIn [54]: isinstance((x for x in range(10)), Iterable)Out[54]: TrueIn [55]: isinstance(100, Iterable)Out[55]: False

而生成器不但可以作用于 for 循环,还可以被 next() 函数不断调用并返回下一个值,直到最后抛出 StopIteration 错误表示无法继续返回下一个值了。

3.迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

In [56]: from collections import IteratorIn [57]: isinstance((x for x in range(10)), Iterator)Out[57]: TrueIn [58]: isinstance([], Iterator)Out[58]: FalseIn [59]: isinstance({}, Iterator)Out[59]: FalseIn [60]: isinstance('abc', Iterator)Out[60]: FalseIn [61]: isinstance(100, Iterator)Out[61]: False

4.iter()函数

生成器都是 Iterator 对象,但 list 、 dict 、 str 虽然是 Iterable ,却不是 Iterator 。

把 list 、 dict 、 str 等 Iterable 变成 Iterator 可以使用 iter() 函数:

In [62]: isinstance(iter([]), Iterator)Out[62]: TrueIn [63]: isinstance(iter('abc'), Iterator)Out[63]: True

总结

凡是可作用于 for 循环的对象都是 Iterable 类型;凡是可作用于 next() 函数的对象都是 Iterator 类型集合数据类型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个 Iterator 对象。


1.6. 闭包



1. 函数引用

def test1():   print("--- in test1 func----")#调用函数test1()#引用函数ret = test1print(id(ret))print(id(test1))#通过引用调用函数ret()运行结果:--- in test1 func----140212571149040140212571149040--- in test1 func----

2. 什么是闭包

#定义一个函数def test(number):   #在函数内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包   def test_in(number_in):       print("in test_in 函数, number_in is %d"%number_in)       return number+number_in   #其实这里返回的就是闭包的结果   return test_in#给test函数赋值,这个20就是给参数numberret = test(20)#注意这里的100其实给参数number_inprint(ret(100))#注意这里的200其实给参数number_inprint(ret(200))运行结果:in test_in 函数, number_in is 100120in test_in 函数, number_in is 200220

3. 闭包再理解

内部函数对外部函数作用域里变量的引用(非全局变量),则称内部函数为闭包。

# closure.pydef counter(start=0):   count=[start]   def incr():       count[0] += 1       return count[0]   return incr

启动python解释器

>>>import closeure>>>c1=closeure.counter(5)>>>print(c1())6>>>print(c1())7>>>c2=closeure.counter(100)>>>print(c2())101>>>print(c2())102

nonlocal访问外部函数的局部变量(python3)

def counter(start=0):   def incr():       nonlocal start       start += 1       return start   return incrc1 = counter(5)print(c1())print(c1())c2 = counter(50)print(c2())print(c2())print(c1())print(c1())print(c2())print(c2())

4. 看一个闭包的实际例子:

def line_conf(a, b):   def line(x):       return a*x + b   return lineline1 = line_conf(1, 1)line2 = line_conf(4, 5)print(line1(5))print(line2(5))

这个例子中,函数line与变量a,b构成闭包。在创建闭包的时候,我们通过line_conf的参数a,b说明了这两个变量的取值,这样,我们就确定了函数的最终形式(y = x + 1和y = 4x + 5)。我们只需要变换参数a,b,就可以获得不同的直线表达函数。由此,我们可以看到,闭包也具有提高代码可复用性的作用。

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

闭包思考:

1.闭包似优化了变量,原来需要类对象完成的工作,闭包也可以完成2.由于闭包引用了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存


1.7. 装饰器



装饰器是程序开发中经常会用到的一个功能,用好了装饰器,开发效率如虎添翼,所以这也是Python面试中必问的问题,但对于好多初次接触这个知识的人来讲,这个功能有点绕,自学时直接绕过去了,然后面试问到了就挂了,因为装饰器是程序开发的基础知识,这个都不会,别跟人家说你会Python, 看了下面的文章,保证你学会装饰器。

1、先明白这段代码

#### 第一波 ####def foo():   print('foo')foo     #表示是函数foo()   #表示执行foo函数#### 第二波 ####def foo():   print('foo')foo = lambda x: x + 1foo()   # 执行下面的lambda表达式,而不再是原来的foo函数,因为foo这个名字被重新指向了另外一个匿名函数

2、需求来了

初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

############### 基础平台提供的功能如下 ###############def f1():   print('f1')def f2():   print('f2')def f3():   print('f3')def f4():   print('f4')############### 业务部门A 调用基础平台提供的功能 ###############f1()f2()f3()f4()############### 业务部门B 调用基础平台提供的功能 ###############f1()f2()f3()f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

老大把工作交给 Low B,他是这么做的:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。太棒了,有充足的时间泡妹子…

当天Low B 被开除了…

老大把工作交给 Low BB,他是这么做的:

############### 基础平台提供的功能如下 ############### def f1():   # 验证1   # 验证2   # 验证3   print('f1')def f2():   # 验证1   # 验证2   # 验证3   print('f2')def f3():   # 验证1   # 验证2   # 验证3   print('f3')def f4():   # 验证1   # 验证2   # 验证3   print('f4')############### 业务部门不变 ############### ### 业务部门A 调用基础平台提供的功能### f1()f2()f3()f4()### 业务部门B 调用基础平台提供的功能 ### f1()f2()f3()f4()

过了一周 Low BB 被开除了…

老大把工作交给 Low BBB,他是这么做的:

只对基础平台的代码进行重构,其他业务部门无需做任何修改

############### 基础平台提供的功能如下 ############### def check_login():   # 验证1   # 验证2   # 验证3   passdef f1():   check_login()   print('f1')def f2():   check_login()   print('f2')def f3():   check_login()   print('f3')def f4():   check_login()   print('f4')

老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天:

老大说:

写代码要遵循开放封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

封闭:已实现的功能代码块
开放:对扩展开发
如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:

def w1(func):   def inner():       # 验证1       # 验证2       # 验证3       func()   return inner@w1def f1():   print('f1')@w1def f2():   print('f2')@w1def f3():   print('f3')@w1def f4():   print('f4')

对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。

Low BBB心惊胆战的问了下,这段代码的内部执行原理是什么呢?

老大正要生气,突然Low BBB的手机掉到地上,恰巧屏保就是Low BBB的女友照片,老大一看一紧一抖,喜笑颜开,决定和Low BBB交个好朋友。

详细的开始讲解了:

单独以f1为例:

def w1(func):   def inner():       # 验证1       # 验证2       # 验证3       func()   return inner@w1def f1():   print('f1')python解释器就会从上到下解释代码,步骤如下:def w1(func): ==>将w1函数加载到内存@w1``没错, 从表面上看解释器仅仅会解释这两句代码,因为函数在 没有被调用之前其内部代码不会被执行。从表面上看解释器着实会执行这两句,但是 @w1 这一句代码里却有大文章, @函数名 是python的一种语法糖。上例@w1内部会执行一下操作:执行w1函数执行w1函数 ,并将 @w1 下面的函数作为w1函数的参数,即:@w1 等价于 w1(f1) 所以,内部就会去执行:```pythondef inner():    #验证 1   #验证 2   #验证 3   f1()     # func是参数,此时 func 等于 f1 return inner# 返回的 inner,inner代表的是函数,非执行函数 ,其实就是将原来的 f1 函数塞进另外一个函数中

w1的返回值

将执行完的w1函数返回值 赋值 给@w1下面的函数的函数名f1 即将w1的返回值再重新赋值给 f1,即:

新f1 = def inner():            #验证 1           #验证 2           #验证 3           原来f1()       return inner

所以,以后业务部门想要执行 f1 函数时,就会执行 新f1 函数,在新f1 函数内部先执行验证,再执行原来的f1函数,然后将原来f1 函数的返回值返回给了业务调用者。

如此一来, 即执行了验证的功能,又执行了原来f1函数的内容,并将原f1函数返回值 返回给业务调用着

Low BBB 你明白了吗?要是没明白的话,我晚上去你家帮你解决吧!!!

3. 再议装饰器

#定义函数:完成包裹数据def makeBold(fn):   def wrapped():       return "<b>" + fn() + "</b>"   return wrapped#定义函数:完成包裹数据def makeItalic(fn):   def wrapped():       return "<i>" + fn() + "</i>"   return wrapped@makeBolddef test1():   return "hello world-1"@makeItalicdef test2():   return "hello world-2"@makeBold@makeItalicdef test3():   return "hello world-3"print(test1()))print(test2()))print(test3()))运行结果:<b>hello world-1</b><i>hello world-2</i><b><i>hello world-3</i></b>

4. 装饰器(decorator)功能

引入日志函数执行时间统计执行函数前预备处理执行函数后清理功能权限校验等场景缓存

5. 装饰器示例

例1:无参数的函数

from time import ctime, sleepdef timefun(func):   def wrappedfunc():       print("%s called at %s"%(func.__name__, ctime()))       func()   return wrappedfunc@timefundef foo():   print("I am foo")foo()sleep(2)foo()上面代码理解装饰器执行行为可理解成foo = timefun(foo)#foo先作为参数赋值给func后,foo接收指向timefun返回的wrappedfuncfoo()#调用foo(),即等价调用wrappedfunc()#内部函数wrappedfunc被引用,所以外部函数的func变量(自由变量)并没有释放#func里保存的是原foo函数对象

例2:被装饰的函数有参数

from time import ctime, sleepdef timefun(func):   def wrappedfunc(a, b):       print("%s called at %s"%(func.__name__, ctime()))       print(a, b)       func(a, b)   return wrappedfunc@timefundef foo(a, b):   print(a+b)foo(3,5)sleep(2)foo(2,4)

例3:被装饰的函数有不定长参数

from time import ctime, sleepdef timefun(func):   def wrappedfunc(*args, **kwargs):       print("%s called at %s"%(func.__name__, ctime()))       func(*args, **kwargs)   return wrappedfunc@timefundef foo(a, b, c):   print(a+b+c)foo(3,5,7)sleep(2)foo(2,4,9)

例4:装饰器中的return

from time import ctime, sleepdef timefun(func):   def wrappedfunc():       print("%s called at %s"%(func.__name__, ctime()))       func()   return wrappedfunc@timefundef foo():   print("I am foo")@timefundef getInfo():   return '----hahah---'foo()sleep(2)foo()print(getInfo())执行结果:foo called at Fri Nov  4 21:55:35 2016I am foofoo called at Fri Nov  4 21:55:37 2016I am foogetInfo called at Fri Nov  4 21:55:37 2016None如果修改装饰器为return func(),则运行结果:foo called at Fri Nov  4 21:55:57 2016I am foofoo called at Fri Nov  4 21:55:59 2016I am foogetInfo called at Fri Nov  4 21:55:59 2016----hahah---

总结:

一般情况下为了让装饰器更通用,可以有return

例5:装饰器带参数,在原有装饰器的基础上,设置外部变量

#decorator2.pyfrom time import ctime, sleepdef timefun_arg(pre="hello"):   def timefun(func):       def wrappedfunc():           print("%s called at %s %s"%(func.__name__, ctime(), pre))           return func()       return wrappedfunc   return timefun@timefun_arg("itcast")def foo():   print("I am foo")@timefun_arg("python")def too():   print("I am too")foo()sleep(2)foo()too()sleep(2)too()可以理解为foo()==timefun_arg("itcast")(foo)()

例6:类装饰器(扩展,非重点)

装饰器函数其实是这样一个接口约束,它必须接受一个callable对象作为参数,然后返回一个callable对象。在Python中一般callable对象都是函数,但也有例外。只要某个对象重写了 __call__() 方法,那么这个对象就是callable的。

class Test():   def __call__(self):       print('call me!')t = Test()t()  # call me

类装饰器demo

class Test(object):   def __init__(self, func):       print("---初始化---")       print("func name is %s"%func.__name__)       self.__func = func   def __call__(self):       print("---装饰器中的功能---")       self.__func()#说明:#1. 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象#    并且会把test这个函数名当做参数传递到__init__方法中#    即在__init__方法中的func变量指向了test函数体#2. test函数相当于指向了用Test创建出来的实例对象#3. 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法#4. 为了能够在__call__方法中调用原来test指向的函数体,所以在__init__方法中就需要一个实例属性来保存这个函数体的引用#    所以才有了self.__func = func这句代码,从而在调用__call__方法中能够调用到test之前的函数体@Testdef test():   print("----test---")test()showpy()         # 如果把这句话注释,重新运行程序,依然会看到"--初始化--"运行结果如下:---初始化---func name is test---装饰器中的功能-------test---
原创粉丝点击