Python-编写好代码

注:本文为学习朱雷老师的《Python工匠:案例、技巧与工程实践》一书的总结与心得。

一、变量和注释

        好的变量和注释:   

# 用户输入可能有空格,使用strip()去掉空格
username = extract_username(input_string.strip())

        变量:见名知意,符合规范

        注释:介绍使用场景、目的,及具体的作用

一)基础知识

1、变量常见用法

1)变量解包

把一个可迭代对象(比如列表)的所有成员,一次性赋值给多个变量

情况1:(常用)

usernames = ['piglei', 'raymond']

# 注意:左侧变量的个数必须和待展开的列表长度相等,否则会报错

author, reader = usernames >>> author 'piglei'       

 情况2:假如在赋值语句左侧添加小括号(...),甚至可以一次展开多层嵌套数据

attrs = [1, ['piglei', 100]]
user_id, (username, score) = attrs

情况3:动态解包,用*variables(星号表达式),贪婪捕获)

data = ['piglei', 'apple', 'orange', 'banana', 100]
username, *fruits, score = data

# 结果
>>> username
'piglei'
>>> fruits
['apple', 'orange', 'banana']
>>> score
100

等同于切片赋值:

#1. 动态解包
username, *fruits, score = data
# 2. 切片赋值
username, fruits, score = data[0], data[1:-1], data[-1]

2)单下划线变量名_

3)给变量注明类型

因Python是动态类型语言,使用变量时不需要做任何类型声明。造成了一些阅读上的不清楚。

方法1:在函数文档(docstring)---用:type items:注明了items是个整型列表

def remove_invalid(items):
    """剔除 items 里面无效的元素

    :param items: 待剔除对象
    :type items: 包含整数的列表,[int, ...]
    """

方法2:类型注解,Python的内置功能 ---在变量后添加类型,并用冒号隔开

from typing import List

def remove_invalid(items: List[int]): ➊
    """剔除 items 里面无效的元素"""
    ... ...
4)变量命名原则

1-遵循PEP 8原则(驼峰命名-CamelCase;下划线连接的蛇形命名-snake_case)

2-命名规范:

· 对于普通变量,使用蛇形命名法,比如max_value

· 对于常量,采用全大写字母,使用下划线连接,比如MAX_VALUE

· 如果变量标记为“仅内部使用”,为其增加下划线前缀,比如_local_var

· 当名字与Python关键字冲突时,在变量末尾追加下划线,比如class_

除变量名以外,PEP 8中还有许多其他命名规范,比如类名应该使用驼峰风格(FooClass)、函数应该使用蛇形风格(bar_function)

3-描述性强:可接受的长度范围内,变量名所指向的内容描述得越精确越好

4-要尽量短:为变量命名要结合代码情境和上下文

5-匹配类型:

        匹配布尔值类型的变量名:让读到变量的人觉得它只有“肯定”和“否定”两种可能(is_superuhas_error) 

        匹配int/float类型的变量名:别拿一个名词的复数形式来作为int类型的变量名,比如apples、trips,建议用number_of_apples或trips_count这类复合词来作为int类型的名字。

6-

7-其他技巧

· 在同一段代码内,不要出现多个相似的变量名,比如同时使用users、users1、users3这种序列;

· 可以尝试换词来简化复合变量名,比如用is_special来代替is_not_normal;

2、注释基础知识

注释方式:

1-代码内注释:如#。。。

2-接口注释:如:函数(类)文档

常见错误

1-用注释屏蔽代码:对于不再需要的代码,我们应该直接把它们删掉

2-用注释复述代码:应该尽量提供那些读者无法从代码里读出来的信息。描述代码为什么要这么做,而不是简单复述代码本身。

3-指引性代码?--

4-弄错接口注释的受众:接口文档主要是给函数(或类)的使用者看的,它最主要的存在价值,是让人们不用逐行阅读函数代码,也能很快通过文档知道该如何使用这个函数,以及在使用时有什么注意事项

