Python编码及项目相关建议

Python编码及项目相关建议

遵循良好的编码风格,可以有效的提高代码的可读性,降低出错几率和维护难度。在团队开发中,使用(尽量)统一的编码风格,还可以降低沟通成本。

网上有很多版本的编码规范,基本上都是遵循 PEP8 的规范:

除了手动遵循规范,还有很多有用的工具:

  • IntelliJ IDEA 和 PyCharm 的格式化代码功能
  • Google 开源的 Python 文件格式化工具:github.com/google/yapf
  • pyflakes, pylint 等工具及各种编辑器的插件

缩进

  • 不要使用 tab 缩进
  • 使用任何编辑器写 Python,请把一个 tab 展开为 4 个空格
  • 绝对不要混用 tab 和空格,否则容易出现 IndentationError

空格

  • 在 list, dict, tuple, set, 参数列表的 , 后面加一个空格
  • 在 dict 的 : 后面加一个空格
  • 在注释符号 # 后面加一个空格,但是 #!/usr/bin/python# 后不能有空格
  • 操作符两端加一个空格,如 +, -, *, /, |, &, =
  • 接上一条,在参数列表里的 = 两端不需要空格
  • 括号((), {}, [])内的两端不需要空格

空行

  • function 和 class 顶上两个空行
  • class 的 method 之间一个空行
  • 函数内逻辑无关的段落之间空一行,不要过度使用空行
  • 不要把多个语句写在一行,然后用 ; 隔开
  • if/for/while 语句中,即使执行语句只有一句,也要另起一行

    def add_feedback(self,
                     user_id=None,
                     content="",
                     source=-1,
                     app_version=""):
        if not user_id:
            return dict(ret=1, msg=u"缺少反馈人信息")
        if not content:
            return dict(ret=1, msg=u"请填写反馈信息")
        if source == -1:
            log.error(u"add feedback: 缺少来源信息")
            return dict(ret=1, msg=u"请求有误,请联系管理员")
        if not app_version:
            log.error(u"add feedback: 缺少app版本信息")
            return dict(ret=1, msg=u"请求有误,请联系管理员")
    
        try:
            Feedback.objects.create(
                user_id=user_id,
                content=content,
                source=source,
                app_version=app_version)
            return dict(ret=0)
        except Exception as e:
            log.error("new feedback error: %s" % str(e))
            return dict(ret=1, msg=u"服务器错误")

换行

  • 每一行代码控制在 80 字符以内
  • 使用 \() 控制换行,举例:

    def get_customer_statics_list(self,
                                  year=None,
                                  month=None,
                                  sale_id=None,
                                  department_id=None,
                                  business_id=None,
                                  is_superuser=False):
        new_contract_finances = ContractFinance.objects \
                .filter(contract_detail_id__in=new_contract_detail_ids) \
                .filter(sale_user_id__in=sale_ids) \
                .filter(business_id__in=filter_business_ids) \
                .all()
    
    text = ('Long strings can be made up '
            'of several shorter strings.')

命名

  • 使用有意义的,英文单词或词组,绝对不要使用汉语拼音
  • package/module 名中不要出现 -
  • 各种类型的命名规范:

    TypePublicInternal
    Moduleslower_with_under_lower_with_under
    Packageslower_with_under
    ClassesCapWords_CapWords
    ExceptionsCapWords
    Functionslower_with_under()_lower_with_under()
    Global/Class ConstantsCAPS_WITH_UNDER_CAPS_WITH_UNDER
    Global/Class Variableslower_with_under_lower_with_under
    Instance Variableslower_with_under_lower_with_under (protected) or __lower_with_under (private)
    Method Nameslower_with_under()_lower_with_under() (protected) or __lower_with_under() (private)
    Function/Method Parameterslower_with_under
    Local Variableslower_with_under

import

  • 所有 import 尽量放在文件开头,在 docstring 下面,其他变量定义的上面
  • 不要使用 from foo imort *
  • import 需要分组,每组之间一个空行,每个分组内的顺序尽量采用字典序,分组顺序是:
    1. 标准库
    2. 第三方库
    3. 本项目的 package 和 module
  • 不要使用隐式的相对导入(implicit relative imports),可是使用显示的相对导入(explicit relative imports),如 from ..utils import validator,最好使用全路径导入(absolute imports)
  • 对于不同的 package,一个 import 单独一行,同一个 package/module 下的内容可以写一起:

    
    # bad
    
    import sys, os, time
    
    
    # good
    
    import os
    import sys
    import time
    
    
    # ok
    
    from flask import Flask, render_template, jsonify
    
    from kpi.models import (
        SaleYearKpi, SaleMonthKpi, SaleWeekKpi,
        BusinessYearKpi, DepartmentYearKpi, DepartmentMonthKpi,
    )
  • 为了避免可能出现的命名冲突,可以使用 as 或导入上一级命名空间
  • 不要出现循环导入(cyclic import)

