快乐学习
前程无忧、中华英才非你莫属!

让你少走弯路的34条建议(Python)

前言

设计模式的目标:首先对象要被创建,然后被组建成有用的结构,然后让他们进行交互.

一般来说设计模式分为三大类:

  1. 创建型模式:抽象工厂模式,创建者模式,原型模式,单例模式。

  2. 结构型模式:适配器模式,桥模式,组合模式,装饰模式,外观模式,享元模式,代理模式。

  3. 行为型模式:解释器模式,责任链模式,命令模式,迭代器模式,中介者模式,备忘录模式,观察者模式,状态模式,策略模式,访问者模式,模板方法模式

创建型模式中较有用的(创建者和原型模式)

通过每种模式,都用demo 跑了跑,工厂模式对于大点的项目肯定是核心,对于python短平快的项目肯定不适用。

因为Python 的模块是天然的单例,所以 单例我们不考虑,所以创建型模式中较有用的(创建者和原型模式)。

创建者: 建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节.

本身Python的底层还是很复杂,但是优于它的胶水性,语法简洁性,把内部的复杂细节隐藏起来。
so ,创建者(建造者)模式还是很符合Python 的。

原型模式: 通过copy函数,直接可以复制另外一个对象出来,不用重新的初始化。

结构型模式中较有用的(组合模式,装饰模式)

其他结构模式只是为了解耦,解耦之后还得需要维护更多的接口、更多的中间层的类。

短平快的项目以扩展功能为主,最有用的就是装饰模式
接下来就是组合模式,因为Python 可以是一子多父的存在,父类之间又相互继承,如果弄的不好那可会一团乱。
组合模式,就可以其他类直接作为一个类的属性来处理。用来处理各层次之间的关系。就不会乱了~

*行为型模式中(有用的、状态、观察者、模板)**

个人觉得有用的模板方法比较有用,先抽象的定义好具体的功能的运行逻辑,然后用子类进行实现。
用于同一套流程,不同的功能效果。

状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题,将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。

但是状态责任链模式非常的像,目前为止不建议使用责任链,原因如下:

状态模式是让各个状态对象自己知道其下一个处理的对象是谁,即在编译时便设定。相当于If ,else-if,else-if……, 设计思路是把逻辑判断转移到各个State类的内部实现(相当于If,else If),执行时客户端通过调用环境—Context类的方法来间接执行状态类的行为,客户端不直接和状态交互。而职责链模式中的各个对象并不指定其下一个处理的对象到底是谁,只有在客户端才设定某个类型的链条,请求发出后穿越链条,直到被某个职责类处理或者链条结束。本质相当于swich-case,


34条进阶之路

建议1:函数设计要尽量短小,嵌套层次不宜过深,不得超过3层

说明:因为这样不需要上下拉动滚动条就能获得整体感观,而不是来回翻动屏幕去寻找某个变量或者某条逻辑判断等

建议2 函数申明应该做到合理、简单、易于使用。

说明:除了函数名能够正确反映其大体功能外,参数的设计也应该简洁明了,参数个数不宜太多。
参数太多带来的弊端是:调用者需要花费更多的时间去理解每个参数的意思,测试人员需要花费更多的精力来设计测试用例,以确保参数的组合能够有合理的输出,这使覆盖测试的难度大大增加。因此函数参数设计最好经过深思熟虑。

建议3 函数参数设计应该考虑向下兼容。建议3 函数参数设计应该考虑向下兼容。

比如下个函数版本加入日志处理,可以加入默认参数来到达向下兼容

建议4 一个函数只做一件事,尽量保证函数语句粒度的一致性。

有3个不同的任务:获取网页内容、查找指定网页内容、发送邮件。
要保证一个函数只做一件事,就要尽量保证抽象层级的一致性,所有的语句尽量在一个粒度上

建议5:将常量集中到一个文件

Python并未提供如C/C++/Java一样的const修饰符,换言之,python中没有常量,python程序一般通过约定俗成的变量名全大写的形式表示这是一个常量。然而这种方式并没有真正实现常量,其对应的值仍然可以被改变。后来,python提供了新的方法实现常量:即通过自定义类实现常量。这要求符合“命名全部为大写”和“值一旦被绑定便不可再修改”这两个条件。
用自定义类实现常量,例如,如下写了一个const.py文件

