文章目录
1. 基础了解
1.1 运行方式了解
Python有多种运行方式,以下是几种常见的执行Python代码的方法:
- 交互式解释器: 打开终端或命令提示符,输入python或python3(取决于你的系统配置),即可进入Python交互模式。你可以直接输入代码并立即看到结果。
- 脚本文件执行: 将Python代码保存到一个.py扩展名的文件中,例如hello.py。然后在终端或命令行中,通过python hello.py或python3 hello.py命令来运行这个脚本。
- 集成开发环境(IDE): 如PyCharm、VS Code(配Python插件)、Jupyter Notebook等,这些环境提供了编辑、调试和运行Python代码的图形界面。
- 在线编程平台: Repl.it、Google Colab、JupyterHub等在线平台允许用户在网页上编写并运行Python代码,无需本地安装Python环境。
- 模块导入与作为库使用: Python代码也可以被其他Python程序作为模块导入并执行,这种方式常用于组织大型项目或开发库供他人使用。
- 嵌入式Python: Python可以嵌入到其他应用程序中,允许在非Python环境中执行Python代码,如C/C++程序通过Python API调用Python函数。
每种方式有其特定的使用场景和优势,可根据需求选择合适的运行方式。
对于初学者来说,使用命令行或简单的 IDE 可能是最好的起点;而对于更复杂的项目,则可能需要考虑更高级别的工具和平台。
1.2 注释了解
注释是用来对关键代码解释说明的,被注释的代码不会被解释器执行。合理使用注释可以提高代码的可读性和维护性。
- 支持的注释分类:单行注释和多行注释
- 单行注释:以
#
符号开头,从#
开始直到行尾的所有内容都会被视为注释。每个注释需要单独一行,或者跟在有效代码之后。例如:
# 这是一个单行注释
print("Hello, World!") # 输出问候语句
代码规范建议:单独一行的注释,# 后面建议先添加一个空格,然后再编写相应的说明文字;代码后面增加的单行注释,注释应该至少离开代码 2 个空格;
- 多行注释
实际上 Python 没有专门的多行注释语法,只是可以使用三重引号(单引号或双引号)来创建多行字符串实现类型的功能。
这通常用于定义函数、类或模块的文档字符串(docstring)。而且这种形式的注释不仅可以在多行中写注释,还可以作为对象的文档说明。
def my_function():
"""
这是一个多行注释的例子。
使用三重双引号定义的字符串可以用作函数、类或模块的文档字符串。
"""
pass
# 也可以直接用三重引号写注释,尽管这不是标准做法:
'''这是另一种
多行注释的方式,
但它实际上是一个字符串字面量,
不推荐用作注释'''
- 注释使用注意
- 简洁明了:注释应尽量简短且清晰,帮助读者快速理解代码的目的。
- 更新注释:当修改代码时,记得同步更新相关的注释,以确保其准确性。
- 避免过度注释:不要为每一行代码都添加注释,而是选择那些不容易理解的部分进行解释。
- 使用文档字符串:对于函数、类和模块,使用文档字符串来描述其功能、参数和返回值。
- 交替使用:如果外面使用三个单引号,则里面要使用三个双引号,反之亦然。即,在嵌套使用注释的时候,两种多行注释形式交替使用。
1.3 代码规范了解
遵循编码规范不仅可以提高代码质量,还能增强团队协作效率。对于初学者可以从遵守 PEP 8
, 开始逐步养成良好的编程习惯。
PEP 8
是 Python 官方推荐的编码风格指南,涵盖了代码格式、命名约定、注释等多个方面。虽然不是强制性的,但大多数 Python 开发者都遵循它。
Python
官方提供有一系列 PEP(Python Enhancement Proposals) 文档,其中第 8 篇文档专门针对 Python 的代码格式 给出了建议,也就是俗称的PEP 8
。
1.3.1 代码布局类规范
- 缩进
Python中依靠缩进来区分代码块的开始和结束。每一级缩进使用 4 个空格。不要使用制表符(Tab)进行缩进,因为不同的文本编辑器对制表符的解释可能不同,这会导致代码缩进在不同环境下不一致。例如:
def my_function(): if True: print("Indentation with 4 spaces")
Python解释器会严格检查代码块的缩进,不正确的缩进会导致
IndentationError
错误。
- 行长度
保持所有行的最大长度为
79
个字符。这样可以确保代码在大多数终端和编辑器中能够良好地显示,而不需要水平滚动。
但是在实际开发中有时会遇到需要更长行情况的话。对于长的表达式或语句,可以使用括号来隐式换行。或者在适当的位置使用反斜杠(\)进行换行,但要注意反斜杠后的换行符会被忽略。所以更加 建议使用括号来隐式换行拼接。# 超过 79 字符的行应该被拆分 def long_function_name( var_one, var_two, var_three, var_four): print("This function has a very long argument list.") # 长字符串可以通过括号续行 long_string = ( "这是一个非常长的字符串," "它超出了常规的79个字符限制,因此我们将其拆分为多行。" )
隐式的行拼接可以通过将表达式包含在
括号 ()
,方括号 []
, 或花括号 {}
内来实现。还可以添加注释,并且后续行的缩进不会影响程序结构。此外,后续行也可以是空白行,用于提高可读性。long_string = [ "这是一个非常长的字符串," "它超出了常规的79个字符限制," # 续行并添加注释 "因此我们将其拆分为多行。" ]
- 空行
空行的使用对于提高代码的可读性和结构清晰性非常重要。
- 顶级函数定义和类定义之间用两个空行分隔,以增强代码的模块性和可读性。
- 类的方法定义之间应使用一个空行分隔。
- 在导入声明之后应该有一个空行。可以清楚地区分导入部分和实际代码部分。
- 在函数或方法体内,可以使用空行来分隔逻辑段落,以增强可读性。
- 每个文件应在末尾包含一个空行。这确保了文件以换行符结束,符合 Unix 文件格式标准。
import os import sys def function_one(): """这是一个函数。""" pass class MyClass: """这是一个类。""" def method_one(self): """这是类的一个方法。""" pass def method_two(self): """这是类的另一个方法。""" pass def function_two(): """这是另一个函数。""" pass
- 制表符和空格
制表符和空格不要进行混用,强烈推荐仅使用空格而不是制表符。
如果两者混用了,虽然在编辑环境中显示两条语句为同一缩进层次,但因为制表符和空格的不同会导致 Python 解释为两个不同的层次。可以在调用 Python 命令行解释器时使用 -t
选项对代码中不合法的混合制表符和空格发出警告,或使用 -tt
将警告将变成错误。
空格的一般使用有:
- 在二元运算符两边各空一格,比如赋值(=)、比较(==, <, >, !=, <>, <=, >=, in, not in, is, is not), 布尔(and, or, not),算术操作符两边的空格可灵活使用,但两侧务必要保持一致;
- 不要在逗号、分号、冒号前面加空格,但应该在它们后面加(除非在行尾);
- 函数的参数列表中,逗号之后要有空格;
- 函数的参数列表中,默认值等号两边不要添加空格;
- 左括号之后,右括号之前不要加添加空格;
- 参数列表, 索引或切片的左括号前不应加空格
- 当’='用于指示关键字参数或默认参数值时,不要在其两侧使用空格
- 分号使用
不推荐使用分号来分隔同一行中的多条语句。PEP 8 强调代码的可读性和简洁性,因此建议每条语句独占一行,不要将多条语句放在同一行中,即使它们之间用分号分隔。
- 避免在行尾加分号:因为Python 解释器会在遇到换行符时自动结束语句,因此在行尾添加分号不仅多余,而且可能降低代码的可读性。
print("Hello") # 正确 print("Hello"); # 不推荐
- 避免在同一行中使用分号分隔多条语句:这会使得代码难以阅读和维护。
- 代码编码格式
Python 3默认使用 UTF - 8
编码,所以在Python 3中通常不需要指定编码。但如果要兼容Python 2的话,因为Python 2的默认编码是 ASCII
,所以要建议显式声明为 UTF-8。
- 显式声明 UTF-8 编码,在文件开头添加编码声明注释
# -*- coding: utf-8 -*-
- 模块导入
导入语句应放在文件顶部,位于模块注释和文档字符串之后,任何其他代码之前。
导入应该按照从最通用到最不通用的顺序分组:标准库导入、第三方库导入、应用程序指定导入,分组之间空一行。
每个导入语句应单独占一行,避免在一行中导入多个模块。
import os
import sys
import numpy as np
import pandas as pd
from my_module import MyClass
from my_module import my_function
- main()函数使用
在 Python 中,main() 函数并不是一个内置的关键字或特殊函数,而是一个约定俗成的编程实践。它通常用于定义程序的主要执行逻辑,并通过 if __name__ == "__main__":
守卫来来检查当前模块是否是主程序(即是否直接运行了这个文件)。如果是,则调用 main() 函数;如果不是(例如,作为模块导入),则不会执行 main() 函数中的代码。
def main():
# 主程序逻辑
print("This is the main function.")
if __name__ == "__main__":
main()
1.3.2 命名规范
合理的命名不仅可以提高代码的可读性,还减少了潜在的错误和混淆。
- 包和模块名称:使用简短的小写字母,可以使用下划线分隔单词(但应尽量避免),例如:
my_module.py
; - 类名:采用驼峰命名法(CamelCase),即每个单词的首字母大写,单词之间没有下划线,例如:
MyClass
,EmployeeRecord
; - 函数名、变量名和方法名:使用小写字母,单词之间用下划线分隔(lower_case_with_underscores),例如:
def my_function()
,user_name = "Alice"
; - 常量:使用大写字母,单词之间用下划线分隔,例如:
MAX_CONNECTIONS = 5000
; - 私有方法和内部属性:在类中,使用单个下划线前缀表示私有方法或内部属性,例如:
_internal_method()
,_private_attribute
; - 特殊方法:Python 的特殊方法(如
__init__
,__str__
等)以双下划线开头和结尾。不要使用这些特殊方法作为普通方法名,以避免冲突; - 避免使用单个字符的名称(除了计数器和迭代器):尽量使用有意义的名称,使代码更具可读性。例如:i, j, k 可以用于简单的循环索引,但其他情况下应使用更描述性的名称;
- 不要使用保留字:避免使用 Python 的保留字(如
class
,def
,return
等)作为变量名或函数名; - 函数和方法参数:参数名应简洁且具有描述性。如果参数是布尔值,使用
is_*
或has_*
前缀,例如:def is_valid(user_input):
; - 避免混淆的名称:避免使用容易混淆的名称,如 l(小写的 L),O(大写的 o),和 I(大写的 i),因为它们可能与数字 1 和 0 混淆。
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
单从语法层面去讲,常量的使用与变量完全一致。
补充:内存管理(垃圾回收机制)
垃圾回收机制(简称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的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。
- 【清除】
清除的过程将遍历堆中所有的对象,将没有标记存活的对象全部清除掉。
直接引用指的是从栈区出发直接引用到的内存地址,间接引用指的是从栈区出发引用到堆区后再进一步引用到的内存地址,以我们之前的两个列表l1与l2为例画出如下图像:
当我们同时删除l1与l2时,会清理到栈区中l1与l2的内容。这样在启用标记清除算法时,发现栈区内不再有l1与l2(只剩下堆区内二者的相互引用),于是列表1与列表2都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄漏问题。
- 分代回收
基于引用计数的回收机制,每次回收内存,都需要把所有对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了 分代回收
来提高回收效率。分代回收是基于一个重要的观察:大多数对象的生命周期都很短,只有少数对象会存活较长时间。根据这一特性,Python 将对象分为不同的“代”,并针对不同代的对象采取不同的垃圾回收策略。
核心思想:新创建的对象往往更有可能成为垃圾,所以应该对这类较新的对象进行更为频繁的检查。相对地,那些存在时间较久的对象,由于它们大概率是活跃的,参与垃圾回收的频率便较低 。如果某个变量在多次垃圾回收扫描中都未被回收,垃圾回收(GC)机制会判定它是一个常用对象,此后降低对它的扫描频率。
实现原理:
【分代】
分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)。新定义的变量,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),会将它移动到更高一级的青春代,青春代的gc扫描的频率低于新生代(扫描时间间隔更长),假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,接下来,青春代中的对象,也会以同样的方式被移动到老年代中。也就是等级(代)越高,被垃圾回收机制扫描的频率越低。
【回收】
当触发垃圾回收时,Python会从最低代开始检查(通常是新生代),并将未被标记为垃圾的对象提升到更高的一代。如果某一代的对象数量达到了预设阈值,或者内存使用情况触发了垃圾回收,那么该代及其以下的所有代都会被检查和清理。
补充: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
补充:内存优化机制(小整数池[-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 来检查值的相等性。