Chapter 1. 变量与注释

变量解包

变量解包(unpacking)是 Python 里的一种特殊赋值操作,允许我们把一个可迭代对象(比如列表)的所有成员,一次性赋值给多个变量

PYTHON
user_id, (username, score) = [1, ['piglei', 100]]
点击展开查看更多

Python 还支持更灵活的动态解包语法。只要用星号表达式(*variables)作为变量名,它便会贪婪地捕获多个值,并将捕获到的内容作为列表赋值给 variables

PYTHON
username, *fruits, score = ['piglei', 'apple', 'orange', 'banana', 100]
点击展开查看更多

单下划线变量名 _

_ 本身没什么特别之处,这算是大家约定俗成的一种用法

类型注解

PYTHON
from typing import List

def remove_invalid(items: List[int]):
    """剔除 items 里面无效的元素"""
点击展开查看更多

几条变量命名的基本原则

注释

Chapter 2. 数值与字符串

字符串

数字

在 Python 中,一共存在三种内置数值类型:整型(int)、浮点型(float)和复数类型(complex)。

Chapter 3. 容器类型

在 Python 中,最常见的内置容器类型有四种:列表 list、元组 tuple、字典 dict、集合 set

特性元组 (tuple)列表 (list)集合 (set)
有序性有序有序无序
可变性==不可变==可变可变;有不可变版本 frozenset
元素是否可重复允许重复允许重复==不允许重复==

列表

元组

具名元组 namedtuple

Python 3.6 后可以使用 typing.NamedTuple,可读性更好:

PYTHON
>>> from typing import NamedTuple

>>> class Rectangle(NamedTuple):
     width: int
     height: int  # 并不会真的做类型校验

>>> rect = Rectangle(20, 35.5)
点击展开查看更多

较旧的方法:

PYTHON
>>> from collections import namedtuple

>>> Rectangle = namedtuple('Rectangle', ['width', 'height'])
>>> rect = Rectangle(20, 35.5)
>>> rect.width
20
>>> rect[1]
35.5
点击展开查看更多

字典

集合

集合运算

生成器 - 按需生成,不是一次性返回

定义一个生成器,需要用到生成器函数与 yield 关键字

PYTHON
def generate_even(max_number):
    """一个简单生成器,返回 0 到 max_number 之间的所有偶数"""
    for i in range(0, max_number):
        if i % 2 == 0:
            yield i
for i in generate_even(10):
    print(i)

>>> i = generate_even(10)
>>> next(i)
0
>>> next(i)
2
点击展开查看更多

使用 deque 在头部追加成员

Python 列表底层使用了 array 数据结构,当你在数组中间插入新成员时,该成员之后的其他成员都需要移动位置,该操作的平均时间复杂度是 O(n)

因此,在列表的头部插入成员,比在尾部追加要慢得多

deque 底层使用了双端队列,无论在头部还是尾部追加成员,时间复杂度都是 O(1)

PYTHON
from collections import deque
l = deque()
l.appendleft(i)
点击展开查看更多

使用集合判断成员是否存在

快速合并字典

PYTHON
>>> d1 = {'name': 'apple'}
>>> d2 = {'price': 10}

# d1、d2 原始值不会受影响
>>> {**d1, **d2}
{'name': 'apple', 'price': 10}
点击展开查看更多

还可以使用单星号 * 来解包任何可迭代对象

PYTHON
>>> [1, 2, *range3]
[1, 2, 0, 1, 2]

>>> l1 = [1, 2]
>>> l2 = [3, 4]
# 合并两个列表
>>> [*l1, *l2]
[1, 2, 3, 4]
点击展开查看更多

别把推导式当作代码量更少的循环

推导式的核心意义在于它会返回值——一个全新构建的列表,如果你不需要这个新列表,就失去了使用表达式的意义。==直接编写循环并不会多出多少代码量,而且代码更直观。==

让返回多个值的函数返回 NamedTuple

对于==未来可能会变动的多返回值函数==来说,如果使用 NamedTuple 类型对返回结果进行建模,已有的函数调用代码也不用进行任何适配性修改

PYTHON
from typing import NamedTuple

class Address(NamedTuple):
    """地址信息结果"""
    country: str
    province: str
    city: str

