Python进阶操作笔记
从注册表添加开机自启动
添加自启动
import winreg
import os
def add_to_startup(name, exe_path=None):
if exe_path is None:
exe_path = os.path.abspath(__file__) # 当前脚本路径
key = r"Software\Microsoft\Windows\CurrentVersion\Run"
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key, 0, winreg.KEY_SET_VALUE)
winreg.SetValueEx(reg_key, name, 0, winreg.REG_SZ, exe_path)
winreg.CloseKey(reg_key)
print(f"已设置 {name} 为开机自启:{exe_path}")
# 使用示例
add_to_startup("MyApp", r"C:\Path\To\your_program.exe")
移除自启动
def remove_from_startup(name):
key = r"Software\Microsoft\Windows\CurrentVersion\Run"
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key, 0, winreg.KEY_ALL_ACCESS)
try:
winreg.DeleteValue(reg_key, name)
print(f"已移除 {name} 的开机自启设置")
except FileNotFoundError:
print("该启动项不存在")
winreg.CloseKey(reg_key)
解析:
reg_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, key, 0, winreg.KEY_SET_VALUE)
打开注册表中的这个路径:
- HKEY_CURRENT_USER 表示当前登录的用户(非所有用户)。
- key 是子路径。
- 0 是保留参数(一般填 0)。
- winreg.KEY_SET_VALUE 表示打开后具有“写入值”的权限。
winreg.SetValueEx(reg_key, name, 0, winreg.REG_SZ, exe_path)
在打开的注册表路径下添加一个名为 name 的键值项。
- reg_key:上面打开的注册表对象
- name:启动项名称。
- 0:保留参数。
- winreg.REG_SZ:值类型为字符串。
- exe_path:程序路径(字符串)。
winreg.CloseKey(reg_key)
使用完注册表对象后关闭句柄,释放资源。
程序崩坏日志
使用方法
可以将以下代码加在程序入口文件的最前面。当程序出现未知异常而崩溃时,就会记录下来并生成日志文件
import sys
import logging
logging.basicConfig(filename="crash.log", level=logging.ERROR)
def handle_exception(exc_type, exc_value, exc_traceback):
logging.error("程序崩溃", exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = handle_exception #sys.excepthook可以捕获所有未被捕获的异常
以下情况不会被sys.excepthook捕获:
- 已经被你自己的
try/except捕获 - 线程里抛异常(默认不走 sys.excepthook),需要单独处理
import threading
import logging
def worker():
raise RuntimeError("线程崩了")
def thread_wrapper(func):
try:
func()
except Exception:
logging.exception("线程异常")
t = threading.Thread(target=thread_wrapper, args=(worker,))
t.start()
t.join()
生成一副纸牌
ranks = [str(n) for n in range(2,11)] + list('JQKA')
魔术方法
表 1-1:跟运算符无关的特殊方法
| 类别 | 方法名 |
|---|---|
| 字符串 / 字节序列表示形式 | __repr__、__str__、__format__、__bytes__ |
| 数值转换 | __abs__、__bool__、__complex__、__int__、__float__、__hash__、__index__ |
| 集合模拟 | __len__、__getitem__、__setitem__、__delitem__、__contains__ |
| 迭代枚举 | __iter__、__reversed__、__next__ |
| 可调用模拟 | __call__ |
| 上下文管理 | __enter__、__exit__ |
| 实例创建和销毁 | __new__、__init__、__del__ |
| 属性管理 | __getattr__、__getattribute__、__setattr__、__delattr__、__dir__ |
| 属性描述符 | __get__、__set__、__delete__ |
| 跟类相关的服务 | __prepare__、__instancecheck__、__subclasscheck__ |
表 1-2:跟运算符相关的特殊方法
| 类别 | 方法名 | 对应运算符 / 函数 |
|---|---|---|
| 一元运算符 | __neg__ |
- |
__pos__ |
+ |
|
__abs__ |
abs() |
|
| 比较运算符 | __lt__ |
< |
__le__ |
<= |
|
__eq__ |
== |
|
__ne__ |
!= |
|
__gt__ |
> |
|
__ge__ |
>= |
|
| 算术运算符 | __add__ |
+ |
__sub__ |
- |
|
__mul__ |
* |
|
__truediv__ |
/ |
|
__floordiv__ |
// |
|
__mod__ |
% |
|
__divmod__ |
divmod() |
|
__pow__ |
** / pow() |
|
__round__ |
round() |
|
| 反向算术运算符 | __radd__、__rsub__、__rmul__、__rtruediv__、__rfloordiv__、__rmod__、__rdivmod__、__rpow__ |
右操作数运算 |
| 增量赋值算术运算符 | __iadd__、__isub__、__imul__、__itruediv__、__ifloordiv__、__imod__、__ipow__ |
+= 等 |
| 位运算符 | __invert__ |
~ |
__lshift__ |
<< |
|
__rshift__ |
>> |
|
__and__ |
& |
|
__or__ |
| |
|
__xor__ |
^ |
|
| 反向位运算符 | __rlshift__、__rrshift__、__rand__、__rxor__、__ror__ |
右操作数位运算 |
| 增量赋值位运算符 | __ilshift__、__irshift__、__iand__、__ixor__、__ior__ |
<<= 等 |
列表推导式
1.与普通方法的对比
普通方法
nums = [1, 2, 3, 4, 5, 6]
result = []
for n in nums:
if n % 2 == 0:
result.append(n * n)
print(result)
列表推导式
nums = [1, 2, 3, 4, 5, 6]
result = [n * n for n in nums if n % 2 == 0]
print(result)
2.含有if...else...的列表推导式
示例需求:把列表中的数字转换为字符串:
- 偶数 →
"even" - 奇数 →
"odd"
普通写法:
nums = [1, 2, 3, 4, 5]
result = []
for n in nums:
if n % 2 == 0:
result.append("even")
else:
result.append("odd")
print(result)
列表推导式写法
nums = [1, 2, 3, 4, 5]
result = ["even" if n % 2 == 0 else "odd" for n in nums]
print(result)
if...else...结构
[结果A if 条件 else 结果B for 元素 in 可迭代对象]
if ... else ...不能 放在for后面for ... if ...只支持过滤,不支持二选一
3.if过滤与if...else...对比
- 过滤用的
if
[n for n in nums if n % 2 == 0]
不满足条件的元素被丢弃
2. 表达式用的if...else...
["even" if n % 2 == 0 else "odd" for n in nums]
每个元素都会生成一个结果
4.嵌套列表推导式
生成 3×3 的坐标点 (x, y)
普通方法:
result = []
for x in range(3):
for y in range(3):
result.append((x, y))
print(result)
输出:
[(0, 0), (0, 1), (0, 2),
(1, 0), (1, 1), (1, 2),
(2, 0), (2, 1), (2, 2)]
嵌套列表推导式
result = [(x, y) for x in range(3) for y in range(3)]
执行顺序:
for x in range(3):
for y in range(3):
(x, y)
生成器表达式
1.与列表推导式的对比
# 列表推导式
lst = [x * x for x in range(5)]
# 生成器表达式
gen = (x * x for x in range(5))
特点:
- 不一次性生成所有数据
- 按需生成,用一个算一个
2.运算逻辑
gen = (x * x for x in range(3))
此时:
- 什么都没算
- 内存里只保存了:
- 当前状态
- 迭代规则
当开始使用时
next(gen) # 0
next(gen) # 1
next(gen) # 4
next(gen) # StopIteration
注:生成器只能消费一次
元组
元组其实是对数据的记录:元组中的每个元素都存放了记录中一个字段的数据,外加这个
字段的位置。正是这个位置信息给数据赋予了意义。
如果只把元组理解为不可变的列表,那其他信息——它所含有的元素的总数和它们的位
置——似乎就变得可有可无。但是如果把元组当作一些字段的集合,那么数量和位置信息
就变得非常重要了。
注:尽量不要把可变对象放入元组中
重要作用1:用元组来存储信息
示例 2-7 中的元组就被当作记录加以利用。如果在任何的表达式里我们在元组内对元素排
序,这些元素所携带的信息就会丢失,因为这些信息是跟它们的位置有关的。
>>> lax_coordinates = (33.9425, -118.408056) ➊
>>> city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014) ➋
>>> traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ➌
... ('ESP', 'XDA205856')]
>>> for passport in sorted(traveler_ids): ➍
... print('%s/%s' % passport) ➎
...
BRA/CE342567
ESP/XDA205856
USA/31195855
>>> for country, _ in traveler_ids: ➏
... print(country)
...
USA
BRA
ESP
重要作用2:把元组当做不可变的列表
元组与列表的对比
| 方法 / 属性 | list列表 | tuple元组 | 说明 |
|---|---|---|---|
s.__add__(s2) |
✅ | ✅ | s + s2,拼接 |
s.__iadd__(s2) |
✅ | ❌ | s += s2,就地拼接 |
s.append(e) |
✅ | ❌ | 在尾部添加元素 |
s.clear() |
✅ | ❌ | 删除所有元素 |
s.__contains__(e) |
✅ | ✅ | e in s |
s.copy() |
✅ | ❌ | 浅拷贝 |
s.count(e) |
✅ | ✅ | 统计元素出现次数 |
s.__delitem__(p) |
✅ | ❌ | 删除指定位置元素 |
s.extend(it) |
✅ | ❌ | 追加可迭代对象 |
s.__getitem__(p) |
✅ | ✅ | s[p] 取值 |
s.__getnewargs__() |
❌ | ✅ | pickle 优化相关 |
s.index(e) |
✅ | ✅ | 查找元素位置 |
s.insert(p, e) |
✅ | ❌ | 插入元素 |
s.__iter__() |
✅ | ✅ | 获取迭代器 |
s.__len__() |
✅ | ✅ | len(s) |
s.__mul__(n) |
✅ | ✅ | s * n 重复拼接 |
s.__imul__(n) |
✅ | ❌ | s *= n 就地重复 |
s.__rmul__(n) |
✅ | ✅ | n * s |
s.pop([p]) |
✅ | ❌ | 删除并返回元素 |
s.remove(e) |
✅ | ❌ | 删除首次出现的元素 |
s.reverse() |
✅ | ❌ | 就地反转 |
s.__reversed__() |
✅ | ❌* | 返回倒序迭代器 |
s.__setitem__(p, e) |
✅ | ❌ | 修改元素 |
s.sort(key, reverse) |
✅ | ❌ | 就地排序 |
元组支持几乎所有“只读”的列表操作,不支持任何“修改自身”的操作。
占位符的用法
在平行赋值中,* 前缀只能用在一个变量名前面,但是这个变量可以出现在赋值表达式的任意位置:
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)
#或如上例中:
>>> for country, _ in traveler_ids: ➏
... print(country)
...
USA
BRA
ESP
注:占用一个位置时,可以用
_充当占位符。占用两个及以上位置时,可以写多个_或写*_, filename = some_func()来占据多个位置
元组拆包
1.基本操作
注:元组拆包可以应用到任何可迭代对象上,唯一的硬性要求是,被可迭代对象中的元素数量必须要跟接受这些元素的元组的空档数一致。除非我们用
*来表示忽略多余的元素
>>> lax_coordinates = (33.9425, -118.408056)
>>> latitude, longitude = lax_coordinates # 元组拆包
>>> latitude
33.9425
>>> longitude
-118.408056
2.进阶操作:不使用中间变量交换两个变量的值
>>> b, a = a, b
这也是元组拆包在起作用
# 第一步:先把右边的值全部算出来
temp = (a, b)
# 第二步:再把结果依次解包赋值
b = temp[0]
a = temp[1]
所以真正发生的其实是
(a, b) # 先被打包成一个元组
然后:
b, a = (a, b)
3.进阶操作:用 * 运算符把一个可迭代对象拆开作为函数的参数
>>> divmod(20, 8)
(2, 4)
>>> t = (20, 8)
>>> divmod(*t)
(2, 4)
>>> quotient, remainder = divmod(*t)
>>> quotient, remainder
(2, 4)
定义和使用具名元组
>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates') ➊
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667)) ➋
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722,
139.691667))
>>> tokyo.population ➌
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'
- 创建一个具名元组需要两个参数,一个是类名,另一个是类的各个字段的名字。后者可以是由数个字符串组成的可迭代对象,或者是由空格分隔开的字段名组成的字符串。
- 存放在对应字段里的数据要以一串参数的形式传入到构造函数中(注意,元组的构造函数却只接受单一的可迭代对象)。
- 你可以通过字段名或者位置来获取一个字段的信息。
切片
列表切片的Pythonic
-
当起止位置信息都可见时,我们可以快速计算出切片和区间的长度,用后一个数减去第一个下标(stop - start)即可。
-
这样做也让我们可以利用任意一个下标来把序列分割成不重叠的两部分,只要写成 my_list[:x] 和 my_list[x:] 就可以了,如下所示。
注:从被选中元素的左边开始切割列表
>>> l = [10, 20, 30, 40, 50, 60]
>>> l[:2] # 在下标2的地方分割
[10, 20]
>>> l[2:]
[30, 40, 50, 60]
>>> l[:3] # 在下标3的地方分割
[10, 20, 30]
>>> l[3:]
[40, 50, 60]
给切片赋值
>>> l = list(range(10))
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[2:5] = [20, 30]
>>> l
[0, 1, 20, 30, 5, 6, 7, 8, 9]
>>> del l[5:7]
>>> l
[0, 1, 20, 30, 5, 8, 9]
>>> l[3::2] = [11, 22]
>>> l
[0, 1, 20, 11, 5, 22, 9]
>>> l[2:5] = 100 ➊
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: can only assign an iterable
>>> l[2:5] = [100]
>>> l
[0, 1, 100, 22, 9]
注:如果赋值的对象是一个切片,那么赋值语句的右侧必须是个可迭代对象。即便只有单独一个值,也要把它转换成可迭代的序列。
对列表使用+或*
通常 + 号两侧的序列由相同类型的数据所构成,在拼接的过程中,两个被操作的序列都不会被修改,Python 会新建一个包含同样类数据的序列来作为拼接的结果。
>>> l = [1, 2, 3]
>>> l * 5
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> 5 * 'abcd'
'abcdabcdabcdabcdabcd'
注:如果在 a * n 这个语句中,序列 a 里的元素是对其他可变对象的引用的话,你就需要格外注意了,因为这个式子的结果可能会出乎意料。比如,你想用my_list = [[]] * 3 来初始化一个由列表组成的列表,但是你得到的列表里包含的 3 个元素其实是 3 个引用,而且这 3 个引用指向的都是同一个列表。这可能不是你想要的效果。
导入父文件夹的py文件
import os
import sys
# 获取上一层目录
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
# 添加到Python搜索路径
sys.path.insert(0, parent_dir)
import 父文件夹文件

浙公网安备 33010602011771号