# -*- coding: utf-8 -*-
# python 3.x
# Filename:const.py
# 定义一个常量类实现常量的功能
#
# 该类定义了一个方法__setattr()__,和一个异常ConstError, ConstError类继承
# 自类TypeError. 通过调用类自带的字典__dict__, 判断定义的常量是否包含在字典
# 中。如果字典中包含此变量,将抛出异常,否则,给新创建的常量赋值。
# 最后两行代码的作用是把const类注册到sys.modules这个全局字典中。
class _const:
    class ConstError(TypeError):pass
    def __setattr__(self,name,value):
        if name in self.__dict__:
            raise self.ConstError("Can't rebind const (%s)" %name)
        self.__dict__[name]=value

import sys
sys.modules[__name__]=_const()

如果上面对应的模块名为const,使用的时候只要 import const,便可以直接定义常量了,例如
# test.py
import const
const.PI=3.14
print(const.PI)

我们运行test.py,就可打印出常量的值,如果再次修改const.PI=3.15,则会抛出const.constError异常。
其中,sys.modules[name]=_const()这条语句将系统已经加载的模块列表中的const替换为_const()实例。这样,在整个工程中使用的常量都定义在一个文件中,如下

from project.utils import const
const.PI=3.14

python中import module和from module import的区别
import module 只是将module的那么加入到目标文件的局部字典中,不需要对module进行解释
from module import xx 需要将module解释后加载至内存中,再将相应部分加入目标文件的局部字典中
python模块中的代码仅在首次被import时被执行一次。
如果我们定义常量的地方和类文件定义在一个文件中,我们可以直接实例一个对象,如下
# -*- coding: utf-8 -*-
# python 3.x
# Filename:const.py
# 定义一个常量类实现常量的功能
#
# 该类定义了一个方法__setattr()__,和一个异常ConstError, ConstError类继承
# 自类TypeError. 通过调用类自带的字典__dict__, 判断定义的常量是否包含在字典
# 中。如果字典中包含此变量,将抛出异常,否则,给新创建的常量赋值。
# 最后两行代码的作用是把const类注册到sys.modules这个全局字典中。
class _const:
    class ConstError(TypeError):pass
    def __setattr__(self,name,value):
        if name in self.__dict__:
            raise self.ConstError("Can't rebind const (%s)" %name)
        self.__dict__[name]=value

const = _const()
const.PI=3.14
print(const.PI)

建议6:数据交换值的时候不推荐使用中间变量

Python 著名的cookbook书籍中的数据交换案例:x,y=y,x

建议7:充分利用Lazy evaluation的特性

Lazy evaluation常被译为“延迟计算”或“惰性计算”,指的是仅仅在真正需要执行的时候才计算表达式的值。充分利用Lazy evaluation的特性带来的好处主要体现在以下两个方面:

1)避免不必要的计算,带来性能上的提升。对于Python中的条件表达式if x and y,在x为false的情况下y表达式的值将不再计算。而对于if x or y,当x的值为true的时候将直接返回,不再计算y的值
因此在编程过程中,如果对于or条件表达式应该将值为真可能性较高的变量写在or的前面,而and则应该推后。

2)节省空间,使得无限循环的数据结构成为可能。Python中最典型的使用延迟计算的例子就是生成器表达式了,它仅在每次需要计算的时候才通过yield产生所需要的元素。

建议8:不推荐使用type来进行类型检查,可以使用isinstance()函数来检测。

说明:因为type 搞不定继承的类

建议9:尽量转换为浮点类型后再做除法.

说明:这是python官方的Bug,他们不承认,不想改罢了~,因为会损失精度的,小数点后几位的可能就没了。

建议10:警惕eval()的安全漏洞, 商业软件的话,就不要使用此函数。

说明,非得用的时候可用安全性更好的ast.literal_eval替代

建议11:使用enumerate()获取序列迭代的索引和值

说明:因为它代码清晰简洁,可读性最好。函数enumerate()是在Python2.3中引入的,主要是为了解决在循环中获取索引以及对应值的问题。它具有一定的惰性(lazy),每次仅在需要的时候才会产生一个(index,item)对。

建议12:分清==与is的适用场景,判断两个对象相等应该使用==而不是is。

说明:is的作用是用来检查对象的标示符是否一致的,也就是比较两个对象在内存中是否拥有同一块内存空间,它并不适合用来判断两个字符串是否相等。x is y仅当x和y是同一个对象的时候才返回True,x is y 基本相当于id(x) == id(y)

建议13:考虑兼容性,尽可能使用Unicode

说明:Unicode提供了不同编码系统之间字符转换的桥梁,要避免令人头疼的乱码或者避免UnicodeDecodeError以及UnicodeEncodeError等错误,需要弄清楚字符所采用的编码方式以及正确的解码方法。对于中文字符,为了做到不同系统之间的兼容,建议直接使用Unicode表示方式。所以乱码的时候,都是通过先把乱码转换成Unicode,在转换成目标编码。

想要不乱码,可以遵守的规则,和注意的地方:

  1. 文件存储为utf-8格式,
  2. 编码声明为utf-8,# encoding:utf-8
  3. 出现汉字的地方前面加 u 不同编码之间不能直接转换,要经过unicode中间跳转
  4. cmd 下不支持utf-8编码
  5. raw_input提示字符串只能为gbk编码
  6. 文件打开时指定utf8

建议14:有节制地使用from...import语句,优先使用import

  • 一般情况下尽量优先使用import a形式,如访问B时需要使用a.B的形式。
  • 有节制地使用from a import B形式,可以直接访问B。
  • 尽量避免使用from a import *,因为这会污染命名空间,并且无法清晰地表示导入了哪些对象。
  • 为什么在使用import的时候要注意以上几点呢?在回答这个问题之前先来简单了解一下Python的import机制。Python在初始化运行环境的时候会预先加载一批内建模块到内存中,这些模块相关的信息被存放在sys.modules中

建议15:使用with自动关闭资源

建议16:使用else子句简化循环(以及异常处理)

建议17:建议使用 if list1:xxxx # 来判断value is not empty

建议18:连接字符串应优先使用join而不是+

建议19:格式化字符串时尽量使用.format方式而不是%

建议20:警惕默认参数潜在的问题

如果不想让默认参数所指向的对象在所有的函数调用中被共享,
而是在函数调用的过程中动态生成,可以在定义的时候使用None对象作为占位符。

建议21:利用模块实现单例模式

模块采用的其实是天然的单例的实现方式。

  • 所有的变量都会绑定到模块。
  • 模块只初始化一次。
  • import机制是线程安全的(保证了在并发状态下模块也只有一个实例)当我们想要实现一个游戏世界时,只需简单地创建World.py就可以了。

建议22:用mixin模式让程序更加灵活

import mixins
def staff():
    people=People()
    bases=[]
    for i in config.checked():
        bases.append(getattr(mixins,i))
    people.__bases__+=tuple(bases)
    rerurn people

建议23:用发布订阅模式实现松耦合

建议24:用状态模式美化代码

https://blog.csdn.net/hbu_pig/article/details/80808896

建议25、消灭if1:通过逻辑 (与或非) 合并判断表达式,来重构if.

建议26、消灭if2: 字典dict 取值使用get或setdefault函数。

字典取值使用get函数:
如果 key 存在于字典中则返回 key 的值,否则返回 default。
如果 default 未给出则默认为 None,因而此方法绝不会引发 KeyError。

字典取值使用setdefault函数:
如果字典存在键 key ,返回它的值。如果不存在,插入值为 default 的键 key ,并返回 default 。 
default 默认为 None。

建议27:要刻意的减少代码行数,从列表中取出多个值,用一行代码来解决

# A: 不想取出的值,可以用_来占位,多余的_ 可以删掉。
data = [ 'ACME', 50, 91.1, (2012, 12, 21) ]
_, shares, price, _ = data

# B: 多余的数据,用* ,email 后面的数据就会形成一个列表数据。
record = ('Dave', 'dave@example.com', '773-555-1212', '847-555-1212')
name, email, *phone_numbers = record

建议28: 同时遍历多个列表,减少遍历代码。

建议29: 某些处理数据的功能,建议热门处理数据函数库,Numpy、Pandas.

建议30:用统一配置文件进行管理,yaml、ini,来满足函数参数需要经常改动来满足不同的需求,

建议31: 项目级别开发,必须要使用logging 在关键的地方,记录日志信息。

记录程序的运行时间,描述信息,报错信息,以及特定的上下文环境信息。

建议32: 子类初始化父类信息,统一使用super().init 函数。

  1. 越靠上的基类,_init 中别写参数,或者写可变以及关键字参数来代替。
  2. 避免多重继承,使用一些设计模式来替代
  3. 多继承显示初始化,类名._init 多继承会初始化多遍,影响性能。

建议33: 封装常用的繁琐操作和重复操作(数据库的操作、文件的查找,读写等等)

建议34: 小数据量用列表生成式,大数据量用生成器。

打赏
赞(2) 打赏
未经允许不得转载:同乐学堂 » 让你少走弯路的34条建议(Python)

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!