python代码如何写的优雅?
简介
在实际项目中,我们可能一开始为了完成功能而忽视了代码的整体质量,因此,使用一些高阶的函数或方法,能够更加使我们的代码更加优雅。废话不多说,现在马上开始。
使用enumerate方法替代range(len)
enumerate()中也包含了下标和值,可以很方便的进行索引和值的遍历。
data = range(10000)
start = time.time()
data_len = len(data)
for i in range(data_len):
print(data[i])
print(time.time() - start)
start = time.time()
for index, item in enumerate(data):
print(index, item)
print(time.time() - start)
列表生成式的使用
对于一些有规律的列表需要生成时,建议使用列表生成式进行生成,方便快捷,对于一些属性稍微复杂点的可以使用map进行批量操作。例如生成[1,4,9,16]
print([item * item for item in range(1, 5)])
使用sorted对复杂对象进行排序
在对一些复杂对象进行排序时候建议使用sorted进行排序,例如字典的key或者字典的value等
sorted(Iterable, key=Callable)
假设有如下列表中嵌套字典,依据age进行排序
data = [
{"name": "Alex", "age": 18},
{"name": "Band", "age": 21},
{"name": "Coco", "age": 17},
]
print(sorted(data, key=lambda n: n["age"]))
[{'name': 'Coco', 'age': 17}, {'name': 'Alex', 'age': 18}, {'name': 'Band', 'age': 21}]
假设有如下字典,需要将字典按照value值进行排序并返回一个字典
data = {"a": 19, "c": 10, "b": 20}
print(dict(sorted(data.items(), key=lambda data: data[1])))
{'c': 10, 'a': 19, 'b': 20}
使用set存放唯一值\去重
set集合是无序的,值唯一,可以进行交、并、差等运算操作,除此之外,一般去重也可以使用set进行去重。
data = set({1, 2, 3, 1})
print(data)
使用生成器节省内存消耗
一般我们称一个函数或者方法中使用了yield方法进行返回的,则成此方法或者函数为生成器,除了这种方法之外,可以使用类似于列表生成式的方式进行操作,只需要将列表生成式的[]
换成()
即可,示例如下:
l_data = [i for i in range(100)]
print(l_data)
print(sys.getsizeof(l_data))
g_data = (i for i in range(100))
print(g_data)
print(sys.getsizeof(g_data))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
920
<generator object <genexpr> at 0x0000028E1A626570>
104
使用sys
模块的getsizeof可以获取变量的内存占用。
generator就是生成器,它不会一次性加载全部数据到内存中,只会再for循环调用或者next()\send()方法时进行懒加载方式的输出,极大的减少了内存消耗,尤其是对于数据量较大的情况。
对于字典中值的获取时建议使用get(key, default)并设置默认值
在对于字典的value值的获取中,目前有两种方式,一种是使用索引进行获取,另一种则使用get方法去获取,如下:
data = {"a": 19, "c": 10, "b": 20}
print(data["a"])
print(data.get("a", None))
print(data.get("v", None))
print(data["v"])
19
19
None
Traceback (most recent call last):
File "c:\Users\ts\Desktop\2022.7\2022.7.20\test.py", line 76, in <module>
print(data["v"])
KeyError: 'v'
由上述可以看出,在使用索引进行对字典value值的获取时,key存在还好,不存在则会直接报错,而采用get并设置默认值的,则会返回默认值,不会抛出异常。
统计序列中各元素个数时建议使用collection中的Counter
from collections import Counter
s_data = "asdfqwrewasfasdvfare"
print(s_data.count("a"))
print(Counter(s_data))
l_data = [1, 3, 2, "a", "b", 1, "c", "a", "c"]
print(l_data.count("a"))
print(Counter(l_data))
4
Counter({'a': 4, 's': 3, 'f': 3, 'd': 2, 'w': 2, 'r': 2, 'e': 2, 'q': 1, 'v': 1})
2
Counter({1: 2, 'a': 2, 'c': 2, 3: 1, 2: 1, 'b': 1})
由上述可以看见,大部分序列都有一个count方法来获取单个元素的个数,但Counter会统计所有的个数,类似于对于每个元素进行了遍历。
格式化字符串值使用f{string}(适用于Python 3.6+)
print("key:{}".format("aaa"))
key = "aaa"
print(f"key:{key}")
print("key:%s" % (key))
key:aaa
key:aaa
key:aaa
不过在实际使用中会发现,在部分常见下f
会引起异常,可使用%s
替代。
拼接字符串使用join
适用于多个元素存放在某一个序列中,纯字符串建议使用如下方法。
a = "hello"
b = "world"
c = "!"
print(a + b + c)
print("{} {} {}".format(a, b, c))
print(f"{a} {b} {c}")
print("%s %s %s" % (a, b, c))
l_data = ["hello", "world", "!"]
print(" ".join(l_data))
使用双星号语法合并字典(适用于Python 3.5+)
字典之间的合并可以使用自带的update方法进行合并,注意操作的是原字典;除此之外,也可以使用双星号进行字典的合并。
d1 = {"a": 1, "b": 2}
d2 = {"c": 1, "d": 2}
d1.update(d2)
print(d1)
print({**d1, **d2})
使用切片进行字符串的切割
切片一般使用[start:end:step]
- start:开始的索引位置,包含
- end:结束的索引位置,不包含
- step:步长
需要注意切片也可以从后向前,从前向后的从0开始,从后向前的从-1开始
data = "asdsdfaf"
# 选择所有元素
print(data[::])
# 选择从开始到索引为4的元素(不包含4),步长为2的元素
print(data[:4:2])
# 选择从索引为2的位置到索引为7的位置,步长为2的所有元素
print(data[2:7:1])
# 选择最后一个元素到从后向前第6个元素,步长为1的所有元素
print(data[-6:-1:1])
asdsdfaf
ad
dsdfa
dsdfa
字典推导式
与列表生成式类似,但是不常使用
print({item: item + 1 for item in range(10)})
{0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}
字典setdefault与get区别
get
和setdefault
都是获取字典的元素,唯一不同在于get
获取不到元素后不会做任何操作,setdefault
则会将不存在的元素添加至字典中。
data = {"a": 1, "b": 2}
print(data.get("c", 12))
print(data)
print(data.setdefault("c", 12))
print(data)
当需要切片较多时,建议使用slice进行切片的定义,可提高代码可读性
例如存在如下的数据,不同的位置代表不同的含义
data = "430900198306141777"
province = slice(0, 6)
year = slice(6, 10)
month = slice(10, 12)
date = slice(12, 14)
print(data[province])
print(data[year])
print(data[month])
print(data[date])
430900
1983
06
14
由结果可知,slice可以对切片的位置进行定义,方便后续直接使用。
slice适用于大部分的切片位置场景,尤其是切片较多的情况,也极大的提高了代码的可读性。
当代码中需要检查一个元素是否出现在某一个序列中,使用集合更为合适
如题,在检查序列中是否存在一个元素时,我们可以使用列表的count去检查,推荐使用集合(set),set对检查元素做过优化。
当需要频繁的进行先进先出的操作,deque双端队列的速度会更快
如题,collection的deque既可以从左边进行操作,也可以从右边进行操作。
当只需要一个只包含数字的列表时,使用数组比列表更高效。
array.array()创建数组,支持列表的插入、删除、扩展等功能,还支持文件的快速读写。需要注意的是数组默认不支持向列表sort一样的方法进行排序,但是我们可以使用sorted方法进行排序,如下:
sorted(array_data)
当需要在插入新元素的同时保证有序序列的顺序(稳定性)使用bisect.insort。
如题,除此之外,当需要快速查找元素时可以使用bisect.bisect
.
对于字典需要处理找不到的键并且我们需要对相应的键进行操作时或者需要对序列中的相同键进行归并时
我们可以使用如下方法:
- 使用setdefault方法
默认情况下,setdefault方法,有两个参数,一个是需要查找的键,一个是当键不存在时,需要设置的值,setdefault方法相当于既有get,也有set,获取值时示例如下;
dict_data.setdefault(key, default_value)
当需要对序列中的元素进行去重归并时
list_data = [("apple",10), ("orange", 2), ("apple", 2), ("banana", 3), ("orange", 10)]
for key, value in list_data:
dict_data2.setdefault(key, []).append(value)
- 使用collection.defaultdict方法
直接进行序列中相应元素的归并时
from collections import defaultdict
list_data = [("apple",10), ("orange", 2), ("apple", 2), ("banana", 3), ("orange", 10)]
dict_data2 = defaultdict(list)
for key, value in list_data:
dict_data2[key].append(value)
当我们需要字典不可变时
使用types
中的MappingProxyType
当存储较大数量的数据记录时,建议不要使用dict,建议直接使用元组或者命名元组的列表
读写文件时必须指定encoding编码格式
关于文件读写,大家都知道使用open函数,需要切记最好在文本操作时设置encoding编码,因为不同的电脑可能编码存在的不同,当在不同编码的电脑中执行时将会发生意想不到的错误。
关于python高阶函数的使用,在python3中可以使用推导式代替
python有几个比较常用的高阶函数,map、filter、reduce、sorted,除了排序之外都可以使用推导式去代替,并且推导式清晰可读。
当我们需要获取函数的参数、返回值等信息时(也称为自省),我们可以使用inspect模块进行参数自省
主要使用到inspect的signature方法,返回的是Signature对象,对象的parameters是函数的参数,return_annotation是函数的返回值。
@classmethod(类方法)是方法、@staticmethod(静态方法)不是方法,是函数
当使用递归时,可以使用functools.lru_cache装饰器加快速度
由于递归时会出现重复计算的情况,使用lru_cache装饰器可以加快运行速度,需要注意的是递归函数返回的值需要是可散列的。
lru_cache(maxsize=128, typed=false)还有两个参数,lru_cache使用类似缓存的方式,缓存了相同参数的操作结果,因此在递归时会提升运行速度,maxsize是控制保存的操作结果的最大数量,typed是控制是否区分操作结果的类型,类似1.0与1是否是相同的。
当我们需要对不同类型的数据分别进行处理时,可以使用@singledispatch装饰器进行处理,类似于注册器的方式
singledispatch
装饰器可以创建一个单分派泛函数,个人理解就是处理同一类型事件的多个函数,singledispatch可以创建一个主函数,然后再由singledispatch.register分别注册相应的子函数,示例如下:
from functools import singledispatch
class Person(object):
def __init__(self, name, age) -> None:
self.name = name
self.age = age
@singledispatch
def run(val):
return val
@run.register(int)
def _(val):
return val * val
@run.register(str)
def _(val):
return f"{val}-{val}"
@run.register(Person)
def _(val: Person):
return val.name + "-" + val.age
# 查找对应类型的处理函数
print(run.dispatch(int))
# 查找所有类型的处理函数
print(run.registry.items())
print(run(2))
print(run("ts"))
p = Person("mike", "12")
print(run(p))
singledispatch的优点是支持模块化的扩展,各个模块可以为它支持的类型注册一个专门的函数,其实这里的模块泛指函数
python默认/
除法返回浮点型数据,当需要整除结果时,可以使用//
。
实现装饰器模式不建议使用函数装饰器,建议使用类表示装饰器。
在函数或方法中,不要使用可变类型作为参数的默认值
当确实需要传递可变类型时,可以使用下面的方法,设置默认参数为None,由是否为None来进行初始化可变对象,防止出现可变对象出现叠加的情况:
def run(number: int = 1, para=None):
if para is None:
para = []
else:
para.append(number)
print(f"para:{para}")
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现