您的位置:首页 > 编程语言 > Python开发

改善Python代码的建议-内部机制

2019-07-21 17:38 1336 查看

《编写高质量代码:改善Python程序的91个建议》笔记之内部机制:知其然知其所以然

1.理解built-in objects

在Python2.x中,默认都是经典类,只有显式继承了object才是新式类,即:

class Person(object):pass 新式类写法

class Person():pass 经典类写法

class Person:pass 经典类写法

 

在Python 3.x中取消了经典类,默认都是新式类,并且不必显式的继承object,也就是说:

class Person(object):pass

class Person():pass

class Person:pass

三种写法并无区别,推荐第一种

 

若一个类定义时继承自object类或者内建类型,则认为它是一个新式类的理解是不对的,应当通过元类的类型来确定类的类型,古典类的元类为type.ClassType,新式类的元类为type类。

新式类的优点在于能够基于内建类型构建新的用户类型,支持property和描述特性等。

新式类 vs 古典类:

MRO(Method Resolution Order):方法解析顺序。菱形继承是在多继承设计时需要尽量避免的问题。

新式类多继承搜索顺序MRO是广度优先:先在水平方向查找,再向上查找;

古典类多继承搜索顺序MRO是深度优先:先深入继承树左侧查找,返回,开始查找右侧;

 

2.__init__()不是构造方法

__init__()不是真正意义上的构造方法,__init__()所做的工作是在类的对象创建好之后进行变量的初始化。

__new__()方法才会真正创建实例,是类的构造方法。

 

object.__new__(cls, [args…]):cls代表类,args为参数列表。

1)它是静态方法;

2)一般需要返回类的对象,当返回类的对象时会自动调用__init__()方法进行初始化,若没有对象返回,则__init__()方法不会被调用;

3)用于创建控制实例;

4)一般不需要覆盖__new__(),当子类继承自不可变类型,如str,int,unicode或tuple时,需要覆盖该方法,覆盖时必须保持一致,不一致将导致异常。

object.__init__(self, [args…]):self代表实例对象,args为参数列表。

1)它是实例方法;

2)不需要显式返回,默认为None,否则会在运行时抛出typeError;

3)用于控制实例初始化。

 

需要覆盖__new__()方法的情况:

1.当类继承(如str,int,unicode,tuple或者frozenset等)不可变类型且默认的__new__()方法不能满足需求的时候。

2.用来实现工厂模式或者单例模式或者元类编程时,需要使用__new__()控制对象的创建。

3.作为用来初始化的__init__()方法在多继承的情况下,子类的__init__()方法若不显式调用父类的__init__()方法,则父类的__init__()方法不会被调用。

 

3.理解名字查找机制

Python中所有的变量名都在复制时生成,对任何变量名的创建,查找或者改变都会在命名空间中进行。变量名所在的命名空间直接决定了其所能访问到的范围,即变量的作用域。Python的作用域自2.2之后分为以下4种:

    局部作用域,全局作用域,嵌套作用域,内置作用域。

Python变量查找顺序遵循变量解析机制LEGB法则:

    1)在最内层的范围内查找,一般而言,就是在函数内部,即在locals()里面查找;

    2)在模块内查找,即在globals()里面查找;

    3)在外层查找,即在内置模块中查找,即在__builtin__中查找。

 

4.self参数

self:在类中当定义实例方法时需要将第一个参数显式声明为self,调用时不需要传入该参数。可以使用self.x来访问实例变量,也可以在类中使用self.m()来访问实例方法。self表示的是实例对象本身,及对应类在内存中的地址。self是对对象本身的引用。

需要self的原因:

1)设计Python语言之初借鉴其他语言如Moudla-3中方法会显式地在参数列表中传入self;

2)Python本身的动态性决定了使用self能够带来一定便利,在调用时决定应该传入哪个对象;

3)在存在同名的局部变量及实例变量的情况下使用self使得实例变量更容易被区分。

 

5.熟悉Python对象协议

类型转换协议:如__str__(),__int__();

比较大小的协议:依赖于__cmp__()方法,这是Python对==,!=,<,>等操作符的进行重载的支撑机制;

数值类型相关协议:数值运算符如__add__(),位运算符__or__(),运算赋值符__iadd__()等;

容器类型协议:内置函数len(),__getitem__(),__setitem__(),__delitem__()等;

可调用对象协议:类似函数对象,能够让类实例表现得像函数一样,让每一个函数调用都不同;

上下文管理器协议:对with语句的支持,该协议通过__enter__()和__exit__()实现对资源的清理,确保资源无论在什么情况下都会正常清理。

 

6.理解GIL的局限性

    GIL(Global Interpreter Lock)全局解释器锁:是Python虚拟机上用作互斥线程的一种机制,用于保证任何情况下虚拟机中只会有一个线程被运行,其他线程都处于等待GIL被释放的状态,这保证了对虚拟机内部资源访问的互斥性。

    在单核CPU上,GIL对多线程的执行没有太大影响,因为单核上的多线程本质上就是顺序执行的。对于多核CPU来说,多线程并不能明显提升效率,甚至在频繁IO操作的情况下,由于存在需要多次释放和申请GIL的情形,效率反而会下降。

 

7.对象的管理与垃圾回收

    Python管理内存的方式:使用计数器(Reference counting)的方式来管理内存中的对象,针对每一个对象维护一个引用计数值来表示该对象当前有多少个引用。当其他对象引用该对象时,其引用计数会增加1,而删除一个对当前对象的引用,其引用计数会减1。只有当引用计数的值为0时该对象才会被垃圾收集器回收,因为它表示这个对象不再被其他对象引用,是个不可达对象。引用计数算法最明显的缺点是无法解决循环引用的问题,即两个对象相互引用。

    对于由循环引用而导致的内存泄露的情况,Python自带一个gc模块,用来跟踪对象的”入引用(incoming reference)“和”出引用(outgoing reference)“,并找出复杂数据结构之间的循环引用,同时回收内存垃圾。触发垃圾回收的两种方式:一种是通过显示地调用gc.collect()进行垃圾回收,还有一种是在创建新的对象为其分配内存的时候,检查threshold阈值,当对象的数量超过threshold时自动进行垃圾回收。

    gc.garbage返回的是由于循环引用而产生的不可达的垃圾对象的列表,输出为空表示内存中此时不存在垃圾对象。

    gc.collect显示所有收集和销毁的对象的数目。

 

 

 

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: