Python语言学习之Python 进阶内容整理
小标 2019-04-04 来源 : 阅读 928 评论 0

摘要:本文主要向大家介绍了Python语言学习之Python 进阶内容整理,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。

本文主要向大家介绍了Python语言学习之Python 进阶内容整理,通过具体的内容向大家展示,希望对大家学习Python语言有所帮助。

Python语言学习之Python 进阶内容整理

The Zen of Python, by Tim Peters

Beautiful is better than ugly.

Explicit is better than implicit.

Simple is better than complex.

Complex is better than complicated.

Flat is better than nested.

Sparse is better than dense.

Readability counts.

Special cases aren't special enough to break the rules.

Although practicality beats purity.

Errors should never pass silently.

Unless explicitly silenced.

In the face of ambiguity, refuse the temptation to guess.

There should be one-- and preferably only one --obvious way to do it.

Although that way may not be obvious at first unless you're Dutch.

Now is better than never.

Although never is often better than *right* now.

If the implementation is hard to explain, it's a bad idea.

If the implementation is easy to explain, it may be a good idea.

Namespaces are one honking great idea -- let's do more of those!


Python之禅 by Tim Peters

优美胜于丑陋(Python 以编写优美的代码为目标)

明了胜于晦涩(优美的代码应当是明了的,命名规范,风格相似)

简洁胜于复杂(优美的代码应当是简洁的,不要有复杂的内部实现)

复杂胜于凌乱(如果复杂不可避免,那代码间也不能有难懂的关系,要保持接口简洁)

扁平胜于嵌套(优美的代码应当是扁平的,不能有太多的嵌套)

间隔胜于紧凑(优美的代码有适当的间隔,不要奢望一行代码解决问题)

可读性很重要(优美的代码是可读的)

即便假借特例的实用性之名,也不可违背这些规则(这些规则至高无上)

不要包容所有错误,除非你确定需要这样做(精准地捕获异常,不写 except:pass 风格的代码)

当存在多种可能,不要尝试去猜测

而是尽量找一种,最好是唯一一种明显的解决方案(如果不确定,就用穷举法)

虽然这并不容易,因为你不是 Python 之父(这里的 Dutch 是指 Guido )

做也许好过不做,但不假思索就动手还不如不做(动手之前要细思量)

如果你无法向人描述你的方案,那肯定不是一个好方案;反之亦然(方案测评标准)

命名空间是一种绝妙的理念,我们应当多加利用(倡导与号召)


参数传递是值传递还是引用传递


引用传递


都是引用,对于不可改变的数据类型来说,不能改变,如果修改了,事实上是新建一个对象来对待。

或者说

Python中有可变对象(比如列表List)和不可变对象(比如字符串),在参数传递时分为两种情况:


对于不可变对象作为函数参数,相当于C系语言的值传递;


对于可变对象作为函数参数,相当于C系语言的引用传递。


再或者

传值方式等价于python赋值号(=),但不应该说是浅拷贝。

以list为例,浅拷贝可变对象时(如list),会创建一个新的list对象,并让新对象内部的每一个元素指向原对象每个元素指向的元素;而赋值号将不会创建新对象,而是直接创建一个引用连接到原对象。函数传值是后者,可以写一个函数,在函数里面打印传入参数的id(),与原值的id()是一样的,因此是直接赋值而不是浅拷贝。


深拷贝与浅拷贝


Python中对象的赋值都是进行对象引用(内存地址)传递


使用copy.copy(),可以进行对象的浅拷贝,它复制了对象,但对于对象中的元素,依然使用原始的引用.


如果需要复制一个容器对象,以及它里面的所有元素(包含元素的子元素),可以使用copy.deepcopy()进行深拷贝


对于非容器类型(如数字、字符串、和其他'原子'类型的对象)没有被拷贝一说


如果元祖变量只包含原子类型对象,则不能深拷贝


引用


image.png


浅拷贝


image.png


深拷贝


image.png


垃圾回收机制


引用计数


python里每一个东西都是对象,它们的核心就是一个结构体:PyObject


typedef struct_object
 {
     int ob_refcnt;
     struct_typeobject *ob_type;
} PyObject;


PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少


#define Py_INCREF(op)   ((op)->ob_refcnt++) //增加计数
#define Py_DECREF(op)  //减少计数

if (--(op)->ob_refcnt != 0) ; 
else  __Py_Dealloc((PyObject *)(op))


当引用计数为0时,该对象生命就结束了。


引用计数机制的优点:


简单


实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。


引用计数机制的缺点:


维护引用计数消耗资源


循环引用


list1 = []
list2 = []
list1.append(list2)
list2.append(list1)


list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。

对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。

(标记清除和分代收集)


基于零代算法的循环引用检测及垃圾回收机制


Python中的循环数据结构以及引用计数


我们知道在Python中,每个对象都保存了一个称为引用计数的整数值,来追踪到底有多少引用指向了这个对象。无论何时,如果我们程序中的一个变量或其他对象引用了目标对象,Python将会增加这个计数值,而当程序停止使用这个对象,则Python会减少这个计数值。一旦计数值被减到零,Python将会释放这个对象以及回收相关内存空间。


