茴香豆的“茴”有四种写法,Python的格式化字符串也有

最近正在阅读《Python Tricks: The Book》这本书,想要通过掌握书中提及的知识点提高自己的Python编程能力。本文主要记录在学习该书第二章“Patterns for Cleaner Python”过程中的一些心得体会。

被低估的断言

断言是编程语言提供的一种调试工具,是让程序在运行时进行自检的代码,可以辅助程序员进行交流和调试。程序员可以通过断言了解代码正确运行所依赖的假设,此外,当断言为假时,程序员可以快速排查出由于输入(输入参数的取值范围不符合预期)输出(返回的结果是没有意义的)不符合接口假设而导致的错误。

断言常用于验证以下两类条件是否满足:

  • 前置条件,调用方(Caller)在调用函数或类时必须满足的条件,比如平方根运算要求被开方数必须大于0;
  • 后置条件,被调用方(Callee)的执行结果需要满足的条件,比如商品打折后的价格不能高于原价或者低于0。

在Python中通过“assert expression ["," expression]”的方式声明断言,例如:

def apply_discount(product, discount):
price = int(product['price'] * (1.0 - discount))
# 验证后置条件:打折后的价格应该介于0到原价之间
assert 0 <= price <= product['price']
return price

注意:断言主要用于处理代码中不应发生的错误,而那些在预期中的可能发生的错误建议使用异常进行处理。

多一个逗号,少一点糟心事

在定义列表、元组、字典以及集合时,在最后一个元素后面追加一个额外的逗号非常有用:

  • 增删元素或调整元素的顺序将变得容易,不会因为忘了逗号而导致错误;
  • 使得Git这样的软件配置管理工具可以准确追踪到代码的更改(git diff,改了哪一行就显示哪一行,新增元素时不会因为在上一行的末尾加了个逗号,把上一行也标绿)。
# 新增元素'Jane',由于忘了在'Dilbert'后面加逗号,names变成了['Alice', 'Bob', 'DilbertJane']
names = [
'Alice',
'Bob',
'Dilbert'
'Jane'
]

上下文管理器和with语句

with语句解构了try/finally语句的标准用法:with语句开始执行时,会调用上下文管理器对象的“__enter__”方法,这对应try/finally语句之前申请系统资源的过程(比如打开文件);with语句执行结束后,会调用上下文管理器对象的“__exit__”方法,这对应finally子句中释放系统资源的过程(比如关闭文件句柄)。

可以通过两种方式定义上下文管理器:

  1. 通过定义类的方式,实现“__enter__”和“__exit__”方法;
  2. 使用contextlib模块的contextmanager装饰器,利用yield语句解构try/finally。
class Indenter:
"""缩进管理器"""
def __init__(self):
self.level = 0 def __enter__(self):
self.level += 1
return self def __exit__(self, exc_type, exc_val, exc_tb):
self.level -= 1 def print(self, text: str):
print(' ' * self.level + text) with Indenter() as indent:
indent.print('风在吼')
indent.print('马在叫')
with indent:
indent.print('黄河在咆哮')
indent.print('黄河在咆哮')
with indent:
indent.print('河西山冈万丈高')
indent.print('河东河北高粱熟了')
indent.print('...')
indent.print('保卫家乡!保卫黄河!')
indent.print('保卫华北!保卫全中国!') # 风在吼
# 马在叫
# 黄河在咆哮
# 黄河在咆哮
# 河西山冈万丈高
# 河东河北高粱熟了
# ...
# 保卫家乡!保卫黄河!
# 保卫华北!保卫全中国!

(有没有更好的写法,感觉使用全局变量不太优雅?)

import contextlib

level = 0

@contextlib.contextmanager
def indenter():
global level
level += 1
yield
level -= 1 def cprint(text: str):
global level
print(' ' * level + text) with indenter():
cprint('风在吼')
cprint('马在叫')
with indenter():
cprint('黄河在咆哮')
cprint('黄河在咆哮')
with indenter():
cprint('河西山冈万丈高')
cprint('河东河北高粱熟了')
cprint('...')
cprint('保卫家乡!保卫黄河!')
cprint('保卫华北!保卫全中国!')

