精华内容
下载资源
问答
  • 序言 Python是当今最流行的语言之一。相对较新的领域如数据科学、人工...对此,小编整理了一份适合所有Python学习者的资料:Python代码整洁之道。 结语 当然关于 Python 中的规范还有很多很多,建议大家参考 Python 之

    前言

    Python是当今最流行的语言之一。相对较新的领域如数据科学、人工智能、机器人和数据分析,以及传统的专业如Web开发和科学研究等,都在拥抱Python。随着时间的推移,Python有可能会发展成一门基础学科,所以,学好Python是在一些领域生存发展的必备技能。

    对于用Python这样的动态语言编写代码的程序员来说,确保代码的高质量和无错误变得越来越重要。作为一名Python开发人员,你希望确保正在构建的软件能够让用户满意,而不会超出预算或无法发布。

    目前Python中缺乏的是代码一致性、模式以及开发人员对良好Python代码的理解。对于每个Python程序员,良好的Python代码都有不同的含义。出现这种情况的原因可能是Python被用于如此多的领域,以至于开发人员很难就特定的模式达成一致。

    Python是一种简单的语言,但是很难写出好的代码,因为目前可以教我们写出更好的Python代码的资源并不多见。如果你想熟练地编写整洁的Python代码,并可以成功地将这些原则应用到自己的Python项目中,小编为你推荐你阅读 《Python代码整洁之道:编写优雅的代码》 一书。

    在这里插入图片描述

    在这里插入图片描述
    详情请参见

    本书的主要目的是为不同级别的Python开发人员提供技巧,以便他们能够编写更好的Python软件和程序。无论你在哪个领域使用Python,本书都可以为你提供各种各样的技巧。本书涵盖了从基础到高级的所有级别的Python知识,并向你展示了如何使代码更符合Python的风格。

    请记住,编写软件不仅是一门科学,而且还是一门艺术,本书将教你如何成为一名更好的Python程序员。

    精彩抢先看

    内容简介

    探索使用Python编写代码的正确方法。本书提供了构建无错误和强壮的Python项目所需的技巧和技术。

    为了讲授如何编写更好的代码,本书首先介绍理解代码格式化和代码注释的重要性,以及利用内置数据结构和Python字典提高可维护性,使用模块和元类有效地组织代码;

    然后深入介绍Python语言的新特性,并教会读者如何有效地使用它们;接下来,将深入介绍一些关键概念,如异步编程、Python数据类型、类型提示和路径处理等,并讲述调试、单元测试和集成测试的技巧,以保证代码可以投入生产;

    最后在附录中介绍了一些有助于加快开发速度和提高代码质量的优秀Python工具。

    阅读本书之后,你将会熟练地编写整洁的Python代码,并可以成功地将这些原则应用到自己的Python项目中。

    通过阅读本书,你将学到以下内容

    (1)如何编写整洁的Python代码。

    (2)Python的数据结构及特点。

    (3)Python中的函数、类和模块(模块在很多书中没有提及或只是简单提及,本书有着较详细的讲解)。

    (4)装饰器、生成器、迭代器和上下文管理器的作用和使用场景。

    (5)Python 3.x中的一些新特性,如async及协程、类型标注等。

    (6)调试和单元测试的一些工具。

    作者简介

    Sunil Kapil在过去的10年中一直从事软件开发工作,用Python和其他几种语言编写代码,主要涉及Web和移动端服务的软件开发。他开发、部署并维护了被数百万用户喜爱和使用的各种项目,这些项目是与来自不同专业环境的团队合作完成的,涉及世界著名的软件公司。他也是开源的热情倡导者,并持续贡献Zulip Chat和Black等项目。他还与非营利组织合作,并以志愿者的身份为其软件项目做出贡献。

    译者介绍

    连少华,先后就职于中兴通讯、深交所、金证股份等知名公司和机构,热衷于软件事业,技术栈广泛,涉及C++、C#、Java、Python、Golang等,对架构设计和底层技术有深入的理解和实践,曾经给国外的一些开源库提交过bug并贡献过代码。在CSDN论坛担任过5年多的C++小版的版主和C/C++大版的版主。译有《C++代码整洁之道》,现致力于大数据平台的设计与开发。

    目录

    ●第1章 关于Python的思考1

    1.1 编写Python代码1

    1.1.1 命名2

    1.1.2 代码中的表达式和语句5

    1.1.3 拥抱Python编写代码的方式8

    1.2 使用文档字符串14

    1.2.1 模块级文档字符串17

    1.2.2 使类文档字符串具有描述性17

    1.2.3 函数文档字符串18

    1.2.4 一些有用的文档字符串工具19

    1.3 编写Python的控制结构20

    1.3.1 使用列表推导20

    1.3.2 不要使用复杂的列表推导21

    1.3.3 应该使用lambda吗23

    1.3.4 何时使用生成器与何时使用列表推导23

    1.3.5 为什么不要在循环中使用else24

    1.3.6 为什么range函数在Python 3中更好27

    1.4 引发异常28

    1.4.1 习惯引发异常28

    1.4.2 使用finally来处理异常30

    1.4.3 创建自己的异常类31

    1.4.4 只处理特定的异常32

    1.4.5 小心第三方的异常34

    1.4.6try最少的代码块35

    1.5 小结36

    ●第2章 数据结构38

    2.1 常用数据结构38

    2.1.1 使用集合38

    2.1.2 返回和访问数据时使用namedtuple40

    2.1.3 理解str、Unicode和byte43

    2.1.4 谨慎使用列表,优先使用生成器44

    2.1.5 使用zip处理列表47

    2.1.6 使用Python的内置函数48

    2.2 使用字典50

    2.2.1 何时使用字典与何时使用其他数据结构51

    2.2.2collections51

    2.2.3 有序字典、默认字典、普通字典54

    2.2.4 使用字典的switch语句55

    2.2.5 合并两个字典的方法56

    2.2.6 优雅地打印字典57

    2.3 小结58

    ●第3章 编写更好的函数和类59

    3.1 函数59

    3.1.1 编写小函数60

    3.1.2 返回生成器61

    3.1.3 引发异常替代返回None63

    3.1.4 使用默认参数和关键字参数64

    3.1.5 不要显式地返回None66

    3.1.6 编写函数时注意防御68

    3.1.7 单独使用lambda表达式70

    3.2 类72

    3.2.1 类的大小72

    3.2.2 类结构73

    3.2.3 正确地使用@property75

    3.2.4 什么时候使用静态方法77

    3.2.5 继承抽象类79

    3.2.6 使用@classmethod来访问类的状态80

    3.2.7 使用公有属性代替私有属性81

    3.3 小结83

    ●第4章 使用模块和元类84

    4.1 模块和元类84

    4.2 如何使用模块组织代码86

    4.3 使用__init__文件88

    4.4 以正确的方式从模块导入函数和类90

    4.5 何时使用元类92

    4.6 使用__new__方法验证子类93

    4.7__slots__的用途95

    4.8 使用元类改变类的行为98

    4.9Python描述符100

    4.10 小结102

    ●第5章 装饰器和上下文管理器104

    5.1 装饰器105

    5.1.1 装饰器及其作用105

    5.1.2 理解装饰器106

    5.1.3 使用装饰器更改行为108

    5.1.4 同时使用多个装饰器110

    5.1.5 使用带参数的装饰器111

    5.1.6 考虑使用装饰器库112

    5.1.7 用于维护状态和验证参数的类装饰器114

    5.2 上下文管理器117

    5.2.1 上下文管理器及用途117

    5.2.2 理解上下文管理器119

    5.2.3 使用contextlib创建上下文管理器120

    5.2.4 上下文管理器的示例121

    5.3 小结124

    ●第6章 生成器与迭代器125

    6.1 使用生成器和迭代器125

    6.1.1 理解迭代器125

    6.1.2 什么是生成器128

    6.1.3 何时使用迭代器129

    6.1.4 使用itertools130

    6.1.5 为什么生成器非常有用132

    6.1.6 列表推导和迭代器133

    6.2 使用yield关键字133

    6.2.1yield from135

    6.2.2yield相比数据结构更快135

    6.3 小结136

    ●第7章 使用Python的新特性137

    7.1 异步编程137

    7.1.1Python中的async138

    7.1.2asyncio是如何工作的141

    7.1.3 异步生成器151

    7.2 类型标注159

    7.2.1Python中的类型160

    7.2.2typing模块160

    7.2.3 类型检查会影响性能吗163

    7.2.4 类型标注如何帮助编写更好的代码163

    7.2.5typing的陷阱163

    7.3super()方法164

    7.4 类型提示164

    7.5 使用pathlib处理路径164

    7.6print()现在是一个函数165

    7.7f-string165

    7.8 关键字参数166

    7.9 保持字典数据的顺序166

    7.10 迭代解包166

    7.11 小结167

    ●第8章 调试和测试Python代码168

    8.1 调试168

    8.1.1 调试工具169

    8.1.2breakpoint172

    8.1.3 在产品代码中使用logging模块替代print172

    8.1.4 使用metrics库来分析性能瓶颈177

    8.1.5IPython有什么帮助178

    8.2 测试179

    8.2.1 测试非常重要179

    8.2.2Pytest和UnitTest180

    8.2.3 属性测试184

    8.2.4 生成测试报告184

    8.2.5 自动化单元测试185

    8.2.6 让代码为生产做好准备186

    8.2.7 在Python中执行单元和集成测试186

    8.3 小结189

    附录 一些很棒的Python工具190

    展开全文
  • 对此呢,我特意收集了一些适合所有学习 Python 的人,代码整洁之道。 写出 Pythonic 代码 谈到规范首先想到就是 Python 有名的 PEP8 代码规范文档,它定义了编写Pythonic代码的最佳实践。可以在 https://www....

    很多新手在开始学一门新的语言的时候,往往会忽视一些不应该忽视的细节,比如变量命名和函数命名以及注释等一些内容的规范性,久而久之养成了一种习惯。对此呢,我特意收集了一些适合所有学习 Python 的人,代码整洁之道。

    写出 Pythonic 代码

    谈到规范首先想到就是 Python 有名的 PEP8 代码规范文档,它定义了编写Pythonic代码的最佳实践。可以在 https://www.python.org/dev/peps/pep-0008/ 上查看。但是真正去仔细研究学习这些规范的朋友并不是很多,对此呢这篇文章摘选一些比较常用的代码整洁和规范的技巧和方法,下面让我们一起来学习吧!

    命名

    所有的编程语言都有变量、函数、类等的命名约定,以美之称的 Python 当然更建议使用命名约定。 接下来就针对类、函数、方法等等内容进行学习。

    变量和函数

    使用小写字母命名函数和变量,并用下划线分隔单词,提高代码可读性。

    变量的声明
    names = "Python" #变量名 
    namejob_title = "Software Engineer" #带有下划线的变量名
    populated_countries_list = []  #带有下划线的变量名 

    还应该考虑在代码中使用非 Python 内置方法名,如果使用 Python 中内置方法名请使用一个或两个下划线()。

    _books = {}# 变量名私有化
    __dict = []# 防止python内置库中的名称混淆

    那如何选择是用_还是__呢?

    如果不希望外部类访问该变量,应该使用一个下划线(_)作为类的内部变量的前缀。如果要定义的私有变量名称是 Python 中的关键字如 dict 就要使用(__)。

    函数的声明
    def get_data(): 
        pass
    def calculate_tax_data():
        pass

    函数的声明和变量一样也是通过小写字母和单下划线进行连接。
    当然对于函数私有化也是和声明变量类似。

    def _get_data():
       pass

    函数的开头使用单下划线,将其进行私有化。对于使用 Pyton 中的关键字来进行命名的函数
    要使用双下划线。

    def __path():
        pass

    除了遵循这些命名规则之外,使用清晰易懂的变量名和很重要。

    函数名规范
    # Wrong Way
    def get_user_info(id):
       db = get_db_connection()
       user = execute_query_for_user(id)
       return user
    
    # Right way
    def get_user_by(user_id):
        db = get_db_connection()
        user = execute_user_query(user_id) 
        return user

    这里,第二个函数 get_user_by 确保使用相同的参数来传递变量,从而为函数提供正确的上下文。 第一个函数 get_user_info 就不怎么不明确了,因为参数 id 意味着什么这里我们不能确定,它是用户 ID,还是用户付款ID或任何其他 ID? 这种代码可能会对使用你的API的其他开发人员造成混淆。为了解决这个问题,我在第二个函数中更改了两个东西; 我更改了函数名称以及传递的参数名称,这使代码可读性更高。

    作为开发人员,你有责任在命名变量和函数时仔细考虑,要写让人能够清晰易懂的代码。
    当然也方便自己以后去维护。

    类的命名规范

    类的名称应该像大多数其他语言一样使用驼峰大小写。

    class UserInformation:
         def get_user(id):
              db = get_db_connection()
              user = execute_query_for_user(id)
              return user

    常量的命名规范

    通常应该用大写字母定义常量名称。

    TOTAL = 56
    TIMOUT = 6
    MAX_OVERFLOW = 7

    函数和方法的参数

    函数和方法的参数命名应遵循与变量和方法名称相同的规则。因为类方法将self作为第一个关键字参数。所以在函数中就不要使用 self 作为关键字作为参数,以免造成混淆 .

    def calculate_tax(amount, yearly_tax):
        passs
    
    class Player:
        def get_total_score(self, player_name):
             pass

    关于命名大概就强调这些,下面让我们看看表达式和语句中需要的问题。

    代码中的表达式和语句

    users = [
        {"first_name":"Helen", "age":39},
        {"first_name":"Buck", "age":10},
        {"first_name":"anni", "age":9}
    ]
    users = sorted(users, key=lambda user: user["first_name"].lower())

    这段代码有什么问题?
    乍一看并不容易理解这段代码,尤其是对于新开发人员来说,因为 lambdas 的语法很古怪,所以不容易理解。虽然这里使用 lambda 可以节省行,然而,这并不能保证代码的正确性和可读性。同时这段代码无法解决字典缺少键出现异常的问题。

    让我们使用函数重写此代码,使代码更具可读性和正确性; 该函数将判断异常情况,编写起来要简单得多。

    users = [
        {"first_name":"Helen", "age":39},
        {"first_name":"Buck", "age":10},
        {"name":"anni", "age":9}
    ]
    def get_user_name(users):
        """Get name of the user in lower case"""
        return users["first_name"].lower()
    def get_sorted_dictionary(users):
        """Sort the nested dictionary"""
        if not isinstance(users, dict):
            raise ValueError("Not a correct dictionary")
        if not len(users):
           raise ValueError("Empty dictionary")
        users_by_name = sorted(users, key=get_user_name)
        return users_by_name

    如您所见,此代码检查了所有可能的意外值,并且比起以前的单行代码更具可读性。 单行代码虽然看起来很酷节省了行,但是会给代码添加很多复杂性。 但是这并不意味着单行代码就不好 这里提出的一点是,如果你的单行代码使代码变得更难阅读,那么就请避免使用它,记住写代码不是为了炫酷的,尤其在项目组中。

    让我们再考虑一个例子,你试图读取 CSV 文件并计算 CSV 文件处理的行数。下面的代码展示使代码可读的重要性,以及命名如何在使代码可读中发挥重要作用。

    import csv
    with open("employee.csv", mode="r") as csv_file:
        csv_reader = csv.DictReader(csv_file)
        line_count = 0
        for row in csv_reader:
            if line_count == 0:
                print(f'Column names are {", ".join(row)}')
                line_count += 1
                print(f'\t{row["name"]} salary: {row["salary"]}'
                f'and was born in {row["birthday month"]}.')
            line_count += 1
        print(f'Processed {line_count} lines.')

    将代码分解为函数有助于使复杂的代码变的易于阅读和调试。
    这里的代码在 with 语句中执行多项操作。为了提高可读性,您可以将带有 process salary 的代码从 CSV 文件中提取到另一个函数中,以降低出错的可能性。

    import csv
    with open("employee.csv", mode="r") as csv_file:
        csv_reader = csv.DictReader(csv_file)
        line_count = 0
        process_salary(csv_reader)    
    
    
    def process_salary(csv_reader):
    """Process salary of user from csv file."""
        for row in csv_reader:
                if line_count == 0:
                    print(f'Column names are {", ".join(row)}')
                    line_count += 1
                    print(f'\t{row["name"]} salary: {row["salary"]}'
                    f'and was born in {row["birthday month"]}.')
                line_count += 1
            print(f'Processed {line_count} lines.') 

    代码是不是变得容易理解了不少呢。
    在这里,创建了一个帮助函数,而不是在with语句中编写所有内容。这使读者清楚地了解了函数的实际作用。如果想处理一个特定的异常或者想从CSV文件中读取更多的数据,可以进一步分解这个函数,以遵循单一职责原则,一个函数一做一件事。这个很重要

    return语句的类型尽量一致

    如果希望函数返回一个值,请确保该函数的所有执行路径都返回该值。但是,如果期望函数只是在不返回值的情况下执行操作,则 Python 会隐式返回 None 作为函数的默认值。
    先看一个错误示范

    def calculate_interest(principle, time rate):    
         if principle > 0:
             return (principle * time * rate) / 100
    def calculate_interest(principle, time rate):    
        if principle < 0:
            return    
        return (principle * time * rate) / 100ChaPTER 1  PyThonIC ThInkIng
    

    正确的示范应该是下面这样

    def calculate_interest(principle, time rate):    
         if principle > 0:
             return (principle * time * rate) / 100
         else:
            return None
    def calculate_interest(principle, time rate):    
        if principle < 0:
            return None    
        return (principle * time * rate) / 100ChaPTER 1  PyThonIC ThInkIng
    

    还是那句话写易读的代码,代码多写点没关系,可读性很重要。

    使用 isinstance() 方法而不是 type() 进行比较

    当比较两个对象类型时,请考虑使用 isinstance() 而不是 type,因为 isinstance() 判断一个对象是否为另一个对象的子类是 true。考虑这样一个场景:如果传递的数据结构是dict 的子类,比如 orderdict。type() 对于特定类型的数据结构将失败;然而,isinstance() 可以将其识别出它是 dict 的子类。

    错误示范

    user_ages = {"Larry": 35, "Jon": 89, "Imli": 12}
    type(user_ages) == dict:

    正确选择

    Do this:user_ages = {"Larry": 35, "Jon": 89, "Imli": 12}
    if isinstance(user_ages, dict):

    比较布尔值

    在Python中有多种方法可以比较布尔值。
    错误示范

    if is_empty = False
    if is_empty == False:
    if is_empty is False:

    正确示范

    is_empty = False
    if is_empty

    使用文档字符串

    Docstrings可以在 Python 中声明代码的功能的。通常在方法,类和模块的开头使用。 docstring是该对象的__doc__特殊属性。
    Python 官方语言建议使用“”三重双引号“”来编写文档字符串。 你可以在 PEP8 官方文档中找到这些实践。 下面让我们简要介绍一下在 Python 代码中编写 docstrings 的一些最佳实践 。

    方法中使用docstring

    def get_prime_number():
        """Get list of prime numbers between 1 to 100.""""

    关于docstring的格式的写法,目前存在多种风格,但是这几种风格都有一些统一的标准。

    • 即使字符串符合一行,也会使用三重引号。当你想要扩展时,这种注释非常有用。‘
    • 三重引号中的字符串前后不应有任何空行
    • 使用句点(.)结束docstring中的语句
      类似地,可以应用 Python 多行 docstring 规则来编写多行 docstring。在多行上编写文档字符串是用更具描述性的方式记录代码的一种方法。你可以利用 Python 多行文档字符串在 Python 代码中编写描述性文档字符串,而不是在每一行上编写注释。

      多行的docstring
    def call_weather_api(url, location):
        """Get the weather of specific location.
    
        Calling weather api to check for weather by using weather api and 
        location. Make sure you provide city name only, country and county 
        names won't be accepted and will throw exception if not found the 
        city name.
    
        :param url:URL of the api to get weather.
        :type url: str
        :param location:Location of the city to get the weather.
        :type location: str
        :return: Give the weather information of given location.
        :rtype: str"""

    说一下上面代码的注意点

    • 第一行是函数或类的简要描述
    • 每一行语句的末尾有一个句号
    • 文档字符串中的简要描述和摘要之间有一行空白

    如果使用 Python3.6 可以使用类型注解对上面的docstring以及参数的声明进行修改。

    def call_weather_api(url: str, location: str) -> str:
        """Get the weather of specific location.
    
        Calling weather api to check for weather by using weather api and 
        location. Make sure you provide city name only, country and county 
        names won't be accepted and will throw exception if not found the 
        city name.
        """

    怎么样是不是简洁了不少,如果使用 Python 代码中的类型注解,则不需要再编写参数信息。
    关于类型注解(type hint)的具体用法可以参考我之前写的【文字连接】.

    模块级别的docstring

    一般在文件的顶部放置一个模块级的 docstring 来简要描述模块的使用。
    这些注释应该放在在导包之前,模块文档字符串应该表明模块的使用方法和功能。
    如果觉得在使用模块之前客户端需要明确地知道方法或类,你还可以简要地指定特定方法或类。

    """This module contains all of the network related requests. 
    This module will check for all the exceptions while making the network 
    calls and raise exceptions for any unknown exception.
    Make sure that when you use this module,
    you handle these exceptions in client code as:
    NetworkError exception for network calls.
    NetworkNotFound exception if network not found.
    """
    
    import urllib3
    import json

    在为模块编写文档字符串时,应考虑执行以下操作:

    • 对当前模块写一个简要的说明
    • 如果想指定某些对读者有用的模块,如上面的代码,还可以添加异常信息,但是注意不要太详细。
    NetworkError exception for network calls.
    NetworkNotFound exception if network not found.
    • 将模块的docstring看作是提供关于模块的描述性信息的一种方法,而不需要详细讨论每个函数或类具体操作方法。

      类级别的docstring

      类docstring主要用于简要描述类的使用及其总体目标。 让我们看一些示例,看看如何编写类文档字符串

      单行类docstring
    class Student:
       """This class handle actions performed by a student."""
       def __init__(self):
          pass

    这个类有一个一行的 docstring,它简要地讨论了学生类。如前所述,遵守了所以一行docstring 的编码规范。

    多行类docstring
    class Student:
        """Student class information.
    
        This class handle actions performed by a student.
        This class provides information about student full name, age, 
        roll-number and other information.
        Usage:
            import student
            student = student.Student()
            student.get_name()
            >>> 678998
       """
       def __init__(self):
           pass
    

    这个类 docstring 是多行的; 我们写了很多关于 Student 类的用法以及如何使用它。

    函数的docstring

    函数文档字符串可以写在函数之后,也可以写在函数的顶部。

    def is_prime_number(number):
        """Check for prime number.
    
        Check the given number is prime number 
        or not by checking against all the numbers 
        less the square root of given number.
    
        :param number:Given number to check for prime
        :type number: int
        :return: True if number is prime otherwise False.
        :rtype: boolean
        """

    如果我们使用类型注解对其进一步优化。

    def is_prime_number(number: int)->bool:
        """Check for prime number.
    
        Check the given number is prime number 
        or not by checking against all the numbers 
        less the square root of given number.
        """

    结语

    当然关于 Python 中的规范还有很多很多,建议大家参考 Python 之禅和 Pep8 对代码进行优化,养成编写 Pythonic 代码的良好习惯。
    更多精彩内容关注微信公众号:python学习开发
    后续。

    转载于:https://www.cnblogs.com/c-x-a/p/10923730.html

    展开全文
  • 本文为英文书籍 Clean Code in Python Chapter 5 Using Decorators to Improve Our Code 学习笔记,...分析如何用装饰器避免代码重复(DRY) 研究装饰器如何为关注点分离做出贡献 优秀装饰器实例分析 回顾常见情况、...

    本文为英文书籍 Clean Code in Python Chapter 5 Using Decorators to Improve Our Code 学习笔记,建议直接看原书

    • 了解Python中装饰器的工作原理
    • 学习如何实现应用于函数和类的装饰器
    • 有效地实现装饰器,避免常见的执行错误
    • 分析如何用装饰器避免代码重复(DRY)
    • 研究装饰器如何为关注点分离做出贡献
    • 优秀装饰器实例分析
    • 回顾常见情况、习惯用法或模式,了解何时装饰器是正确的选择

    虽然一般见到装饰器装饰的是方法和函数,但实际允许装饰任何类型的对象,因此我们将探索应用于函数、方法、生成器和类的装饰器。

    还要注意,不要将装饰器与装饰器设计模式(Decorator Pattern)混为一谈。

    函数装饰

    函数可能是可以被装饰的Python对象中最简单的表示形式。我们可以在函数上使用装饰器来达成各种逻辑——可以验证参数、检查前提条件、完全改变行为、修改签名、缓存结果(创建原始函数的存储版本)等等。

    作为示例,我们将创建实现重试机制的基本装饰器,控制特定的域级异常(domain-level exception)并重试一定次数:

    # decorator_function_1.py
    import logging
    from functools import wraps
    
    logger = logging.getLogger(__name__)
    
    
    class ControlledException(Exception):
        """A generic exception on the program's domain."""
        pass
    
    
    def retry(operation):
        @wraps(operation)
        def wrapped(*args, **kwargs):
            last_raised = None
            RETRIES_LIMIT = 3
            for _ in range(RETRIES_LIMIT):
                try:
                    return operation(*args, **kwargs)
                except ControlledException as e:
                    logger.info("retrying %s", operation.__qualname__)
                    last_raised = e
            raise last_raised
    
        return wrapped
    
    复制代码

    可以暂时忽略@wraps,之后再介绍
    retry装饰器使用例子:

    @retry
    def run_operation(task):
       """Run a particular task, simulating some failures on its execution."""
       return task.run()
    复制代码

    因为装饰器只是提供的一种语法糖,实际上等于run_operation = retry(run_operation)
    比较常用的超时重试,便可以这样实现。

    定义一个带参数的装饰器

    我们用一个例子详细阐述下接受参数的处理过程。 假设你想写一个装饰器,给函数添加日志功能,同时允许用户指定日志的级别和其他的选项。 下面是这个装饰器的定义和使用示例:

    from functools import wraps
    import logging
    
    def logged(level, name=None, message=None):
        """
        Add logging to a function. level is the logging
        level, name is the logger name, and message is the
        log message. If name and message aren't specified,
        they default to the function's module and name.
        """
        def decorate(func):
            logname = name if name else func.__module__
            log = logging.getLogger(logname)
            logmsg = message if message else func.__name__
    
            @wraps(func)
            def wrapper(*args, **kwargs):
                log.log(level, logmsg)
                return func(*args, **kwargs)
            return wrapper
        return decorate
    
    # Example use
    @logged(logging.DEBUG)
    def add(x, y):
        return x + y
    
    @logged(logging.CRITICAL, 'example')
    def spam():
        print('Spam!')
    
    复制代码

    初看起来,这种实现看上去很复杂,但是核心思想很简单。 最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数上面。 内层的函数 decorate() 接受一个函数作为参数,然后在函数上面放置一个包装器。 这里的关键点是包装器是可以使用传递给 logged() 的参数的。

    定义一个接受参数的包装器看上去比较复杂主要是因为底层的调用序列。特别的,如果你有下面这个代码:

    @decorator(x, y, z)
    def func(a, b):
        pass
    复制代码

    装饰器处理过程跟下面的调用是等效的;

    def func(a, b):
        pass
    func = decorator(x, y, z)(func)
    decorator(x, y, z) 的返回结果必须是一个可调用对象,它接受一个函数作为参数并包装它
    复制代码

    类装饰

    有些人认为,装饰类是比较复杂的事情,而且这样的方案可能危及可读性。因为我们在类中声明一些属性和方法,但是装饰器可能会改变它们的行为,呈现出完全不同的类。

    在这种技术被严重滥用的情况下,这种评价是正确的。客观地说,这与装饰函数没有什么不同;毕竟,类只是Python生态系统中的另一种类型的对象,就像函数一样。我们将在标题为“装饰器和关注点分离”的章节中一起回顾这个问题的利弊,但是现在,我们将探讨类的装饰器的好处:

    • 代码重用和DRY。一个恰当的例子是,类装饰器强制多个类符合某个特定的接口或标准(通过在装饰器中仅检查一次,而能应用于多个类)
    • 可以创建更小或更简单的类,而通过装饰器增强这些类
    • 类的转换逻辑将更容易维护,而不是使用更复杂(通常是理所当然不被鼓励的)的方法,比如元类

    回顾监视平台的事件系统,我们现在需要转换每个事件的数据并将其发送到外部系统。 但是,在选择如何发送数据时,每种类型的事件可能都有自己的特殊性。

    特别是,登录的事件可能包含敏感信息,如登录信息需要隐藏, 时间戳等其他字段也可能需要特定的格式显示。

    class LoginEventSerializer:
        def __init__(self, event):
            self.event = event
    
        def serialize(self) -> dict:
            return {
                "username": self.event.username,
                "password": "**redacted**",
                "ip": self.event.ip,
                "timestamp": self.event.timestamp.strftime("%Y-%m-%d% H: % M"),}
    
    
    class LoginEvent:
        SERIALIZER = LoginEventSerializer
    
        def __init__(self, username, password, ip, timestamp):
            self.username = username
            self.password = password
            self.ip = ip
            self.timestamp = timestamp
    
        def serialize(self) -> dict:
            return self.SERIALIZER(self).serialize()
    复制代码

    在这里,我们声明一个类,该类将直接映射到登录事件,包含其逻辑——隐藏密码字段,并根据需要格式化时间戳。

    虽然这种方法可行,而且看起来是个不错的选择,但是随着时间的推移,想要扩展我们的系统,就会发现一些问题:

    • 类太多:随着事件数量的增加,序列化类的数量将以相同的数量级增长,因为它们是一一映射的。
    • 解决方案不够灵活:如果需要重用组件的一部分(例如,我们需要在另一种事件中隐藏密码),则必须将其提取到一个函数中,还要从多个类中重复调用它,这意味着我们没有做到代码重用。
    • Boilerplate:serialize()方法必须出现在所有事件类中,调用相同的代码。虽然我们可以将其提取到另一个类中(创建mixin),但它似乎不是继承利用的好方式( Although we can extract this into another class (creating a mixin), it does not seem like a good use of inheritance.)。

    另一种解决方案是,给定一组过滤器(转换函数)和一个事件实例,能够动态构造对象,该对象能够通过滤器对其字段序列化。然后,我们只需要定义转换每种类型的字段的函数,并且通过组合这些函数中的许多函数来创建序列化程序。

    一旦有了这个对象,我们就可以装饰类,以便添加serialize()方法,该方法将只调用这些Serialization对象本身:

    def hide_field(field) -> str:
        return "**redacted**"
    
    
    def format_time(field_timestamp: datetime) -> str:
        return field_timestamp.strftime("%Y-%m-%d %H:%M")
    
    
    def show_original(event_field):
        return event_field
    
    
    class EventSerializer:
        def __init__(self, serialization_fields: dict) -> None:
            self.serialization_fields = serialization_fields
    
        def serialize(self, event) -> dict:
            return {
                field: transformation(getattr(event, field))
                for field, transformation in self.serialization_fields.items()
            }
    
    
    class Serialization:
        def __init__(self, **transformations):
            self.serializer = EventSerializer(transformations)
    
        def __call__(self, event_class):
            def serialize_method(event_instance):
                return self.serializer.serialize(event_instance)
    
            event_class.serialize = serialize_method
            return event_class
    
    
    @Serialization(
        username=show_original,
        password=hide_field,
        ip=show_original,
        timestamp=format_time,
    )
    class LoginEvent:
        def __init__(self, username, password, ip, timestamp):
            self.username = username
            self.password = password
            self.ip = ip
            self.timestamp = timestamp
    复制代码

    待续。。。

    展开全文
  • python代码整洁之道

    千次阅读 2020-01-29 19:30:00
    总第 113 篇文章,本文大约 8000 字,阅读大约需要 20 分钟原文:https://github.com/zedr/clean-code-pythonpython 版的代码整洁之...

    总第 113 篇文章,本文大约 8000 字,阅读大约需要 20 分钟

    原文:https://github.com/zedr/clean-code-python

    python 版的代码整洁之道。目录如下所示:

    1. 介绍

    2. 变量

    3. 函数


    1. 介绍

    软件工程的原则,来自 Robert C. Martin's 的书--《Clean Code》,而本文则是适用于 Python 版本的 clean code。这并不是一个风格指导,而是指导如何写出可读、可用以及可重构的 pyhton 代码。

    并不是这里介绍的每个原则都必须严格遵守,甚至只有很少部分会得到普遍的赞同。下面介绍的都只是指导而已,但这都是来自有多年编程经验的 《Clean Code》的作者。

    这里的 python 版本是 3.7+


    2. 变量

    2.1 采用有意义和可解释的变量名

    糟糕的写法

    ymdstr = datetime.date.today().strftime("%y-%m-%d")
    

    好的写法

    current_date: str = datetime.date.today().strftime("%y-%m-%d")
    

    2.2 对相同类型的变量使用相同的词汇

    糟糕的写法:这里对有相同下划线的实体采用三个不同的名字

    get_user_info()
    get_client_data()
    get_customer_record()
    

    好的写法:如果实体是相同的,对于使用的函数应该保持一致

    get_user_info()
    get_user_data()
    get_user_record()
    

    更好的写法:python 是一个面向对象的编程语言,所以可以将相同实体的函数都放在类中,作为实例属性或者是方法

    class User:
        info : str
    
    
        @property
        def data(self) -> dict:
            # ...
    
    
        def get_record(self) -> Union[Record, None]:
            # ...
    

    2.3 采用可以搜索的名字

    我们通常都是看的代码多于写过的代码,所以让我们写的代码是可读而且可以搜索的是非常重要的,如果不声明一些有意义的变量,会让我们的程序变得难以理解,例子如下所示。

    糟糕的写法

    # 86400 表示什么呢?
    time.sleep(86400)
    

    好的写法

    # 声明了一个全局变量
    SECONDS_IN_A_DAY = 60 * 60 * 24
    
    
    time.sleep(SECONDS_IN_A_DAY)
    

    2.4 采用带解释的变量

    糟糕的写法

    address = 'One Infinite Loop, Cupertino 95014'
    city_zip_code_regex = r'^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$'
    matches = re.match(city_zip_code_regex, address)
    
    
    save_city_zip_code(matches[1], matches[2])
    

    还行的写法

    这个更好一点,但还是很依赖于正则表达式

    address = 'One Infinite Loop, Cupertino 95014'
    city_zip_code_regex = r'^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$'
    matches = re.match(city_zip_code_regex, address)
    
    
    city, zip_code = matches.groups()
    save_city_zip_code(city, zip_code)
    

    好的写法

    通过子模式命名来减少对正则表达式的依赖

    address = 'One Infinite Loop, Cupertino 95014'
    city_zip_code_regex = r'^[^,\\]+[,\\\s]+(?P<city>.+?)\s*(?P<zip_code>\d{5})?$'
    matches = re.match(city_zip_code_regex, address)
    
    
    save_city_zip_code(matches['city'], matches['zip_code'])
    

    2.5 避免让读者进行猜测

    不要让读者需要联想才可以知道变量名的意思,显式比隐式更好。

    糟糕的写法

    seq = ('Austin', 'New York', 'San Francisco')
    
    
    for item in seq:
        do_stuff()
        do_some_other_stuff()
        # ...
        # item 是表示什么?
        dispatch(item)
    

    好的写法

    locations = ('Austin', 'New York', 'San Francisco')
    
    
    for location in locations:
        do_stuff()
        do_some_other_stuff()
        # ...
        dispatch(location)
    

    2.6 不需要添加额外的上下文

    如果类或者对象名称已经提供一些信息来,不需要在变量中重复。

    糟糕的写法

    class Car:
        car_make: str
        car_model: str
        car_color: str
    

    好的写法

    class Car:
        make: str
        model: str
        color: str
    

    2.7 采用默认参数而不是条件语句

    糟糕的写法

    def create_micro_brewery(name):
        name = "Hipster Brew Co." if name is None else name
        slug = hashlib.sha1(name.encode()).hexdigest()
        # etc.
    

    这个写法是可以直接给 name 参数设置一个默认数值,而不需要采用一个条件语句来进行判断的。

    好的写法

    def create_micro_brewery(name: str = "Hipster Brew Co."):
        slug = hashlib.sha1(name.encode()).hexdigest()
        # etc.
    

    3. 函数

    3.1 函数参数(2个或者更少)

    限制函数的参数个数是很重要的,这有利于测试你编写的函数代码。超过3个以上的函数参数会导致测试组合爆炸的情况,也就是需要考虑很多种不同的测试例子。

    没有参数是最理想的情况。一到两个参数也是很好的,三个参数应该尽量避免。如果多于 3 个那么应该需要好好整理函数。通常,如果函数多于2个参数,那代表你的函数可能要实现的东西非常多。此外,很多时候,一个高级对象也是可以用作一个参数使用。

    糟糕的写法

    def create_menu(title, body, button_text, cancellable):
        # ...
    

    很好的写法

    class Menu:
        def __init__(self, config: dict):
            title = config["title"]
            body = config["body"]
            # ...
    
    
    menu = Menu(
        {
            "title": "My Menu",
            "body": "Something about my menu",
            "button_text": "OK",
            "cancellable": False
        }
    )
    

    另一种很好的写法

    class MenuConfig:
        """A configuration for the Menu.
    
    
        Attributes:
            title: The title of the Menu.
            body: The body of the Menu.
            button_text: The text for the button label.
            cancellable: Can it be cancelled?
        """
        title: str
        body: str
        button_text: str
        cancellable: bool = False
    
    
    
    
    def create_menu(config: MenuConfig):
        title = config.title
        body = config.body
        # ...
    
    
    
    
    config = MenuConfig
    config.title = "My delicious menu"
    config.body = "A description of the various items on the menu"
    config.button_text = "Order now!"
    # The instance attribute overrides the default class attribute.
    config.cancellable = True
    
    
    create_menu(config)
    

    优秀的写法

    from typing import NamedTuple
    
    
    
    
    class MenuConfig(NamedTuple):
        """A configuration for the Menu.
    
    
        Attributes:
            title: The title of the Menu.
            body: The body of the Menu.
            button_text: The text for the button label.
            cancellable: Can it be cancelled?
        """
        title: str
        body: str
        button_text: str
        cancellable: bool = False
    
    
    
    
    def create_menu(config: MenuConfig):
        title, body, button_text, cancellable = config
        # ...
    
    
    
    
    create_menu(
        MenuConfig(
            title="My delicious menu",
            body="A description of the various items on the menu",
            button_text="Order now!"
        )
    )
    

    更优秀的写法

    rom dataclasses import astuple, dataclass
    
    
    
    
    @dataclass
    class MenuConfig:
        """A configuration for the Menu.
    
    
        Attributes:
            title: The title of the Menu.
            body: The body of the Menu.
            button_text: The text for the button label.
            cancellable: Can it be cancelled?
        """
        title: str
        body: str
        button_text: str
        cancellable: bool = False
    
    
    def create_menu(config: MenuConfig):
        title, body, button_text, cancellable = astuple(config)
        # ...
    
    
    
    
    create_menu(
        MenuConfig(
            title="My delicious menu",
            body="A description of the various items on the menu",
            button_text="Order now!"
        )
    )
    

    3.2 函数应该只完成一个功能

    这是目前为止软件工程里最重要的一个规则。函数如果完成多个功能,就很难对这个函数解耦、测试。如果可以对一个函数分离为仅仅一个动作,那么该函数可以很容易进行重构,并且代码也方便阅读。即便你仅仅遵守这一点建议,你也会比很多开发者更加优秀。

    糟糕的写法

    def email_clients(clients: List[Client]):
        """Filter active clients and send them an email.
           筛选活跃的客户并发邮件给他们
        """
        for client in clients:
            if client.active:
                email(client)
    

    好的写法

    def get_active_clients(clients: List[Client]) -> List[Client]:
        """Filter active clients.
        """
        return [client for client in clients if client.active]
    
    
    
    
    def email_clients(clients: List[Client, ...]) -> None:
        """Send an email to a given list of clients.
        """
        for client in clients:
            email(client)
    

    这里其实是可以使用生成器来改进函数的写法。

    更好的写法

    def active_clients(clients: List[Client]) -> Generator[Client]:
        """Only active clients.
        """
        return (client for client in clients if client.active)
    
    
    
    
    def email_client(clients: Iterator[Client]) -> None:
        """Send an email to a given list of clients.
        """
        for client in clients:
            email(client)
    

    3.3 函数的命名应该表明函数的功能

    糟糕的写法

    class Email:
        def handle(self) -> None:
            # Do something...
    
    
    message = Email()
    # What is this supposed to do again?
    # 这个函数是需要做什么呢?
    message.handle()
    

    好的写法

    class Email:
        def send(self) -> None:
            """Send this message.
            """
    
    
    message = Email()
    message.send()
    

    3.4 函数应该只有一层抽象

    如果函数包含多于一层的抽象,那通常就是函数实现的功能太多了,应该把函数分解成多个函数来保证可重复使用以及更容易进行测试。

    糟糕的写法

    def parse_better_js_alternative(code: str) -> None:
        regexes = [
            # ...
        ]
    
    
        statements = regexes.split()
        tokens = []
        for regex in regexes:
            for statement in statements:
                # ...
    
    
        ast = []
        for token in tokens:
            # Lex.
    
    
        for node in ast:
            # Parse.
    

    好的写法

    REGEXES = (
       # ...
    )
    
    
    
    
    def parse_better_js_alternative(code: str) -> None:
        tokens = tokenize(code)
        syntax_tree = parse(tokens)
    
    
        for node in syntax_tree:
            # Parse.
    
    
    
    
    def tokenize(code: str) -> list:
        statements = code.split()
        tokens = []
        for regex in REGEXES:
            for statement in statements:
               # Append the statement to tokens.
    
    
        return tokens
    
    
    
    
    def parse(tokens: list) -> list:
        syntax_tree = []
        for token in tokens:
            # Append the parsed token to the syntax tree.
    
    
        return syntax_tree
    

    3.5 不要将标志作为函数参数

    标志表示函数实现的功能不只是一个,但函数应该仅做一件事情,所以如果需要标志,就将多写一个函数吧。

    糟糕的写法

    from pathlib import Path
    
    
    def create_file(name: str, temp: bool) -> None:
        if temp:
            Path('./temp/' + name).touch()
        else:
            Path(name).touch()
    

    好的写法

    from pathlib import Path
    
    
    def create_file(name: str) -> None:
        Path(name).touch()
    
    
    def create_temp_file(name: str) -> None:
        Path('./temp/' + name).touch()
    

    3.6 避免函数的副作用

    函数产生副作用的情况是在它做的事情不只是输入一个数值,返回其他数值这样一件事情。比如说,副作用可能是将数据写入文件,修改全局变量,或者意外的将你所有的钱都写给一个陌生人。

    不过,有时候必须在程序中产生副作用--比如,刚刚提到的例子,必须写入数据到文件中。这种情况下,你应该尽量集中和指示产生这些副作用的函数,比如说,保证只有一个函数会产生将数据写到某个特定文件中,而不是多个函数或者类都可以做到。

    这条建议的主要意思是避免常见的陷阱,比如分析对象之间的状态的时候没有任何结构,使用可以被任何数据修改的可修改数据类型,或者使用类的实例对象,不集中副作用影响等等。如果你可以做到这条建议,你会比很多开发者都开心。

    糟糕的写法

    # This is a module-level name.
    # It's good practice to define these as immutable values, such as a string.
    # However...
    name = 'Ryan McDermott'
    
    
    def split_into_first_and_last_name() -> None:
        # The use of the global keyword here is changing the meaning of the
        # the following line. This function is now mutating the module-level
        # state and introducing a side-effect!
        # 这里采用了全局变量,并且函数的作用就是修改全局变量,其副作用就是修改了全局变量,
        # 第二次调用函数的结果就会和第一次调用不一样了。
        global name
        name = name.split()
    
    
    split_into_first_and_last_name()
    
    
    print(name)  # ['Ryan', 'McDermott']
    
    
    # OK. It worked the first time, but what will happen if we call the
    # function again?
    

    好的写法

    def split_into_first_and_last_name(name: str) -> list:
        return name.split()
    
    
    name = 'Ryan McDermott'
    new_name = split_into_first_and_last_name(name)
    
    
    print(name)  # 'Ryan McDermott'
    print(new_name)  # ['Ryan', 'McDermott']
    

    另一个好的写法

    from dataclasses import dataclass
    
    
    @dataclass
    class Person:
        name: str
    
    
        @property
        def name_as_first_and_last(self) -> list:
            return self.name.split() 
    
    
    # The reason why we create instances of classes is to manage state!
    person = Person('Ryan McDermott')
    print(person.name)  # 'Ryan McDermott'
    print(person.name_as_first_and_last)  # ['Ryan', 'McDermott']
    

    总结

    原文的目录实际还有三个部分:

    • 对象和数据结构

      • 单一职责原则(Single Responsibility Principle, SRP)

      • 开放封闭原则(Open/Closed principle,OCP)

      • 里氏替换原则(Liskov Substitution Principle ,LSP)

      • 接口隔离原则(Interface Segregation Principle ,ISP)

      • 依赖倒置原则(Dependency Inversion Principle ,DIP)

    • 不要重复

    不过作者目前都还没有更新,所以想了解这部分内容的,建议可以直接阅读《代码整洁之道》对应的这部分内容了。

    精选文章

    欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

    如果觉得不错,在看、转发就是对小编的一个支持!

    展开全文
  • Python整洁之道

    2020-04-30 17:07:00
    介绍几个Python代码增强可读性的小方法
  • Python非常易学,是程序员喜欢的理想语言。虽然这么好,但是想玩得更溜并不容易,本系列帮你写出更好的Python代码
  • 在这一篇文章中,我们来讨论一些更加形而上的知识...《重构》和《代码整洁之道》都是使用Java语言作为思想的载体,为了节省读者的时间,并且强调一些Python语言的特性,所以有了读者现在正在看的这篇文章。 在这篇文...
  • 代码整洁之道

    2017-12-15 09:43:00
    2019独角兽企业重金招聘Python工程师标准>>> ...
  • 点击上方蓝色小字,关注“涛哥聊Python”重磅干货,第一时间送达来自:Python学习开发(微信号:python3-5)很多新手在开始学一门新的语言的时候,往往会忽视一些不应该忽视的细...
  • 参考书籍:《代码整洁之道》,语言:Python 1.函数要短小 1)每个函数只做一件事,并且每个函数都依次把你带到下一个函数,这就是函数应该达到的短小程度。 2)if, else, while等语句的代码块应该只有一行,该行大概...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 参考书籍:《代码整洁之道》,语言:Python 1.变量的命名要与其本意相符合 1)变量、函数或类的命名应该能告诉我们,它为什么存在,它做什么事,应该怎么用 2)如果一个名称还需要注释来补充,那就不算是名副其实 3)...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • Python是当今最流行的语言之一。相对较新的领域如数据科学、人工智能、...对此,小编整理了一份适合所有Python学习者的资料:Python代码整洁之道。 写出 Pythonic 代码 谈到规范首先想到就是 Python 有名的 PEP8 代码
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • 参考书籍:《代码整洁之道》,语言:Python 1.变量的命名要与其本意相符合 1)变量、函数或类的命名应该能告诉我们,它为什么存在,它做什么事,应该怎么用 2)如果一个名称还需要注释来补充,那就不算是名副其实 3...
  • 来自| 架构头条按照《代码整洁之道》的说法,“花在阅读和编码上的时间比远远超过 10:1。”通常,当我们在学校学习时,编程美学不是一个关键问题。用 Python 写代码时,个人也会遵循...
  • 按照《代码整洁之道》的说法,“花在阅读和编码上的时间比远远超过 10:1。” 通常,当我们在学校学习时,编程美学不是一个关键问题。用 Python 写代码时,个人也会遵循自己的风格。然而,当我们必须花大把时间来...
  • Python学习教程(Python学习路线):怎么写出让人看起来很优雅舒服的代码?...对此,我特意收集了一些适合所有学习 Python 的伙伴,代码整洁之道,让你写出来的代码让人眼前一亮!!! 写...
  • Martin(Bob大叔)曾在《代码整洁之道》一书打趣地说:当你的代码在做 Code Review 时,审查者要是愤怒地吼道:“What the fuck, is thi...
  • Martin(Bob大叔)曾在《代码整洁之道》一书打趣地说:当你的代码在做 Code Review 时,审查者要是愤怒地吼道: “What the fuck is this shit?” “Dude, What the fuck!” 等言辞激烈的词语时,那说明你写的代码是 ...
  • Martin(Bob大叔)曾在《代码整洁之道》一书打趣地说:当你的代码在做 Code Review 时,审查者要是愤怒地吼道: “What the fuck is this shit?” “Dude, What the fuck!” 等言辞激烈的词语时,那说明...

空空如也

空空如也

1 2 3
收藏数 50
精华内容 20
关键字:

python代码整洁之道

python 订阅