说明
代码的交互作用不能仅限于人与机器,更应该扩展到人与人,所以才有代码规范这一需求;一段好的代码不仅需要结构简单、功能块分工明确,而且别人能容易读懂。
使用Pycharm等 IDE可以设置自己喜欢的代码风格,这种规范好的代码风格可以帮助新手规避掉很多常识性错误,本文档在此不做叙述(如果有兴趣可以通读python规范.pdf)。接下就“Imports 导⼊”、“注释”、“命名”、“代码结构”4个主题构建规范。
Imports 导⼊规范
格式要求
当没有 from 语句的导入必须分⾏写(习惯按导入模块的首字母排序),形如:
import os
import sys
如果有 from 语句,可以使⽤用如下⽅式,形如:
from subprocess import Popen, PIPE
如果有本地模块,建议使用绝对路径(在服务器中使用相对路径可能会出现意外的错误:如crontab无法识别相对路径的模块),也可以选择将模块添加到sys.path,形如:
sys.path.insert(0, '/DATA/hdfs/lwb/pyspark/ticket/ticket_feature')
from com.spark_dataframe import SimplifyProcess, SparkUdf
注意: 导入模块时不要贪图省事使用通配符 *
位置
导⼊总是位于文件的顶部,在模块注释和⽂文档字符串之后,在模块的全局变量与常量之前,形如:
# -*- coding: utf-8 -*-
"""
模块注释
"""
import sys # 导入
GLOBAL_VARIABLE = 1 # 全局变量(常量)
顺序
导入模块需要按以下顺序分组:
- 标准库导入
- 第三方库导入
- 本地模块导入
注意:一个导入分组间需要加入空行,形如:
import datetime
import sys
from pyspark.sql import SparkSession
from pyspark.sql.functions import max
from pyspark.sql.types import StringType
sys.path.insert(0, '/DATA/hdfs/lwb/pyspark/ticket/ticket_feature')
from com.spark_dataframe import SimplifyProcess, SparkUdf
注释规范
代码块注释
代码块注释通常适用于跟随它们的某些(或全部)代码,并缩进到与代码相同的级别。注释的⾏开头使⽤一个#和⼀个空格。形如:
# 国内机场
airport_list = sc.textFile("hdfs://umecluster/apps/hive/warehouse/"
"dim_db.db/dim_airport_detail") \
.map(lambda x: x.split("\x01")) \
.filter(lambda x: x[9] == "1" and x[16] == "1") \
.map(lambda x: x[0]) \
.collect()
行内注释
⾏内注释是与代码语句同⾏的注释。⾏内注释和代码至少要有两个空格分隔。注释由 # 和⼀个空格开始。形如:
if serializer.is_valid(): # 验证表单 => ⾏内注释
user = serializer.validated_data['user']
文档注释
公共模块,函数,类以及方法需要编写文档说明。非公共的方法没有必要,但是应该 有一个描述方法具体作⽤的注释。形如:
公共模块注释:
"""Return a foobang
Optional plotz says to frobnicate the bizbaz first.
"""
公共方法注释:
def data_list(column, size):
"""
size是滑动窗的大小
:param column: 当前generated_date
:return: 窗内generated_date list
"""
对于单⾏的⽂档说明,尾部的三引号应该和文档在同一⾏,形如:
class UserInfo(models.Model):
"""⽤用户信息表"""
命名规范
命名要尽量有意义,必须使⽤英文单词,不允许使用汉语拼⾳更不能使用汉语拼音和英文单词混
用。
包、模块、类命名
考虑模块名是与文件夹相对应,因此需要考虑文件系统的一些命名规则的,比如Unix系统对大小写敏感,而过长的文件名会影响其在 Windows\Mac\Dos 等系统中的正常使用。
模块应该使用尽可能短的、全小写命名,可以在模块命名时使用下划线以增强可读性。同样包的命名也应该是这样的,虽然其并不鼓励下划线。形如:
ad_stats.py
类名都使用首字母大写开头(Pascal命名风格)的规范,推荐使用驼峰命名法,例如:CapitalizedWords。使用 _单下划线开头的类名为内部使用;使用通配符 *调用类时会自动过滤掉这种仅内部使用类,形如:
from module_name import *
output: ignore class _InnerClass
常量和变量命名
常量
常量通常定义在模块级,通过下划线分隔的全大写字母命名,形如: MAX_OVERFLOW 和TOTAL
全局变量
命名规则为单词全大写,单词之间用 _分割,形如:
NUMBER
COLOR_WRITE
普通变量
命名规则为单词全小写,单词之间用 _分割,形如:
this_var
注意:变量名不应带有类型信息,因为Python是动态类型语言。不推荐的命名形如:
names_list、dict_obj 等
特殊变量
- 实例变量
实例变量的命名建议以 _开头,其他命名规则与普通变量一样,形如:
class DataTransform(object):
def __init__(self, _days):
self._days = _days # 类的实例变量
pass
使用这种方式命名的实例变量是对外公开的,类本身和子类能访问到该实例变量(等效protected)。
- 私有实例变量
私有实例变量的命名建议以 __开头,其他命名规则与普通变量一样,形如:
class DataTransform(object):
def __init__(self, __df):
self.__df = __df # 类的私有实例变量
pass
使用这种方式命名的实例变量仅有类本身能访问,外部访问会报错(等效private)。
- 专有变量
专有变量的命名以__开头,__结尾,这种变量一般为python自有变量,形如:
__doc__
__class__
建议:不要以这种方式命名普通变量,这个是保留字,要满足防御性编程需求。
函数命名
普通函数
普通函数命名和普通变量一样
get_name()
count_number()
还有一类函数以_开头,形如_get_name,这种函数只允许其本身与子类进行访问(等效protected),不能用于 from module import *,其他与普通函数无异。
私有函数
私有函数命名以__开头,其他和普通函数一样
__get_name()
使用这种方式命名的函数仅限于本类使用,子类调用会报错(等效private)。
特殊函数
主要是指 __xxx__
形式的系统保留字命名法。一般是系统定义名字 ,类似 __init__()
。除了作为文档之外,永远不要命这样的名。
代码结构规范
该部分总结了一部分好的编程习惯和一些约定俗成的规定。
异常处理和日志
- 在编写代码时需要培养使用日志的习惯(使用logging而不是print打日志),日志的重要性和灵活性在此不做赘述。
- 不要把所有代码放到try except中, 只捕获会出异常的代码片段. 注意粒度, 不要放入不必要的代码。
- 谨慎使用except Exception捕获所有异常;不要吞掉异常, 处理或抛出, 同时要打异常日志。
函数设计
- 编写函数时控制住函数大小(习惯上函数的大小不超过100行),尽量保证一个函数做一件事
- 抽取反复出现重复的代码到独立函数中;形如下例spark中对列做独热编码:
独热编码是将一列中所有出现的值,构建成新的列;做完编码后将原有列清除,下面的例子是对某一列做独热编码。
col = "adept"
value = ['PVG', 'CAN', 'CTU', 'SZX', 'PEK', 'KMG', 'SHA', 'XIY', 'CKG', 'HGH']
_df = _df.withColumn("adept_PVG"),
when(_df["adept"] == "PVG", lit(1)).otherwise(lit(0))) \
.withColumn("adept_CAN"),
when(_df["adept"] == "CAN", lit(1)).otherwise(lit(0))) \
...
...
.withColumn("adept_HGH"),
when(_df["adept"] == "HGH", lit(1)).otherwise(lit(0)))
_df = _df.select([c for c in _df.columns if c not in {"adept"}])
在编码过程中同一个操作重复多次,可以考虑将该操作抽取到独立函数中,下面的oneHot_encode函数就是对上述操作的抽取整合。
def oneHot_encode(_df, col, value=None):
"""
指定的列做onehot编码
:param _df:
:param col: 指定列
:param value: 指定列中值的集合
:return: 去掉做编码操作的原有列
"""
if value is None:
rdd_tmp = _df.select(col).dropDuplicates().collect()
addCol = [row[col] for row in rdd_tmp]
else:
addCol = value
for column_ in addCol:
# 新建onehot列
_df = _df.withColumn(col + "_" + str(column_), when(_df[col] == column_, lit(1)).otherwise(lit(0)))
_df = _df.select([c for c in _df.columns if c not in {col}])
return _df
- 多个函数间相似度较高可以考虑抽象提取封装
- 切记,函数设计不能过分追求通用
其他
- 空行用以将逻辑相关的代码段分隔开,以提高可读性。两个逻辑代码块之间应该保留一个空行,形如:
def dataSet():
# 国内机场
airport_list = sc.textFile("hdfs://umecluster/apps/hive/warehouse/"
"dim_db.db/dim_airport_detail") \
...
...
.collect()
partion = YESTERDAY
# 计划航班数据
days = Stock.data_list(partion, 7)
...
...
plan_df = plan_df.select(["flightnumber", "schdate", "adept", "adest", "ptdLabel"])
# 库存数据
date = Stock.data_list(partion, 8)
host_ = "hdfs://umecluster/user/ume/ticket/data/flight_inv/legcabininfo/"
...
...
stock_df = stock_df.filter(stock_df.legclazz == "Y")
return stock_df, plan_df
- 熟悉标准库, 减少土制轮子的概率, 可以少写代码,提高代码执行效率
- 熟悉框架/优秀第三方库提供的接口及特性, 原因同上