def latlon_to_address(lat, lon):
    return Address(
        country=country,
        province=province,
        city=city,
    )

addr = latlon_to_address(lat, lon)
# 通过属性名来使用addr
# addr.country / addr.province / addr.city
点击展开查看更多

Chapter 4. 条件分支

PYTHON
# true_value if <expression> else false_value
language = "python" if you.favor("dynamic") else "golang"
点击展开查看更多
PYTHON
# 绝大多数情况下,在分支判断语句里写 == True 都没有必要
# if user.is_active_member() == True:
if user.is_active_member():

# 省略零值判断
# if containers_count == 0:
if not containers_count:

# if fruits_list != []:
if fruits_list:
点击展开查看更多

Python 布尔值规则

修改对象的布尔值

所有用户自定义的类和类实例的计算结果都是 True,如果我们稍微改动一下这个默认行为,就能写出更优雅的代码。

可以通过定义 __bool____len__ 魔法方法来修改对象的布尔值判断行为。

PYTHON
class Account:
    def __init__(self, balance=0):
        self.balance = balance

    def __bool__(self):
        # 当账户余额为正时返回 True,否则返回 False
        return self.balance > 0
点击展开查看更多
PYTHON
class TodoList:
    def __init__(self):
        self.items = []

    def add(self, item):
        self.items.append(item)

    def __len__(self):  # len 应返回非负整数;在布尔判断中只关心是否为 0,非零一律为 True
        # 返回待办事项的数量
        return len(self.items)
点击展开查看更多
  1. 如果同时定义了 __bool__ 和 __len__,Python 会优先使用 __bool__
  2. 如果两者都没有定义,则对象默认为 True

整型驻留 Integer Interning

Python 语言使用了一种名为“整型驻留”(integer interning)的底层优化技术。

对于从 -5 到 256 的这些常用小整数,Python 会将它们缓存在内存里的一个数组中。当你的程序需要用到这些数字时,Python 不会创建任何新的整型对象,而是会返回缓存中的对象。这样能为程序节约可观的内存。

PYTHON
>>> a = 100
>>> b = 100
>>> a is b
True
>>> a = 1000
>>> b = 1000
>>> a is b
False
>>> a = b = 1000
>>> a is b
True
点击展开查看更多

Chapter 5. 异常与错误处理

LBYL(look before you leap)编程风格,常被翻译成「三思而后行」

EAFP(easier to ask for forgiveness than permission)风格,在 Python 世界里,EAFP 指不做任何事前检查,直接执行操作,==在外层用 try 来捕获可能发生的异常==

和 LBYL 相比,EAFP 编程风格更为简单直接,它总是直奔主流程而去,把意外情况都放在异常处理 try/except 块内消化掉。

try/except

PYTHON
def safe_int(value):
    """尝试把输入转换为整数"""
    try:
        return int(value)
    except TypeError:
        # 当某类异常被抛出时,将会执行对应 except 下的语句
        print(f'type error: {type(value)} is invalid')
    except ValueError:
        # 你可以在一个 try 语句块下写多个 except
        print(f'value error: {value} is invalid')
    finally:
        # finally 里的语句,无论如何都会被执行,哪怕已经执行了return
        print('function completed')
点击展开查看更多
PYTHON
try:
    sync_profile(user.profile, to_external=True)
except Exception as e:
    print("Error while syncing user profile")
else:
    send_notification(user, 'profile sync succeeded')
点击展开查看更多

抛出异常,而不是返回错误

返回错误并非解决此类问题的最佳办法,Python 有完善的异常机制

使用上下文管理器 with

with 是一个神奇的关键字,它可以在代码中开辟一段由它管理的上下文,并控制程序在进入和退出这段上下文时的行为。

==使用 @contextmanager 装饰器==

在日常工作中,我们用到的大多数上下文管理器,可以直接通过“生成器函数 +@contextmanager”的方式来定义,这比创建一个符合协议的类要简单得多

自定义异常类

Chapter 6. 循环与可迭代对象

iter()next() 内置函数

PYTHON
>>> l = ['foo', 'bar']
>>> iter_l = iter(l)
>>> next(iter_l)
'foo'
>>> next(iter_l)
'bar'
>>> next(iter_l)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
点击展开查看更多

自定义迭代器

实现下面这两个魔法方法

PYTHON
class Range7:
    """生成某个范围内可被 7 整除或包含 7 的整数"""
    def __init__(self, start, end):
        self.start = start
        self.end = end
        # 使用 current 保存当前所处的位置
        self.current = start
    def __iter__(self):
        return self
    def __next__(self):
        while True:
       # 当已经到达边界时,抛出异常终止迭代
       if self.current >= self.end:
           raise StopIteration
       if self.num_is_valid(self.current):
           ret = self.current
           self.current += 1
           return ret
       self.current += 1
    def num_is_valid(self, num):
        """判断数字是否满足要求"""
        if num == 0:
            return False
        return num % 7 == 0 or '7' in str(num)
点击展开查看更多
PYTHON
>>> r = Range7(0, 20)
>>> for num in r:
     print(num)

7
14
17
点击展开查看更多
PYTHON
>>> r = Range7(0, 20)
>>> tuple(r)
(7, 14, 17)
>>> tuple(r)  # 第二次只能得到一个空元组
点击展开查看更多

区分迭代器与可迭代对象


如果想让 Range7 对象在每次迭代时都返回完整结果,我们必须把现在的代码拆成两部分:可迭代类型 Range7 和迭代器类型 Range7Iterator,每次遍历 Range7 对象时,都会创建出一个全新的迭代器对象 Range7Iterator

PYTHON
class Range7:
    """生成某个范围内可被 7 整除或包含 7 的数字"""
    def __init__(self, start, end):
        self.start = start
        self.end = end
    def __iter__(self):
        # 返回一个新的迭代器对象
        return Range7Iterator(self)


class Range7Iterator:
    def __init__(self, range_obj):
        self.range_obj = range_obj
        self.current = range_obj.start
    def __iter__(self):
        return self
    def __next__(self):
        while True:
            if self.current >= self.range_obj.end:
                raise StopIteration
            if self.num_is_valid(self.current):
                ret = self.current
                self.current += 1
                return ret
            self.current += 1
    def num_is_valid(self, num):
        if num == 0:
            return False
        return num % 7 == 0 or '7' in str(num)
点击展开查看更多

__getitem__

生成器是迭代器

生成器(generator)利用其简单的语法,大大降低了迭代器的使用门槛,是优化循环代码时最得力的帮手。

生成器是一种“懒惰的”可迭代对象,使用它来替代传统列表可以节约内存,提升执行效率

但除此之外,==生成器还是一种简化的迭代器实现==,使用它可以大大降低实现传统迭代器的编码成本。因此在平时,我们基本不需要通过 __iter____next__ 来实现迭代器,只要写上几个 yield 就行。

PYTHON
def range_7_gen(start, end):
    """生成器版本的 Range7Iterator"""
    num = start
    while num < end:
        if num != 0 and (num % 7 == 0 or '7' in str(num)):
            yield num
        num += 1
>>> nums = range_7_gen(0, 20)
>>> iter(nums)
<generator object range_7_gen at 0x10404b2e0>
>>> iter(nums) is nums
True
>>> next(nums)
7
>>> next(nums)
14
点击展开查看更多

enumerate()

enumerate() 是 Python 的一个内置函数,它接收一个可迭代对象作为参数,返回一个不断生成 (当前下标,当前元素) 的新可迭代对象

itertools

itertools 是一个和迭代器有关的标准库模块

使用 product() 扁平化多层嵌套循环

PYTHON
>>> from itertools import product
>>> list(product([1, 2], [3, 4]))
[(1, 3), (1, 4), (2, 3), (2, 4)]
点击展开查看更多

使用 islice() 实现隔行处理,islice(seq, start, end, step)

PYTHON
from itertools import islice

def parse_titles_v2(filename):
    with open(filename, 'r') as fp:
        # 原文本每行之间隔了一个空行
        # 设置 step=2,跳过空行
        for line in islice(fp, 0, None, 2):
            yield line.strip()
点击展开查看更多

使用 takewhile() 替代 break 语句,==在每次开始执行循环体代码时,决定是否需要提前结束循环==

takewhile(predicate, iterable) 会在迭代第二个参数的过程中,不断使用当前值作为参数调用 predicate() 函数

BASH
for user in users:
		# 当第一个不合格的用户出现后,不再进行后面的处理
		if not is_qualified(user):
				break
...
⬇️
from itertools import takewhile
for user in takewhile(is_qualified, users):
点击展开查看更多

在循环中使用 else 关键字

for 循环和 while 循环后的 else 关键字,代表如果循环正常结束(没有碰到任何 break),便执行该分支内的语句

BASH
numbers = [1, 2, 3, 4, 5]

for num in numbers:
    if num == 6:
        print("找到数字 6")
        break
else:
    print("没有找到数字 6")  # 会执行这个
点击展开查看更多

中断嵌套循环

Chapter 7. 函数

别将可变类型作为参数默认值

PYTHON
def append_value(value, items=[]):
    items.append(value)
    return items
>>> append_value('foo')
['foo']
>>> append_value('bar')
['foo', 'bar']
点击展开查看更多
PYTHON
def append_value(value, items=None):
    if items is None:
        items = []
    items.append(value)
    return item
点击展开查看更多

functools

functools 是一个专门用来处理函数的内置模块

PYTHON
from functools import partial

def multiply(x, y):
    return x * y

double = partial(multiply, 2)  # x 固定位 2
double_2 = partial(multiply, y = 2)

print(double(5))
点击展开查看更多
PYTHON
from functools import lru_cache

@lru_cache(maxsize=128)
def function_to_cache(arg1, arg2, ...):
    ...
    return result
点击展开查看更多

给函数加状态

函数的状态(State)指的是函数在运行过程中保存的内部数据,可以让函数每次调用的输出不同

方法一:在函数内使用 global 关键字声明一个全局变量

用全局变量保存状态,其实是写代码时最应该避开的事情之一

方法二:闭包

方法三:类 ✅

Chapter 8. 装饰器

装饰器把影响函数的装饰行为移到了函数头部,降低了代码的阅读与理解成本,装饰器特别适合用来实现以下功能:

Chapter 9. 面向对象编程

私有属性是「君子协定」

在 Python 里,所有的类属性和方法默认都是公开的,不过你可以通过添加双下划线前缀 __ 的方式把它们标示为私有。

内置类方法装饰器

在编写类时,除了普通方法以外,我们还常常会用到一些特殊对象,比如类方法、静态方法等。要定义这些对象,得用到特殊的装饰器

==类方法==:可以用 @classmethod 装饰器定义一种特殊的方法:类方法(class method),它属于类但是无须实例化也可调用

PYTHON
>>> Duck.quack()
TypeError: quack() missing 1 required positional argument: 'self'

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    @classmethod
    def from_birth_year(cls, name, birth_year):
        age = datetime.date.today().year - birth_year
        return cls(name, age)

person1 = Person('Alice', 25)
person2 = Person.from_birth_year('Bob', 1990)
点击展开查看更多

==静态方法==:如果你发现某个方法不需要使用当前实例里的任何内容,那可以使用 @staticmethod

属性装饰器

在一个类里,属性代表状态,方法代表行为。属性可以通过 inst.attr 的方式直接访问,而方法需要通过 inst.method() 来调用

PYTHON
class FilePath:
    ...
    @property
    def basename(self):
        """获取文件名"""
        return self.path.rsplit(os.sep, 1)[-1]

>>> p = FilePath('/tmp/foo.py')
>>> p.basename
'foo.py'
点击展开查看更多

多重继承与 MRO

在复杂的继承关系下,如何确认子类的某个方法会用到哪个父类?

在解决多重继承的方法优先级问题时,Python 使用了一种名为 MRO(method resolution order)的算法。该算法会遍历类的所有基类,并将它们按优先级从高到低排好序

MRO 与 super()

大多数情况下,你需要的并不是多重继承,而也许只是一个更准确的抽象模型,在该模型下,最普通的继承关系就能完美解决问题

版权声明

作者: Aspi-Rin

链接: https://blog.aspi-rin.top/posts/python-%E5%B7%A5%E5%8C%A0%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/

许可证: CC BY 4.0

This work is licensed under a Creative Commons Attribution 4.0 International License. Please attribute the source.

开始搜索

输入关键词搜索文章内容

↑↓
ESC
⌘K 快捷键