作为前缀和后缀的下划线

在Python的变量名或方法名的前面或后面使用单个下划线或两个下划线,有着不同的含义:

  1. 单个下划线作为前缀(_var)表示类或模块的私有成员,尝试通过通配符导入模块的所有函数和变量时(from module import *),私有成员不会被导入;

  2. 单个下划线作为后缀(var_)用于与Python关键字进行区分,比如“def make_object(name, class_)”中的形参”class_”;

  3. 两个下划线作为前缀(__var)用于避免与子类相同名称的类变量之间的冲突,变量名会被Python解释器重写为“_类名__var”;

    class Test:
    """父类"""
    def __init__(self):
    self.foo = 0
    self.__bar = 1 test = Test()
    dir(test) # ['_Test__bar', 'foo', ...] class ExtendedTest(Test):
    """子类"""
    def __init__(self):
    super().__init__()
    self.foo = 3
    self.__bar = 4 test = ExtendedTest()
    dir(test) # ['_ExtendedTest__bar', '_Test__bar', 'foo', ...]
  4. 两个下划线同时作为前缀和后缀(__var__)表示特殊方法;

  5. 单独的下划线表示临时变量的名称(主要用于拆包时进行占位),也表示REPL最近一个表达式的结果。

茴香豆的“茴”有四种写法,格式化字符串也有

在Python中有四种常用的格式化字符串的方法:

  1. printf风格的格式化,比如print('逐梦演艺%s' % '圈圈圈圈圈');

  2. str.format,比如print('逐梦演艺{0}{0}{0}{0}{0}'.format('圈'))

  3. f-string字符串插值:

    echoes = '圈'
    print(f'逐梦演艺{echoes * 5}')
  4. string.Template:

    import string
    
    template = string.Template('逐梦演艺${echoes}')
    print(template.substitute(echoes='圈圈圈圈圈'))

当需要对用户输入的字符串进行格式化时,推荐使用string.Template而非其他方案,因为恶意用户可能通过类似SQL注入的方式获取系统的敏感信息:

import string

SECRET = '这是私钥'

class Error:
def __init__(self):
pass err = Error()
user_input = '{error.__init__.__globals__[SECRET]}'
user_input.format(error=err) # '这是私钥',糟糕,私钥被泄露了! user_input = '${error.__init__.__globals__[SECRET]}'
string.Template(user_input).substitute(error=err) # ValueError

Python之禅

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren’t special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one—and preferably only one—obvious way to do it.
Although that way may not be obvious at first unless you’re Dutch.
Now is better than never.
Although never is often better than right now.
If the implementation is hard to explain, it’s a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea—let’s do more of those!

参考资料

  1. Python Tricks: The Book
  2. 什么时候用异常,什么时候用断言?
  3. 《重构:改善既有代码的设计》,9.8节“引入断言”
  4. 《代码大全-第二版》,8.2节“断言”
  5. Why are trailing commas allowed in a list?
  6. 《流畅的Python》,15.2节“上下文管理器和with块”