注释

  • 文档字符串 docstring, 是 package, module, class, method, function 级别的注释,可以通过 __doc__ 成员访问到,注释内容在一对 """ 符号之间
  • function, method 的文档字符串应当描述其功能、输入参数、返回值,如果有复杂的算法和实现,也需要写清楚
  • 不要写错误的注释,不要无谓的注释

    
    # bad 无谓的注释
    
    x = x + 1       # increase x by 1
    
    
    # bad 错误的注释
    
    x = x - 1       # increase x by 1
  • 优先使用英文写注释,英文不好全部写中文,否则更加看不懂

异常

  • 不要轻易使用 try/except
  • except 后面需要指定捕捉的异常,裸露的 except 会捕捉所有异常,意味着会隐藏潜在的问题
  • 可以有多个 except 语句,捕捉多种异常,分别做异常处理
  • 使用 finally 子句来处理一些收尾操作
  • try/except 里的内容不要太多,只在可能抛出异常的地方使用,如:

    
    # bad
    
    try:
        user = User()
        user.name = "leon"
        user.age = int(age) # 可能抛出异常
        user.created_at = datetime.datetime.utcnow()
    
        db.session.add(user)
        db.session.commit() # 可能抛出异常
    except:
        db.session.rollback()
    
    
    # better
    
    try:
        age = int(age)
    except (TypeError, ValueError):
        return # 或别的操作
    
    user = User()
    user.name = "leon"
    user.age = age
    user.created_at = datetime.datetime.utcnow()
    db.session.add(user)
    
    try:
        db.session.commit()
    except sqlalchemy.exc.SQLAlchemyError: # 或者更具体的异常
        db.session.rollback()
    finally:
        db.session.close()

对象

  • 关于对象属性操作

    
    # 获取对象属性值
    
    if getattr(obj, 'name', None):
        return obj.name
    
    
    # 判断对象是否有某种属性并返回属性值
    
    if hasattr(obj, 'name') and obj.name:
        return obj.name

字符串

  • 使用字符串的 join 方法拼接字符串
  • 使用字符串类型的方法,而不是 string 模块的方法
  • 使用 startswithendswith 方法比较前缀和后缀
  • 使用 format 方法格式化字符串

比较

  • 空的 list, str, tuple, set, dict0, 0.0, None 都是 False
  • 使用 if some_list 而不是 if len(some_list) 判断某个 list 是否为空,其他类型同理
  • 使用 isis not 与单例(如 None)进行比较,而不是用 ==!=
  • 使用 if a is not None 而不是 if not a is None
  • isinstance 而不是 type 判断类型
  • 不要用 ==!=TrueFalse 比较(除非有特殊情况,如在 sqlalchemy 中可能用到)
  • 使用 in 操作:

    1. key in dict 而不是 dict.has_key()

      
      # bad
      
      if d.has_key(k):
          do_something()
      
      
      # good
      
      if k in d:
          do_something()
    2. set 加速 “存在性” 检查,list 的查找是线性的,复杂度 O(n),set 底层是 hash table, 复杂度 O(1),但用 set 需要比 list 更多内存空间

项目结构参考

Flask Web APP
.
├── README.md
├── app
│   ├── __init__.py
│   ├── config.py
│   ├── forms
│   │   ├── __init__.py
│   │   └── order.py
│   ├── globals.py
│   ├── models
│   │   ├── __init__.py
│   │   ├── base.py
│   │   └── hotel.py
│   ├── server.conf -> /path/to/app/etc/server.example.conf
│   ├── tasks
│   │   ├── __init__.py
│   │   └── utils.py
│   ├── utils
│   │   ├── __init__.py
│   │   ├── email.py
│   │   ├── middleware.py
│   │   └── timeutil.py
│   └── views
│       ├── __init__.py
│       └── order.py
├── celery_worker.py
├── docs
│   ├── api
│   │   └── kpi.md
│   └── api_category.md
├── etc
│   ├── server.example.conf
│   ├── supervisor.example.conf
│   └── unicorn.example.py
├── fixtures
│   └── regions.json.zip
├── manage.py
├── requirements.txt
├── scripts
│   └── encrypt.py
└── wsgi.py

关于这个问题,在stackoverflow已有相关的讨论。

关于项目文档

文档有项目说明文档,以及项目安装文档,接口文档。完善的文档可以方便团队协作,节省沟通成本。
CRM项目的文档就比较全。我个人还是比较习惯写文档。

其他

  • 使用列表表达式(list comprehension),字典表达式(dict comprehension, Python 2.7+) 和生成器(generator)
  • dictget 方法可以指定默认值,但有些时候应该用 [] 操作,使得可以抛出 KeyError
  • 使用 for item in list 迭代 list, for index, item in enumerate(list) 迭代 list 并获取下标
  • 使用内建函数 sortedlist.sort 进行排序
  • 适量使用 map, reduce, filterlambda,使用内建的 all, any 处理多个条件的判断
  • 使用 defaultdict (Python 2.5+), Counter(Python 2.7+) 等 “冷门” 但好用的标准库
  • 使用装饰器(decorator)
  • 使用 logging 记录日志,配置好格式和级别
  • 了解 Python 的 Magic Method:A Guide to Python’s Magic Methods, Python 魔术方法指南
  • 阅读优秀的开源代码,如 Flask 框架, Requests for Humans
  • 不要重复造轮子,查看标准库、PyPi、Github、Google等使用现有的优秀的解决方案
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值