Skip to main content

变量

info

错误绝不应该悄无声息地过去。除非明确地被静默。面对歧义,拒绝猜测的诱惑。

PEP 20 – Python之禅

变量

变量的创建

这是一个变量创建的示例。

# 变量名 = 数据
a = 1
数据1被创建,后被引用,并被标注为变量名a

变量命名规则

1. 变量名只能包含字母、数字和下划线。变量名可以字母或下划线打头,但不能以数字打头,例如,可将变量命名为 `message_1`,但不能将其命名为 `1_message`。
2. 变量名不能包含空格,但可使用下划线来分隔其中的单词。例如,变量名 `greeting_message` 可行,但变量名 `greeting message` 会引发错误。
3. 不要将 Python 关键字和函数名用作变量名,即不要使用 Python 保留用于特殊用途的单词,如 `print` `list` `dict`等。
4. 变量名应既简短又具有描述性。例如,`name` 比 `n` 好,`student_name` 比 `s_n` 好,`name_length` 比 `length_of_persons_name` 好。
5. 约定:全大写字母表示这个变量通常不会发生改变,表示常量。
6. 约定:变量名前加`_`表示这个变量为程序过程中间私有变量,不建议使用。
7. 驼峰命名法:变量名由多个单词组成,每个单词的首字母大写。如`StudentName`。
8. 蛇形命名法:变量名由多个单词组成,每个单词的小写,单词之间用下划线连接。如`student_name`。

别名、相等性与一致性

举个通俗的例子:变量是一个标签,用于标记数据。变量名是标签的名字,数据是标签指向的内容。语法示例:

# 变量名 = 变量名 2 = 变量名 3 = 数据 
# 这种写法表示 数据 有多个名字,好比某人又叫张三、又叫Bob

# 此时该数值的任意修改都会影响x,y,z的值,因为他们表示的都是同一个数据的内存指向
# 多出来的x和y是z的别名
x = y = z = [0,1]

e = [0,1]
f = e # 变量e和变量f绑定的是同一个值,多出来的f是e的别名

# 一次性给多个变量赋值
a = 1
b = 2
# 可以写为:
a,b = 1,2
# a,b 交换值可写为:
a,b = b,a


# 变量可以重新指向新的数据,此时旧的数据1由于缺少引用,会被Python的垃圾回收机制自动回收。
a = 1
a = [1,2,3]

# 此时g虽然也是[1,2,3],但是g和a是两个不同的变量,他们指向不同的内存地址。即a和g值相等,但不是同一个对象。
g = [1,2,3]

print(a == g) # True
print(a is g) # False

# 比较特别是None,None是Python中表示空值。且为单例常量,即整个程序中只有一个 None 对象。
# Python3.12中5个单例常量:Ellipsis, False, None, NotImplemented, True
a = None
b = None
print(a is b) # True - 它们指向同一个对象
print(id(a) == id(b)) # True - 内存地址相同

驻留机制

驻留是一种数据缓存机制,对不可变数据类型( 字符串、数字、布尔值等)使用同一个内存地址,防止重复创建热门对象,例如:0、1等,有效的节省了空间。使用不同的环境时,代码的优化方式不同。

在交互控制台中

`-5~256` 之间整数对象,会被驻留不会创建新的整数对象。
满足标识符命名规范的字符串都会被驻留,长度不限。
空字符串会驻留
使用乘法得到的字符串且满足标识符命名规范的字符串:长度小于等于20会驻留(peephole优化),Python 3.7改为4096(AST优化器)。
长度为1的特殊字符(ASCII 字符中的)会驻留
空元组或者只有一个元素且元素范围为`-5~256`的元组会驻留

在非交互环境中

所有数字对象都会驻留,即便是是`a = 1 -10`这样的表达式,也会驻留。
默认字符串都会驻留
使用乘法运算得到的字符串与在控制台相同
元组类型(元组内数据为不可变数据类型)会驻留,但是元组内数据为可变数据类型时,不会驻留。
函数、类、变量、参数等的名称以及关键字都会驻留
注意:字符串是在编译时进行驻留。
a = 'ABC'
b = 'ABC'
print(a is b) # True

数据一旦被创建,其内存地址不会改变。因为如果内存地址发生了改变,变量的指向就会异常。

a = [1,2,3]
print(id(a)) # 2060220698944

a.append(4)
print(a) # [1, 2, 3, 4]
print(id(a)) # 2060220698944

b = [1,2,3,4]
print(a == b) # True
print(id(a) == id(b)) # False