def resize_image(image, size):
    """将图片缩放到指定尺寸,并返回新的图片。
    注意:当文件超过 5MB 时,请使用resize_big_image()
    :param image: 图片文件对象
    :param size: 包含宽高的元组:(width, height)
    :return: 新图片对象
    """
    
# 无需介绍具体设计

  二)案例:冒泡排序

前:

def magic_bubble_sort(numbers):
    j = len(numbers) - 1  
    while j > 0:
        for i in range(j):
            if numbers[i] % 2 == 0 and numbers[i + 1] % 2 == 1:
                numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
                continue
        elif (numbers[i + 1] % 2 == numbers[i] % 2) and numbers[i] > numbers[i + 1]:
            numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
            continue
    j -= 1
return numbers

后:

def magic_bubble_sort(numbers: List[int]):
    """有魔力的冒泡排序算法,默认所有的偶数都比奇数大
    :param numbers: 需要排序的列表,函数会直接修改原始列表
    """
    stop_position = len(numbers) - 1
    while stop_position > 0:
        for i in range(stop_position):
            current, next_ = numbers[i], numbers[i + 1] ➊
            current_is_even, next_is_even = current % 2 == 0, next_ % 2 == 0
            should_swap = False
            # 交换位置的两个条件:
            # - 前面是偶数,后面是奇数
        # - 前面和后面同为奇数或者偶数,但是前面比后面大
        if current_is_even and not next_is_even:
            should_swap = True
        elif current_is_even == next_is_even and current > next_:
            should_swap = True
        if should_swap:
            numbers[i], numbers[i + 1] = numbers[i + 1], numbers[i]
    stop_position -= 1
return numbers

三)编程建议

1-保持变量一致性

        在使用变量时,你需要保证它在两个方面的一致性:名字一致性与类型一致性

2-变量定义尽量靠近使用

3-定义临时变量提升可读性

5-同一作用域内不要有太多变量

        要减少函数里的变量数量,最直接的方式是给这些变量分组,建立新的模型。如:添加类

class ImportedSummary:
    """保存导入结果摘要的数据类"""

    def __init__(self):
        self.succeeded_count = 0
        self.failed_count = 0

class ImportingUserGroup:
    """用于暂存用户导入处理的数据类"""

    def __init__(self):
        self.duplicated = []
        self.banned = []
        self.normal = []

def import_users_from_file(fp):
    """尝试从文件对象读取用户,然后导入数据库  

    :param fp: 可读文件对象
    :return: 成功与失败的数量
    """
    importing_user_group = ImportingUserGroup()
    for line in fp:
        parsed_user = parse_user(line)
        # …… 进行判断处理,修改上面定义的importing_user_group 变量

    summary = ImportedSummary()
    # …… 读取 importing_user_group,写入数据库并修改成功与失败的数量

    return summary.succeeded_count, summary.failed_count

6-能不定义变量就别定义

        如:return ...

7- 空行也是一种“注释”

        在写代码时,我们可以适当地在代码中插入空行,把代码按不同的逻辑块分隔开,这样能有效提升代码的可读性。

8-先写注释,后写代码。

        每个函数的名称与接口注释(也就是docstring),其实是一种比函数内部代码更为抽象的东西。

二、数值与字符串

一)基础知识

1、数值

在定义数值字面量时,如果数字特别长,可以通过插入_分隔符来让它变得更易读:

#以"千"为单位分隔数字
i = 1_000_000
i + 10
1000010

浮点数精度问题:需要精确的浮点数计算,请考虑使用decimal.Decimal对象来替代普通浮点数

from decimal import Decimal
# 注意:这里的'0.1'和'0.2' 必须是字符串,必须使用字符串来表示数字
Decimal('0.1') + Decimal('0.2')
Decimal('0.3')

布尔值其实也是数字:绝大多数情况下,True和False这两个布尔值可以直接当作1和0来使用

# 此处的表达式i % 2 == 0会返回一个布尔值结果,该结果随后会被当成数字0或1由sum()函数累加求和
count = sum(i % 2 == 0 for i in numbers)