茴香豆的“茴”有四种写法,Python的格式化字符串也有的更多相关文章

  1. Python中斐波那契数列的四种写法

    在这些时候,我可以附和着笑,项目经理是决不责备的.而且项目经理见了孔乙己,也每每这样问他,引人发笑.孔乙己自己知道不能和他们谈天,便只好向新人说话.有一回对我说道,“你学过数据结构吗?”我略略点一点头 ...

  2. swap函数的四种写法

    swap 函数的四种写法 (1)经典型 --- 嫁衣法 void swap(int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } ( ...

  3. Android代码规范----按钮单击事件的四种写法

    [前言] 按钮少的时候用第三种的匿名内部类会比较快,比如写demo测试的时候或者登陆界面之类. 按钮多的时候一般选择第四种写法. 一.第一种写法:在XML文件中声明onClick属性(很少用) 在XM ...

  4. JS 获取星期几的四种写法

    今天是星期几的4种JS代码写法,有需要的朋友可以参考一下 第一种写法 复制代码代码如下: var str = "";  var week = new Date().getDay() ...

  5. [0413] FFTSHIFT的四种写法

    FFTSHIFT的四种写法 前言 matlab说,"你读过书,--我便考你一考.fftshift的函数,怎样写的?"我想,讨饭一样的人,也配考我么?便回过脸去,不再理会.matla ...

  6. Android点击事件(click button)的四种写法

    在学习android开发和测试的时候发现不同的人对于click事件的写法是不一样的,上网查了一下,发现有四种写法,于是想比较一下四种方法的不同 第一种方法:匿名内部类 代码: package com. ...

  7. 17_点击事件第四种写法_布局文件添加onclick属性

    尽量不要用第四种点击事件的写法.在一万多行代码中发现了一个没被调用的代码 public void call(View v){//第四种写法参数一定是View v //public void call( ...

  8. Android笔记---点击事件的四种写法

    Android 点击事件的四种写法: 1. 以内部类的形式实现 OnClickListener 接口.定义点击事件 class MainActivity extents Activity{ // .. ...

  9. Android中点击事件的四种写法详解

    Android中点击事件的四种写法 使用内部类实现点击事件 使用匿名内部类实现点击事件 让MainActivity实现View.OnClickListener接口 通过布局文件中控件的属性 第一种方法 ...

随机推荐

  1. (bzoj4408)[FJOI2016]神秘数(可持久化线段树)

    (bzoj4408)[FJOI2016]神秘数(可持久化线段树) bzoj luogu 对于一个区间的数,排序之后从左到右每一个数扫 如果扫到某个数a时已经证明了前面的数能表示[1,x],那么分情况: ...

  2. idea导入gitee下载的项目文件

    前一段时间在学习javaWeb时想要把gitee中的下载的项目在本地环境中跑一遍,然后根据效果再自己做出来. 但是当导入到IDEA中,配置完tomcat后一直报404错误.404是学习javaweb阶 ...

  3. Java中会存在内存泄漏吗,请简单描述?

    为了搞清楚Java程序是否有内存泄露存在,我们首先了解一下什么是内存泄露:程序运行过程中会不断地分配内存空间:那些不再使用的内存空间应该即时回收它们,从而保证系统可以再次使用这些内存.如果存在无用的内 ...

  4. 如何使用 Spring Boot 实现异常处理?

    Spring 提供了一种使用 ControllerAdvice 处理异常的非常有用的方法. 我们通过实现一个 ControlerAdvice 类,来处理控制器类抛出的所有异常.

  5. EMQX_AUTH_USERNAME 使用

    emqx_auth_username 它通过比对每个终端的接入的 username 和 password 与 EMQ X 中存储的是否一致来实现终端接入的控制.其功能逻辑如下: emqx_auth_u ...

  6. Django中间件 (middleware)

    中间件是处理django的请求和响应的框架级别的钩子,本质是一个类(直白一点中间件是帮助我们在视图函数执行之前和执行之后都可以做一些额外的操作) 由于其影响的是全局,所以需要谨慎使用,使用不当会影响性 ...

  7. 学习Kvm(二)

    一.走进云计算 云计算:云计算是一种按使用量付费的模式,这种模式提供可用的.便捷的.按需的网络访问, 进入可配置的计算资源共享池(资源包括网络,服务器,存储,应用软件,服务),这些资源能够被快速提供, ...

  8. HTML5 localStorage使用方法及注意点

    html5新增了在客户端存储数据的新方法:1.localStorage - 没有时间限制的数据存储:2.sessionStorage - 针对一个session的数据存储,当用户关闭浏览器窗口后,数据 ...

  9. RedisDesktopManager 连接不上远程 Redis

    1.首先确保远程redis-server已经启用: 2.连接不到可能的原因: redis3.2以上版本默认开启保护模式,不允许外网访问,需要修改redis.conf文件 3.redis.conf文件需 ...

  10. ps让图片背景透明

    效果图:  jpg=>png,背景透明 步骤: 1.选择橡皮工具的第三个  魔术橡皮 保存为png,    按住Ctrl+alt+shift+s    保存: