1. 注释了解

注释是用来对关键代码解释说明的,被注释的代码不会被解释器执行。合理使用注释可以提高代码的可读性和维护性。支持的注释分类:单行注释和多行注释。

  1. 单行注释

以 # 符号开头,从 # 开始直到行尾的所有内容都会被视为注释。每个注释需要单独一行,或者跟在有效代码之后。例如:

# 这是一个单行注释
print("Hello, World!")  # 输出问候语句

代码规范建议:单独一行的注释,# 后面建议先添加一个空格,然后再编写相应的说明文字;代码后面增加的单行注释,注释应该至少离开代码 2 个空格;

  1. 多行注释

实际上 Python 没有专门的多行注释语法,只是可以使用三重引号(单引号或双引号)来创建多行字符串实现类型的功能。通常用于定义函数、类或模块的文档字符串(docstring)。而且这种形式的注释不仅可以在多行中写注释,还可以作为对象的文档说明。

def my_function():
    """
    这是一个多行注释的例子。
    使用三重双引号定义的字符串可以用作函数、类或模块的文档字符串。
    """
    pass
    
# 也可以直接用三重引号写注释,尽管这不是标准做法:

'''这是另一种
多行注释的方式,
但它实际上是一个字符串字面量,
不推荐用作注释'''
  • 注释使用注意
  • 简洁明了:注释应尽量简短且清晰,帮助读者快速理解代码的目的。
  • 更新注释:当修改代码时,记得同步更新相关的注释,以确保其准确性。
  • 避免过度注释:不要为每一行代码都添加注释,而是选择那些不容易理解的部分进行解释。
  • 使用文档字符串:对于函数、类和模块,使用文档字符串来描述其功能、参数和返回值。
  • 交替使用:如果外面使用三个单引号,则里面要使用三个双引号,反之亦然。即,在嵌套使用注释的时候,两种多行注释形式交替使用。

2. 变量和常量

变量是计算机编程中的一个核心概念。简单来说,变量是一个可以存储数据的容器,这个数据可以是数字(如整数、浮点数)、文本(字符串)、复杂的数据结构(如列表、字典、集合),甚至是函数等各种类型的信息。在程序运行过程中,变量的值可以被读取、修改,从而使程序能够根据变量的不同状态执行不同的任务。

从计算机内存的角度来讲,当定义一个变量时,实际上是在内存中分配了一块空间来存储这个变量的值。变量名就像是这个内存空间的标签,通过这个标签(变量名),程序可以找到对应的内存空间,从而获取或修改存储在其中的值。

2.1 变量的定义与赋值

大多数编程语言的变量都需要先声明(定义)后引用,不过Python在声明变量的同时就可以赋值。
而且Python可以直接给变量赋值而无需声明其类型,Python会根据所赋予的值自动推断出变量的数据类型。

  • 在 Python 中,定义变量非常简单,直接使用变量名并通过赋值运算符 “=” 赋予一个值即可。
  • Python 是动态类型语言,变量的类型由赋值的值决定,不需要预先声明类型。
# 格式:变量名 = 变量值
-- 变量名  指向值所在的内存地址,是访问至值的唯—方式。
-- =号     赋值符号,用来将变量值的内存地址绑定给变量名。
-- 变量值  存储的数据,反映的是事物的伏态。

# 例如:
# 定义一个整数变量
age = 25
# 定义一个字符串变量
name = "John"
# 定义一个列表变量
fruits = ["apple", "banana", "cherry"]

python解释器执行到变量定义的代码时会申请内存空间存放变量值,然后将变量值的内存地址绑定给变量名。

2.2 变量的访问与使用

一旦变量被定义并赋值,就可以在程序中访问和使用它们。不仅可以在算术表达式、逻辑表达式等各种表达式中使用变量,也可以将变量作为参数传递给函数,对于像列表、字符串这样的序列类型变量,可以进行索引和切片等操作。

# 算术表达式
a = 10
b = 5
sum_result = a + b
print(sum_result)  # 输出15

# 逻辑表达式
flag = a > b
print(flag)  # 输出True

# 变量作为参数传递
def greet(name):
    print("Hello, " + name)
user_name = "Alice"
greet(user_name)  # 输出Hello, Alice

# 列表变量索引操作
my_list = [1, 2, 3, 4, 5]
first_element = my_list[0]
print(first_element)  # 输出1

2.3 变量的更新

变量的值可以在程序运行过程中被重新赋值或更新。由于 Python 是动态类型语言,变量可以重新赋值为不同类型的值。

x = 10
print(x)  # 输出10
x = "Now it's a string"
print(x)  # 输出Now it's a string

2.4 变量名的命名规则

  • 命名原则:变量名应该有意义,能够直观地反映变量所存储的内容或用途。;
  • 命名元素:变量名通常由字母(包括大写和小写)、数字和下划线(_)组成,不能以数字开头;
  • 避免使用关键字:关键字是有特殊用途的,不能用作变量名。常用关键字如下:
import keyword;          # 引入模块
print(keyword.kwlist);   # 打印关键字
['and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from','global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass', 'print', 'raise', 'return', 'try', 'while', 'with', 'yield']
  • 对于常量,通常使用全部大写的字母,单词之间用下划线分隔。

注意:不建议使用拼音,也不要用中文,在见名知意的前提下尽可能短。

变量名的命名风格:

  • 纯小写加下划线的方式(在python中,关于变量名的命名 推荐 使用这种方式)

    age_of_alex = 73
    print(age_of_alex)
    
  • 大驼峰法(首字母大写)

    AgeOfAlex = 73
    print(AgeOfAlex)
    

2.5 变量的三个重要属性

id、type、value 三个属性共同描述了一个变量的状态和行为。

  • id:表示变量在内存中的唯一标识符,用于区分不同的变量实例。

  • type:定义了变量可以存储的数据种类及其操作规则。

  • value:表示变量实际存储的数据内容。

name = 'zhangsan'

print(id(name))    # id = 2429388598384 
print(type(name))  # <class 'str'>
print(name)        # zhangsan

2.6 常量:不变的量

在 Python 中,常量的概念与某些其他编程语言有所不同。Python 没有真正的“常量”类型或关键字(如 C 语言中的 const 或 Java 中的 final),但是在程序的开发过程中会涉及到常量的概念。所以按照惯例,Python 开发者通常使用全部大写的变量名来表示常量。这种命名方式提醒其他开发者这些变量不应该被修改。但这仅是一种约定、规范。

PI = 3.14159
MAX_CONNECTIONS = 100

单从语法层面去讲,常量的使用与变量完全一致。

3. PEP8规范

PEP 8 是 Python 官方的代码风格指南,由 Python 之父 Guido van Rossum 等人制定。它统一了代码格式标准,提升可读性与可维护性,是团队协作和代码审查的通用基准。

注意:PEP 8 是约定而非强制,不会引发语法错误。但正规企业、开源项目及技术面试均将其作为硬性要求,是 Python 开发者的必备素养。

3.1 缩进与空白

规范项具体要求
缩进统一使用 4 个空格,禁用 Tab 字符
行长度代码行 ≤ 79 字符;注释/文档字符串 ≤ 72 字符
空行模块级函数/类之间空 2 行;类内方法之间空 1 行
运算符空格二元运算符两侧各加 1 个空格:x = 1
# ✅ 正确示例
def calculate_total(price, quantity):
    if price > 0 and quantity > 0:
        return price * quantity
    return 0


class OrderProcessor:
    def __init__(self):
        self.orders = []
    
    def add_order(self, order):
        self.orders.append(order)


# ❌ 错误示例(Tab 缩进)
def calculate_total(price, quantity):
	if price > 0:
		return price * quantity

3.2 命名规范

Python 采用多种命名约定区分代码元素:

类型命名风格示例适用场景
变量 / 函数小写+下划线user_name、get_user_by_id()普通变量、函数、方法
常量全大写MAX_RETRY、DEFAULT_TIMEOUT模块级常量
类驼峰式UserManager、HttpResponse类名、异常名
私有属性单下划线前缀_internal_cache模块/类内部使用
强私有双下划线前缀__password_hash触发名称改写(name mangling)
魔术方法双下划线前后缀__init__、__repr__、__eq__Python 内置协议方法

命名原则:命名应体现用途而非实现。get_data() 优于 get_list(),process_payment() 优于 func1()。

3.3 导入规范

导入语句应按以下顺序分组,每组之间空一行:

  1. 标准库导入(os, sys, typing 等)
  2. 第三方库导入(numpy, requests 等)
  3. 本地应用/模块导入(项目内部模块)
# ✅ 规范示例
import os
import sys
from typing import List, Optional

import numpy as np
import pandas as pd
from fastapi import FastAPI

from myproject.database import Session
from myproject.models import User, Order
from myproject.utils.logger import get_logger


# ❌ 反模式示例
import os, sys, json                    # 单行多导入
from module import *                     # 通配符导入(污染命名空间)
import numpy as np; import pandas as pd  # 行尾分号

3.4 字符串引号

场景推荐做法
普通字符串单引号 ' 或双引号 " 均可,项目内保持一致
含引号的字符串灵活选择避免转义:"It's fine" 或 'He said "Hi"'
文档字符串统一使用双引号三引号:"""文档内容"""
# ✅ 一致即可
message = 'Hello World'
path = "/usr/local/bin"

# ✅ 文档字符串规范
def fetch_data():
    """从远程 API 获取数据并解析为 JSON 格式。
    
    Returns:
        dict: 解析后的数据字典
    """
    pass

3.5 表达式与语句

基础语法

# ✅ 每行一个语句,避免分号
x = 1
y = 2

# ❌ 不推荐
x = 1; y = 2


# ✅ 空值判断使用 `is`
if user is None:
    return "匿名用户"

# ❌ 不推荐(身份判断 vs 值判断)
if user == None:
    return "匿名用户"


# ✅ 容器/布尔值直接判断(隐式真假值)
if users:                    # 非空即真
    process(users)

if not is_valid:             # 显式否定
    raise ValueError()

# ❌ 冗余判断
if len(users) > 0:           # 对序列用 bool() 即可
    process(users)

if is_valid == False:        # 直接与布尔字面量比较
    raise ValueError()

比较运算符链式写法

# ✅ Pythonic 风格
if 0 <= x <= 100:
    pass

# ❌ 其他语言风格(冗余)
if x >= 0 and x <= 100:
    pass

3.6 函数与类定义

函数规范

from typing import Optional

def calculate_area(radius: float, precision: int = 2) -> float:
    """计算圆的面积。
    
    根据给定半径计算圆面积,支持指定小数精度。
    
    Args:
        radius: 圆的半径,必须为正数。
        precision: 结果保留的小数位数,默认为 2。
        
    Returns:
        指定精度的圆面积数值。
        
    Raises:
        ValueError: 当 radius 为负数时抛出。
    """
    if radius < 0:
        raise ValueError("半径不能为负数")
    
    area = 3.14159265359 * (radius ** 2)
    return round(area, precision)

类规范

class Circle:
    """表示二维平面上的圆形几何图形。
    
    Attributes:
        radius: 圆的半径。
        center: 圆心坐标元组 (x, y)。
    """
    
    PI = 3.14159265359          # 类常量
    
    def __init__(self, radius: float, center: tuple = (0, 0)):
        self.radius = radius
        self.center = center
        self._area_cache: Optional[float] = None  # 私有属性
    
    def area(self) -> float:
        """计算并缓存圆面积。"""
        if self._area_cache is None:
            self._area_cache = self.PI * (self.radius ** 2)
        return self._area_cache

3.7 高级规范与最佳实践

场景规范说明示例
列表推导简洁逻辑使用;多层嵌套或复杂条件改用循环[x*2 for x in items if x > 0]
异常捕获捕获具体异常类型,禁止裸 exceptexcept ValueError: ✓
返回值一致性函数所有分支返回类型一致return result / return None
否定成员判断使用 not in 而非 not ... inif x not in items: ✓
布尔运算符避免 == True/False/None,直接用对象真假值if enabled: ✓
# ✅ 异常处理最佳实践
try:
    config = load_config(path)
except FileNotFoundError:
    logger.warning("配置文件不存在,使用默认配置")
    config = DEFAULT_CONFIG
except json.JSONDecodeError as e:
    logger.error(f"配置格式错误: {e}")
    raise ConfigError("无效的配置文件") from e

# ❌ 危险做法
try:
    config = load_config(path)
except:                      # 捕获 KeyboardInterrupt、SystemExit 等!
    config = {}


# ✅ 否定成员判断
if item not in valid_items:
    raise InvalidItemError()

# ❌ 不自然表达
if not item in valid_items:
    raise InvalidItemError()

3.8 自动化工具检查

使用以下工具自动检查与格式化代码:

# 代码检查(flake8 整合 pyflakes、pycodestyle、mccabe)
pip install flake8
flake8 your_project/ --max-line-length=88

# 自动格式化(Black 是事实标准,行宽默认 88)
pip install black
black your_project/

# 导入排序
pip install isort
isort your_project/

# 类型检查(配合 PEP 484 类型提示)
pip install mypy
mypy your_project/

Python 官方提供有一系列 PEP(Python Enhancement Proposals) 文档,其中第 8 篇文档专门针对 Python 的代码格式 给出了建议,也就是俗称的 PEP 8。文档地址:https://www.python.org/dev/peps/pep-0008/

4. 补充内容

4.1 补充:内存管理(垃圾回收机制)

垃圾回收机制(简称GC)是Python解释器自带一种机制,专门用来回收不可用的变量值所占用的内存空间。

当一个变量值被绑定的变量名的个数为0时,该变量值无法被访问到,称之为垃圾。

  • 垃圾回收机制原理

Python的GC模块主要运用了“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。

  • 引用计数

引用计数是 Python 内存管理中最基础的一种方式,它为每个对象维护一个计数器,用于记录当前有多少个引用指向该对象。每当有一个新的引用指向这个对象时,计数器加一;每当有一个引用被删除或超出作用域时,计数器减一。当计数器降为零时,表示没有任何引用指向该对象,Python 的垃圾回收机制就会释放该对象所占用的内存。

import sys

# 创建一个列表对象并检查其引用计数
a = [1, 2, 3]
print(sys.getrefcount(a))  # 输出比实际高1,因为 getrefcount 函数本身也会增加一次引用

b = a  # b 和 a 指向同一个列表对象
print(sys.getrefcount(a))  # 参考计数再次增加

del b  # 删除 b 对列表的引用
print(sys.getrefcount(a))  # 参考计数减少

c = [1, 2, 3]  # 创建一个新列表,内容相同但不是同一对象
print(sys.getrefcount(c))  # 新列表有自己的引用计数
  • 在 Python 中,sys.getrefcount() 函数可以用于查看一个对象的引用计数。因为在调用getrefcount 函数时,函数内部也会对对象进行引,所以输出值往往比实际值大1,这一点也需要注意。
  • del 语句 说是删除变量,实际上是解除对对象的引用,如果这是该对象的最后一个引用,则该对象将被垃圾回收机制回收。

但是当两个对象互相引用但没有外部引用指向它们,即使这些对象实际上已经不再需要,它们的引用计数也不会降为零,导致内存泄漏。也就是说引用计数无法处理循环引用的问题。

  • 循环引用

循环引用会导致值不再被任何名字关联,但是值的引用计数并不会为0,应该被回收但不能被回收,继而有可能导致内存泄露问题。

虽然Python还配备了一个周期性运行的垃圾收集器(Garbage Collector, GC)来处理循环引用问题,但在某些情况下,特别是当涉及复杂的对象图或大型数据结构时,GC可能不会及时回收这些对象,从而引发潜在的内存泄漏。

l1=[111,]
l2=[222,]

l1.append(l2)   # 此时l1存放的是[值111的内存地址,l2列表的内存地址]
l2.append(l1)   # 此时l2存放的是[值222的内存地址,l1列表的内存地址]

print(id(l1[1]))       # 2333670512832
print(id(l2))          # 2333670512832

print(l2)            # [222, [111, [...]]]
print(l1[1])         # [222, [111, [...]]]

del l1   # 列表1的引用计数减1,列表1的引用计数变为1(还有一个间接引用)
del l2   # 列表2的引用计数减1,列表2的引用计数变为1(还有一个间接引用)

# 此时两个列表会出现永远取不到,而且是在引用计数不为0的情况(保留一个间接引用)
  • 标记清除

在Python中,“标记-清除”(Mark-and-Sweep)是垃圾回收机制的一部分,用于处理循环引用和其他复杂对象图中的内存管理问题。Python主要依赖引用计数(Reference Counting)来进行内存管理,但对于无法通过引用计数解决的循环引用问题,则会使用“标记-清除”算法。

内存中有两块区域:堆区与栈区,在定义变量时,变量名与值内存地址的关联关系存放于栈区,变量值存放于堆区,内存管理回收的则是堆区的内容。

在这里插入图片描述

标记/清除算法的做法是当应用程序可用的内存空间被耗尽的时,就会停止整个程序,然后进行两项工作,第一项则是标记,第二项则是清除。

  • 【标记】

通俗地讲就是标记的过程就行相当于从栈区出发一条线,“连接”到堆区,再由堆区间接“连接”到其他地址,凡是被这条自栈区起始的线连接到内存空间都属于可以访达的,会被标记为存活。具体而言标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。

  1. 【清除】

清除的过程将遍历堆中所有的对象,将没有标记存活的对象全部清除掉。

直接引用指的是从栈区出发直接引用到的内存地址,间接引用指的是从栈区出发引用到堆区后再进一步引用到的内存地址,以我们之前的两个列表l1与l2为例画出如下图像:

在这里插入图片描述
当我们同时删除l1与l2时,会清理到栈区中l1与l2的内容。这样在启用标记清除算法时,发现栈区内不再有l1与l2(只剩下堆区内二者的相互引用),于是列表1与列表2都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄漏问题。

在这里插入图片描述

  • 分代回收

基于引用计数的回收机制,每次回收内存,都需要把所有对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了 分代回收 来提高回收效率。分代回收是基于一个重要的观察:大多数对象的生命周期都很短,只有少数对象会存活较长时间。根据这一特性,Python 将对象分为不同的“代”,并针对不同代的对象采取不同的垃圾回收策略。

核心思想:新创建的对象往往更有可能成为垃圾,所以应该对这类较新的对象进行更为频繁的检查。相对地,那些存在时间较久的对象,由于它们大概率是活跃的,参与垃圾回收的频率便较低 。如果某个变量在多次垃圾回收扫描中都未被回收,垃圾回收(GC)机制会判定它是一个常用对象,此后降低对它的扫描频率。

实现原理:

【分代】
分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)。新定义的变量,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低。
在这里插入图片描述
【回收】
当触发垃圾回收时,Python会从最低代开始检查(通常是新生代),并将未被标记为垃圾的对象提升到更高的一代。如果某一代的对象数量达到了预设阈值,或者内存使用情况触发了垃圾回收,那么该代及其以下的所有代都会被检查和清理。

4.2 补充:is 与 == 的区别

  • == 运算符:用于检查两个对象的值(value)是否相等。它会调用对象的 __eq__() 方法来比较两个对象的内容(即它们的值)。对于内置类型(如整数、字符串、列表等),它会比较这些对象的实际内容。

  • is 运算符:用于检查两个对象是否是同一个对象,即它们是否有相同的内存地址(id)。它不会比较对象的内容,而是直接比较对象的标识(即它们的内存地址)。如果两个变量指向同一个对象,则 is 返回 True;否则返回 False。

  • is:比较左右两个值身份id是否相等
  • ==:比较左右两个值他们的值value是否相等
id不同的情况下,值有可能相同,即两块不同的内存空间里可以存相同的值;
id相同的情况下,值一定相同,x is y成立,x == y也必然成立;
a = [1, 2, 3]
b = [1, 2, 3]

print(a == b)  # 输出: True
print(a is b)  # 输出: False

print(id(a))   # 2583117304192
print(id(b))   # 2583117306048

4.3 补充:内存优化机制(小整数池[-5,256])

Python 为了优化内存使用和提高性能,对特定范围内的整数进行了缓存。这个机制被称为“小整数池”或“整数缓存”。具体来说,Python 对于 -5 到 256 范围内的整数会进行缓存处理,使得这些整数在程序运行期间只会创建一次,并且可以在多个地方共享。

从python解释器启动那一刻开始,就会在内存中事先申请好一系列内存空间存放好常用的整数。

# 小整数范围内的整数
x = 10
y = 10

print(x == y)  # 输出: True
print(x is y)  # 输出: True (因为 10 在缓存范围内)

# 超出小整数范围的整数
a = 257
b = 257

print(a == b)  # 输出: True
print(a is b)  # 输出: False (因为 257 不在缓存范围内)

除了小整数池之外,Python 还对短字符串、空元组等进行了类似的优化。例如,短字符串 “hello” 通常也会被缓存,使得多次创建相同的短字符串不会占用额外的内存。

注意:除了python自身对缓存进行了优化,一些IDE也对缓存进行了进一步的优化,这也就导致了 == 运算符 和 is 运算符 在使用的时候区别不是很明显,容易导致混用出错。建议使用 == 进行值比较,避免依赖 is 来检查值的相等性。


更多推荐