2、字符串

字符串是一种序列类型,可进行遍历、切片等操作,就像访问一个列表对象一样

反转一个字符串,可以使用切片操作或者reversed内置方法:

# 切片最后一个字段使用-1,表示从后往前反序
s[::-1] ➊
'!dlrow ,olleH'

# reversed会返回一个可迭代对象,通过字符串的.join方法可以将它转换为字符串
''.join(reversed(s)) ➋
'!dlrow ,olleH'

字符串格式化:

username, score = 'piglei', 100
# 1. C 语言风格格式化-----不再常用
print('Welcome %s, your score is %d' % (username, score))

# 2. str.format----可重复多个变量
print('Welcome {}, your score is {:d}'.format(username, score))

# 3. f-string,最短最直观----常用
print(f'Welcome {username}, your score is {score:d}')
# 输出:
# Welcome piglei, your score is 100

拼接多个字符串:

1-一些好用,但不常用的方法:
 

# 判断是否只包含数字
'123'.isdigit(), 'foo'.isdigit()
>>>(True, False)

# 按照分隔符sep切分字符串,返回一个包含三个成员的元组:(part_before, sep, part_after)
def extract_value_v2(s):
    # 当 s 包含分隔符 : 时,元组最后一个成员刚好是value
    # 若是没有分隔符,最后一个成员默认是空字符串 ''
    return s.partition(':')[-1]

# 按规则一次性替换多个字符,比调用多次replace方法更快也更简单
s = '明明是中文,却使用了英文标点.'

# 创建替换规则表:',' -> ',', '.' -> '。'
table = s.maketrans(',.', ',。')
s.translate(table)
>>>'明明是中文,却使用了英文标点。'

二)案例

原:

def add_daily_points(user):
    """用户每天完成第一次登录后,为其增加积分"""
    if user.type == 13:
        return
    if user.type == 3:
        user.points += 120
        return
    user.points += 100
    return

新:

#用户每日奖励积分数量
DAILY_POINTS_REWARDS = 100
# VIP 用户每日额外奖励积分数量
VIP_EXTRA_POINTS = 20


from enum import Enum
# 在定义枚举类型时,如果同时继承一些基础类型,比如int、str,
# 枚举类型就能同时充当该基础类型使用。比如在这里,UserType 就可以当作int 使用
class UserType(int, Enum):
    # VIP 用户
    VIP = 3
    # 小黑屋用户
    BANNED = 13


def add_daily_points(user):
    """用户每天完成第一次登录后,为其增加积分"""
    if user.type == UserType.BANNED:
        return
    if user.type == UserType.VIP:
        user.points += DAILY_POINTS_REWARDS + VIP_EXTRA_POINTS
        return
    user.points += DAILY_POINTS_REWARDS
    return

涉及知识点:
枚举类型?

使用模块改写代码:

当你的代码里出现复杂的裸字符串处理逻辑时,请试着问自己一个问题:“目标/源字符串是结构化的且遵循某种格式吗?”

如果答案是肯定的,那么请先寻找是否有对应的开源专有模块,比如处理SQL语句SQLAlchemy处理XMLlxml模块等。如果你要拼接非结构化字符串,也请先考虑使用Jinja2等模板引擎

三)编程建议

1-不必预计算字面量表达式

2-使用特殊数字:“无穷大”:

        float("inf")   ,float("-inf")  -----正负无穷大; float("-inf")<任意数值< float("inf")

def sort_users_inf(users):

    def key_func(username):
        age = users[username]
        # 当年龄为空时,返回正无穷大作为 key,因此就会被排到最后
        return age if age is not None else float('inf')

    return sorted(users.keys(), key=key_func)

users = {"tom": 19, "jenny": 13, "jack": None, "andrew": 43}
print(sort_users_inf(users))
# 输出:
# ['jenny', 'tom', 'andrew', 'jack']

3-改善超长字符串的可读性

三、容器类型

你可能感兴趣的:(python,开发语言)