从六十年代开始,计算机科学界就面临了一个严重的理论问题,那就是针对引用计数这种算法来说,如果一个数据结构引用了它自身,即如果这个数据结构是一个循环数据结构,那么某些引用计数值是肯定无法变成零的。为了更好地理解这个问题,让我们举个例子。下面的代码展示了一些上周我们所用到的节点类:


我们有一个构造器(在Python中叫做 init ),在一个实例变量中存储一个单独的属性。在类定义之后我们创建两个节点,ABC以及DEF,在图中为左边的矩形框。两个节点的引用计数都被初始化为1,因为各有两个引用指向各个节点(n1和n2)。


现在,让我们在节点中定义两个附加的属性,next以及prev:


跟Ruby不同的是,Python中你可以在代码运行的时候动态定义实例变量或对象属性。这看起来似乎有点像Ruby缺失了某些有趣的魔法。(声明下我不是一个Python程序员,所以可能会存在一些命名方面的错误)。我们设置 n1.next 指向 n2,同时设置 n2.prev 指回 n1。现在,我们的两个节点使用循环引用的方式构成了一个双端链表。同时请注意到 ABC 以及 DEF 的引用计数值已经增加到了2。这里有两个指针指向了每个节点:首先是 n1 以及 n2,其次就是 next 以及 prev。


现在,假定我们的程序不再使用这两个节点了,我们将 n1 和 n2 都设置为null(Python中是None)。


好了,Python会像往常一样将每个节点的引用计数减少到1。


在Python中的零代(Generation Zero)


请注意在以上刚刚说到的例子中,我们以一个不是很常见的情况结尾:我们有一个“孤岛”或是一组未使用的、互相指向的对象,但是谁都没有外部引用。换句话说,我们的程序不再使用这些节点对象了,所以我们希望Python的垃圾回收机制能够足够智能去释放这些对象并回收它们占用的内存空间。但是这不可能,因为所有的引用计数都是1而不是0。Python的引用计数算法不能够处理互相指向自己的对象。


当然,上边举的是一个故意设计的例子,但是你的代码也许会在不经意间包含循环引用并且你并未意识到。事实上,当你的Python程序运行的时候它将会建立一定数量的“浮点数垃圾”,Python的GC不能够处理未使用的对象因为应用计数值不会到零。


这就是为什么Python要引入Generational GC算法的原因!正如Ruby使用一个链表(free list)来持续追踪未使用的、自由的对象一样,Python使用一种不同的链表来持续追踪活跃的对象。而不将其称之为“活跃列表”,Python的内部C代码将其称为零代(Generation Zero)。每次当你创建一个对象或其他什么值的时候,Python会将其加入零代链表:


从上边可以看到当我们创建ABC节点的时候,Python将其加入零代链表。请注意到这并不是一个真正的列表,并不能直接在你的代码中访问,事实上这个链表是一个完全内部的Python运行时。

相似的,当我们创建DEF节点的时候,Python将其加入同样的链表:


现在零代包含了两个节点对象。(他还将包含Python创建的每个其他值,与一些Python自己使用的内部值。)


检测循环引用


随后,Python会循环遍历零代列表上的每个对象,检查列表中每个互相引用的对象,根据规则减掉其引用计数。在这个过程中,Python会一个接一个的统计内部引用的数量以防过早地释放对象。


为了便于理解,来看一个例子:


从上面可以看到 ABC 和 DEF 节点包含的引用数为1.有三个其他的对象同时存在于零代链表中,蓝色的箭头指示了有一些对象正在被零代链表之外的其他对象所引用。(接下来我们会看到,Python中同时存在另外两个分别被称为一代和二代的链表)。这些对象有着更高的引用计数因为它们正在被其他指针所指向着。


接下来你会看到Python的GC是如何处理零代链表的。


通过识别内部引用,Python能够减少许多零代链表对象的引用计数。在上图的第一行中你能够看见ABC和DEF的引用计数已经变为零了,这意味着收集器可以释放它们并回收内存空间了。剩下的活跃的对象则被移动到一个新的链表:一代链表。


从某种意义上说,Python的GC算法类似于Ruby所用的标记回收算法。周期性地从一个对象到另一个对象追踪引用以确定对象是否还是活跃的,正在被程序所使用的,这正类似于Ruby的标记过程。


Python中的GC阈值


Python什么时候会进行这个标记过程?随着你的程序运行,Python解释器保持对新创建的对象,以及因为引用计数为零而被释放掉的对象的追踪。从理论上说,这两个值应该保持一致,因为程序新建的每个对象都应该最终被释放掉。


当然,事实并非如此。因为循环引用的原因,并且因为你的程序使用了一些比其他对象存在时间更长的对象,从而被分配对象的计数值与被释放对象的计数值之间的差异在逐渐增长。一旦这个差异累计超过某个阈值,则Python的收集机制就启动了,并且触发上边所说到的零代算法,释放“浮动的垃圾”,并且将剩下的对象移动到一代列表。