此时a的内存地址没有改变(一致性不变),但是a的值发生了改变(相等性改变)。

新创建的变量b的内存地址与a不同,但是ab的值相等。即ab相等但不一致。

tip

驻留机制和临时对象的内存重用有时候看起来一样,但实际不同。

内存重用是第一次创建时分配到了内存,而如果一个对象被创建后,没有被引用,那么它会被垃圾回收机制回收。

此时如果再次创建该对象,会重新分配内存地址。此时会重用第一次的内存地址。

print("\n=== 临时对象行为 ===")
print(f"hash(float('nan')): {hash(float('nan'))}") #202868087451
print(f"hash(float('nan')): {hash(float('nan'))}") #202868087451
print(f"id(float('nan')): {id(float('nan'))}") # 3245889399216
print(f"id(float('nan')): {id(float('nan'))}") # 3245889399216

这段代码你只需要添加两个变量,并将数据赋值给变量,然后打印变量的内存地址和哈希值,就会发现有了引用之后,内存地址和哈希值不同。

除了判断相等性、同一性,我们有时需要检查某个对象属于哪个类型或两个对象是否是同个类型,下面使用内置函数和运算符来判断。

del

del 关键字用于删除对象,通常用于立刻释放内存,代码调试等场景。

a = 1 # 创建一个整数对象
del a
print(a) # NameError: name 'a' is not defined

对象的判断

方法用途示例与说明
==判断两个对象的值是否相等对应对象内的 __eq__ 方法。即a == b等价于a.__eq__(b)。注意 True == 1False == 0 都是 True
is判断两个对象是否是同一个对象a is b 等价于 id(a) == id(b),比较的是对象的内存地址
type()判断对象的类型type(a) == type(b) 两个都是同个类的实例则相等。如 type(1) == int
isinstance()判断对象是否是某个类型isinstance(a, type),如果 type 是实例 a 的父类也会返回 True。如 isinstance(1, object) 是 True,因为 object 是所有类的父类
issubclass()判断类是否是某个类的子类issubclass(type, type),如果 type 是实例 a 的父类也会返回 True。如 issubclass(int, object) 是 True,因为 object 是所有类的父类

Python 官方编码规范 PEP 8 明确建议:使用 is 和 is not 来比较单例对象(如 None),而不是 ==。不会被 __eq__重写影响。

tip

type 有另外一个截然不同的用法,它是一个元类,用于创建类。

type(MyClass, (object,), {x: 1})
# 等价于
class MyClass(object):
x = 1

type 和 object 的关系

Python 中所有类都是 type 的实例,而 type 本身也是类:

  • object 是所有类的基类
  • typeobject 的子类,也是自身的元类
# type 和 object 的关系验证
print(issubclass(type, object)) # True
print(isinstance(object, type)) # True
print(isinstance(type, type)) # True

关键点:

  1. type 是创建类的类 (元类)
  2. object 是所有类的基类
  3. 类既是 type 的实例,又是 object 的子类

变量注解与类型提示

类型注解是Python 3.5+ 引入的特性,用于在代码中添加类型信息。

语法为 variable: type,可以当作一种文档注释,用于描述变量、函数参数和返回值的类型。同时和注释一样,不会影响运行时行为,即便输入的参数类型不匹配,也不会报错。

当然,类型注解也可以用于类、函数、方法、模块等。

# 基本类型注解
name: str = "Alice"
age: int = 30

# 函数参数和返回值注解
def greet(name: str, age: int = 25) -> str: # 带默认值的参数
return f"{name} is {age} years old"

greet("Alice", '30') # 即便输入的参数类型不匹配,也不会报错。
info

变量注解提供了一种在运行时可访问的标准方式来指定变量的类型。注解不会在运行时强制执行,但可以被类型检查器、IDE和文档工具使用。

PEP 526 – 变量注解

类型提示是Python静态类型检查的基础。它们帮助开发者在开发时发现潜在问题,提高代码质量,而不影响Python的动态特性。

PEP 484 – 类型提示

高级类型注解

Python 3.10+ 支持更简洁的语法:

# 联合类型,表示一个值可以是多种类型中的任意一种。
def process(data: str | int) -> None:
pass

# 可选类型,表示一个值可以是某种特定类型或者 None。
def find_user(id: int) -> str | None:
pass

如果你希望定义两个数据相加,数据可以都是字符串类型,也可以都是数字类型,但是唯独不能一个字符串类型,一个数字类型。

# 不能很好的表达这个需求
def add_str_or_int(a: int | str, b: int | str) -> int | str:
return a + b


# 泛型可以很好的表达这个需求(3.12 的语法)
def add_str_or_int[T:(int , str)](a: T, b: T) -> T:
return a + b

内置函数

help函数

help()是Python的内置函数,用于获取对象、模块、函数等的帮助文档和使用说明。它是Python自带的交互式帮助系统。

函数签名:help([object])

参数说明:

  • object:指定要获取帮助的对象,缺省默认为None,即进入交互式帮助系统
# 用法1,仅输入help(),会进入交互式帮助系统
help()

# 用法2,输入help(对象),会显示对象的文档字符串和相关信息
# 这个对象可以是模块、类、函数、方法、变量、常量等。会显示函数的docstring。
help(str)

input函数、print函数

Python 使用 input()函数来捕获用户的键盘输入,捕获的数据为字符串类型。

函数签名:input([prompt]) -> str

参数说明:

  • prompt:指定输入提示,缺省默认为None,即不显示提示

使用print()函数来输出(默认输出到终端控制台),输出内容也为字符串类型。

函数签名:print(*objects, sep=' ', end='\n', file=None, flush=False)

参数说明:

  • sep:指定分隔符,缺省默认为空格,常设置为逗号、分号、冒号、换行符等等
  • end:指定输出结尾符号,缺省默认为换行符\n
  • file:指定输出到文件,缺省默认为None,即输出到终端控制台
  • flush:指定是否立即刷新输出,缺省默认为False,即不立即刷新输出
# 终端会显示“请输入你的名字:”,你只需要输入任意内容后回车,name变量会接收输入的内容。
# 假设输入“Allen”,则name变量会接收“Allen”。
name = input("请输入你的名字:")
# input内也可以不传参数,name = input()
# 除了终端没有显示外,效果相同。

print(type(name)) # <class 'str'>
print(f"你好,{name}!")
# 输出 :你好,Allen!
# print不会显示引号
# 等价于
print("你好," + name + "!")
# 等价于
print("你好,",name,"!")
# 打印任何不同的类型,打印内容会自动调用__str__方法转换为字符串。

# 多个参数输出的结果默认以空格分隔
print(1,'1',True,None)
# 输出: 1 1 True None

# 指定分隔符,缺省默认为空格,常设置为逗号、分号、冒号、换行符等等
print(1,2,3,4,5,sep=",")
# 输出: 1,2,3,4,5

# 指定输出结尾符号,缺省默认为换行符\n
print("Hello, World!")
print("Hello, World!",end="\n")
# 输出
# Hello, World!
# Hello, World!
#
print("Hello, World!", end="")
print("Hello, World!", end="———— by Allen")
# 输出
# Hello, World!Hello, World!———— by Allen

# 输出到文件
f = open("test.txt", "w") # 打开文件
print('Hello, World!', file=f) # 输出到文件
f.close() # 关闭文件
# 运行后,我们可以在当前目录下看到一个`test.txt`文件,内容为`Hello, World!`。

# 立即刷新输出
# 缓冲区是计算机内存中的一块临时存储区域。在 Python 中,print() 函数默认会将文本发送到这个缓冲区,而不是直接输出到屏幕。
# 这样做是为了提高效率,因为直接操作 I/O 设备(如屏幕或硬盘)比操作内存要慢得多。
# Python 会等到缓冲区满了,或者遇到换行符 \n 时,才将缓冲区里的所有内容一次性发送出去。
# print末尾默认会自动添加换行符\n,一旦我们指定不同的end参数,往往会结合flush参数使用。
# 为了便于观察,我们使用time.sleep()函数来让程序暂停1秒。
import time
for i in range(10):
print(i,end="")
time.sleep(1)
# 一次性输出 :0123456789
import time
for i in range(10):
print(i,end="",flush=True)
time.sleep(1)
# 陆续输出 :0123456789

id函数、hash函数

id函数用于获取对象的内存地址。id函数返回的值是对象的内存地址的整数表示,用于快速查找和比较对象。

函数签名:id(object) -> int

hash函数用于获取对象的哈希值。哈希值是对象的唯一标识,只有不可变对象才有哈希值,同id函数一样用于快速查找和比较对象。

函数签名:hash(object) -> int

a = (1,2,3)
print(id(a))
print(hash(a))

相关库推荐

  • typing: 对类型提示的支持
  • pydantic: 第三方数据验证模块
  • pydoc: 文档生成器和在线帮助系统
  • doctest: 测试交互式Python示例