随着时间的推移,程序所使用的对象逐渐从零代列表移动到一代列表。而Python对于一代列表中对象的处理遵循同样的方法,一旦被分配计数值与被释放计数值累计到达一定阈值,Python会将剩下的活跃对象移动到二代列表。


通过这种方法,你的代码所长期使用的对象,那些你的代码持续访问的活跃对象,会从零代链表转移到一代再转移到二代。通过不同的阈值设置,Python可以在不同的时间间隔处理这些对象。Python处理零代最为频繁,其次是一代然后才是二代。


弱代假说


来看看代垃圾回收算法的核心行为:垃圾回收器会更频繁的处理新对象。一个新的对象即是你的程序刚刚创建的,而一个来的对象则是经过了几个时间周期之后仍然存在的对象。Python会在当一个对象从零代移动到一代,或是从一代移动到二代的过程中提升(promote)这个对象。


为什么要这么做?这种算法的根源来自于弱代假说(weak generational hypothesis)。这个假说由两个观点构成:首先是年轻的对象通常死得也快,而老对象则很有可能存活更长的时间。


假定现在我用Python或是Ruby创建一个新对象:


根据假说,我的代码很可能仅仅会使用ABC很短的时间。这个对象也许仅仅只是一个方法中的中间结果,并且随着方法的返回这个对象就将变成垃圾了。大部分的新对象都是如此般地很快变成垃圾。然而,偶尔程序会创建一些很重要的,存活时间比较长的对象-例如web应用中的session变量或是配置项。


通过频繁的处理零代链表中的新对象,Python的垃圾收集器将把时间花在更有意义的地方:它处理那些很快就可能变成垃圾的新对象。同时只在很少的时候,当满足阈值的条件,收集器才回去处理那些老变量。


del


删除元素


>>> a = [1, "two", 3, "four"]
>>> del a[0]         #删除列表a中,下标为0的元素
>>> a
['two', 3, 'four']
>>> a.append("five")
>>> a.append(6)
>>> a
['two', 3, 'four', 'five', 6]
>>> del a[2:4]        #删除a从下标为2到4的元素,含头不含尾
>>> a


删除引用


>>> x = 1
>>> del x
>>> x
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
x
NameError: name 'x' is not defined

>>> x = ['Hello','world']
>>> y = x
>>> y
['Hello', 'world']
>>> x
['Hello', 'world']
>>> del x
>>> x
Traceback (most recent call last):
File "<pyshell#12>", line 1, in <module>
x
NameError: name 'x' is not defined
>>> y
['Hello', 'world']
>>>


可以看到x和y指向同一个列表,但是删除x后,y并没有受到影响。del的是引用,而不是对象


元类 (metaclass)


类也是对象


在理解元类之前,你需要先掌握Python中的类。Python中类的概念借鉴于Smalltalk,这显得有些奇特。在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立:


>>>  class  ObjectCreator(object):
…       pass

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


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


>>> class ObjectCreator(object):
…       pass


将在内存中创建一个对象,名字就是ObjectCreator。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:


你可以将它赋值给一个变量


你可以拷贝它


你可以为它增加属性


你可以将它作为函数参数进行传递


下面是示例:


# 你可以打印一个类,因为它其实也是一个对象
>>> 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_attribute
foo

# 你可以将类赋值给一个变量
>>> ObjectCreatorMirror = ObjectCreator
>>> print ObjectCreatorMirror()
<__main__.ObjectCreator object at 0x8997b4c>


动态地创建类


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


>>> def choose_class(name):
…       if name == 'foo':
…           class Foo(object):
…               pass
…           return Foo     # 返回的是类,不是类的实例
…       else:
…           class Bar(object):
…               pass
…           return 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)
<type 'type'>
>>> print type(ObjectCreator())
<class '__main__.ObjectCreator'>


这里,type有一种完全不同的能力,它也能动态的创建类。

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


type可以像这样工作:


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


比如下面的代码:


>>> class MyShinyClass(object):
…       pass


可以手动像这样创建:


>>> MyShinyClass = type('MyShinyClass', (), {})  # 返回一个类对象
>>> print MyShinyClass
<class '__main__.MyShinyClass'>
>>> print MyShinyClass()  #  创建一个该类的实例
<__main__.MyShinyClass object at 0x8997cec>


你会发现我们使用“MyShinyClass”作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。


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


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


可以翻译为:


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


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


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


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


>>> class FooChild(Foo):
…       pass


就可以写成:


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


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


>>> def echo_bar(self):
…       print self.bar

>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True


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


到底什么是元类


元类就是用来创建类的“东西”。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。好吧,元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解 为:


MyClass = MetaClass()
MyObject = MyClass()


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


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


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


>>> 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'

本文由职坐标整理并发布,希望对同学们学习Python有所帮助,更多内容请关注职坐标编程语言Python频道!


本文由 @小标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程