Python应知小技巧:如何用更少的代码做更多的事情

Python代码优化小技巧

  • • 短路运算(Short-circuit operation)

  • • 切片(Slice)

  • • 列表推导式(List Comprehension)

  • • 生成器表达式(Generator Expression)

  • • 枚举(Enumerate)

  • • 三元运算符(Ternary Operator)

  • • 字典(Dict)

  • • 装饰器(Decorator)

  • • 上下文管理器(Context Manager)

  • lambda表达式(Lambda Expression)

  • map函数(Map Function)

  • filter函数(Filter Function)

  • @property 装饰器(Property Decorator)

  • __slots__ 属性(Slots Attribute)

Python 是 一种用着很爽的语言。Python也有着很多特性和技巧,可以帮助我们编写更高效、更优雅、更易维护的代码。下面勇哥将介绍一些我常用的Python代码优化的简单小技巧,少写很多代码,包括:

短路运算(Short-circuit operation)

咱们都知道,短路运算也就是 ”与 ,或 ,非“ 这几个组成。但是有些时候,我们很多 if-else语句其实可以使用这种短路运算来简写代码的

例, 获取用户信息,不存在的用户则返回匿名用户, 代码:

def get_user(user):
    # 常规代码
    if user:
        return user
    return "匿名用户"
    
# 短路来简写代码

def get_user(user):
    return user or "匿名用户"

使用短路运算处理,那么就只需要一行代码即可,这样写也挺易读的。

切片替代循环(Slice)

使用切片代替循环或递归来操作序列。切片是一种用于从一个序列(如字符串、列表、元组等)中获取一部分或全部元素的语法。

例,反转数据,代码:

# 使用循环
def reverse(lst):
    new_lst = []
    for i in range(len(lst) - 1-1-1):
        new_lst.append(lst[i])
    return new_lst

lst = [1, 2, 3, 4, 5]
print(reverse(lst)) # [5, 4, 3, 2, 1]

# 使用切片
def reverse(lst):
    return lst[::-1]

lst = [1, 2, 3, 4, 5]
print(reverse(lst)) # [5, 4, 3, 2, 1]

切片的操作比循环或递归更简单并且高效,因为切片是基于内置函数实现的,而循环或递归是基于自定义函数实现。

列表推导式(List Comprehension)

列表推导式是一种用于从一个可迭代对象(如列表、元组、字典、集合等)创建一个新的列表的简洁的语法。

例,从一个列表中筛选出所有的偶数,并将它们乘以2,代码:

# 使用普通的循环
lst = [1, 2, 3, 4, 5, 6]
new_lst = []
for x in lst:
    if x % 2 == 0:
        new_lst.append(x * 2)
print(new_lst) # [4, 8, 12]
# 使用列表推导式
lst = [1, 2, 3, 4, 5, 6]
new_lst = [x * 2 for x in lst if x % 2 == 0]
print(new_lst) # [4, 8, 12]

一行代码实现循环、条件判断和赋值等操作,提高了代码的可读性和效率,而且运行速度也更快(可以思考一下为什么更快)。

生成器表达式(Generator Expression)

生成器表达式是一种类似于列表推导式的语法,但是它不会一次性生成一个完整的列表,而是返回一个生成器对象,可以按需逐个产生元素。

例,计算一个列表中所有偶数的平方和,代码:

# 使用普通的循环
lst = [1, 2, 3, 4, 5, 6]
sum = 0
for x in lst:
    if x % 2 == 0:
        sum += x ** 2
print(sum) # 56
# 使用生成器表达式
lst = [1, 2, 3, 4, 5, 6]
sum = sum(x ** 2 for x in lst if x % 2 == 0)
print(sum) # 56

这个生成器表达式可以节省内存空间,提高性能,适合处理大量或无限的数据,而且不会占用额外的内存空间,特别适用于读取大批量的数据。当然我们也可以用yeild也能做一个生成器,这个太东西很牛逼。

枚举(Enumerate)

枚举是一种用于同时获取可迭代对象中的元素和索引的函数。枚举可以避免使用额外的变量来记录索引,提高了代码的可读性和效率。

例,打印一个列表中每个元素及其对应的索引,代码:

# 使用普通的循环
lst = ["a", "b", "c", "d", "e"]
index = 0
for x in lst:
    print(index, x)
    index += 1
# 输出:
# 0 a
# 1 b
# 2 c
# 3 d
# 4 e
# 使用枚举
lst = ["a", "b", "c", "d", "e"]
for index, x in enumerate(lst):
    print(index, x)
# 输出:
# 0 a
# 1 b
# 2 c
# 3 d
# 4 e

使用枚举的代码更加简洁和清晰,而且不需要手动更新索引。

三元运算符(Ternary Operator)

三元运算符是一种用于根据一个条件表达式来选择两个不同的值的简洁的语法。

例,根据一个数字的正负来赋值一个字符串,代码:

# 使用普通的if-else语句
num = -5
if num > 0:
    sign = "positive"
else:
    sign = "negative"
print(sign) # negative
# 使用三元运算符
num = -5
sign = "positive" if num > 0 else "negative"
print(sign) # negative

三元运算符可以用一行代码实现简单的条件判断和赋值,提高了代码的可读性和效率,而且不需要多余的变量和语句。

字典处理条件判断

遇到if循环语句很长的时候,其实可以使用字典来替代,两者的执行效率没有试验过,感觉差不了多少。

例, 使用字典来判断返回值, 代码:

# 使用多个if-elif-else语句
def foo(x):
    if x == "a":
        return 1
    elif x == "b":
        return 2
    elif x == "c":
        return 3
    else:
        return -1

print(foo("a")) # 1
print(foo("d")) # -1

# 使用字典
def foo(x):
    dic = {"a": 1, "b": 2, "c": 3}
    return dic.get(x, -1)

print(foo("a")) # 1
print(foo("d")) # -1

合理利用字典的get方法,可以减少很多代码的使用。

装饰器(Decorator)

装饰器是一种用于在不修改原函数定义和调用的情况下,给函数添加额外的功能或修改其行为的语法。

例,给一个函数添加一个计时的功能,记录其运行时间,代码:

# 使用普通的函数调用
import time

def foo():
    # do something
    time.sleep(1)

start = time.time()
foo()
end = time.time()
print(f"foo() took {end - start} seconds to run.") 
# 使用装饰器
import time

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__}() took {end - start} seconds to run.")
        return result
    return wrapper

@timer # 相当于 foo = timer(foo)
def foo():
    # do something
    time.sleep(1)

foo() 

装饰器能做的事太多了,比如flask 框架,真尼玛装饰器用到飞天。勇哥也就一般用于实现一些通用的功能,如日志、缓存、计时、权限检查等,让代码可复用更强一写。

上下文管理器(Context Manager)

上下文管理器是一种用于在执行某些操作之前和之后自动执行一些预设的操作的语法。上下文管理器可以用于实现一些资源管理的功能,

例如,打开一个文件,读取其内容,并在完成后自动关闭文件,代码:

# 使用普通的try-finally语句
file = open("test.txt", "r")
try:
    content = file.read()
    print(content)
finally:
    file.close()
# 使用上下文管理器
with open("test.txt", "r") as file:
    content = file.read()
    print(content)

使用上下文管理器我们一般用于 打开和关闭文件、获取和释放锁、连接和断开数据库等。代码的安全性问题和可读性也很好处理。

Lambda 表达式(Lambda Expression)

lambda表达式(代替简单的函数定义来创建匿名函数。lambda表达式是一种用于定义一个只有一行代码的函数的简洁的语法。

例,处理排序sorted,指定字段,代码:

lst = [9,2,3,4,5,5,1,2,3]
# 不使用lambda表达式
def add(item):
   return item

print(sorted(add, item))

# 使用lambda表达式
print(sorted(lst, key=lambda x:x))

lambda表达式我们一般用来实现一些简单的功能,如排序、过滤、映射等。使用匿名函数,代码有时候少写一些外,还有一点就是 lambda 表达式用完就会在内存中舍弃,也挺好。

map 函数

map函数代替循环来对可迭代对象中的每个元素应用一个函数。map函数是一种用于将一个函数作用于一个可迭代对象中的每个元素,并返回一个新的可迭代对象的函数。

例,将一批字符列表字符串转数字列表,代码:

# 不使用map函数
lst = ["1", "2", "3", "4", "5"]
new_lst = []
for x in lst:
    new_lst.append(int(x))
print(new_lst) # [1, 2, 3, 4, 5]

# 使用map函数
lst = ["1", "2", "3", "4", "5"]
new_lst = list(map(int, lst))
print(new_lst) # [1, 2, 3, 4, 5]

是不是看起来代码又少了很多,而且这种内置函数,一般速度都比咱们自己写的代码运行效率高,基于这个强大的高阶函数,我们可以用来实现一些批量处理的功能,如类型转换、格式化、计算,数据合并等。

filter 函数

filter函数代替循环来从可迭代对象中筛选出满足一个条件的元素。filter函数是一种用于将一个条件函数作用于一个可迭代对象中的每个元素,并返回一个只包含满足条件元素的新的可迭代对象的函数。

例,从一个列表中挑选符合要求的数据出来组成另一个列表,代码:

# 不使用filter函数
lst = [1, 2, 3, 4, 5]
new_lst = []
for x in lst:
    if x % 2 == 0:
        new_lst.append(x)
print(new_lst) # [2, 4]

# 使用filter函数
lst = [1, 2, 3, 4, 5]
new_lst = list(filter(lambda x: x % 2 == 0, lst))
print(new_lst) # [2, 4]

filter函数可以用于实现一些筛选和过滤的功能,如删除空值、去重、选择子集等,也是一个很牛的高阶函数。

@properyt 装饰器

@property 装饰器有些时候可以用来替代 getersetter 方法来管理类书信。这个装饰器装饰的函数会转为一个属性的语法,可以在访问和修改属性的时候,执行一些额外的操作,不用显式调用。

例,对类属性的修改与获取, 代码:

# 不使用@property装饰器
class Person:
   def __init__(self, name, age):
       self.name = name
       self.age = age

   def get_name(self):
       return self.name

   def set_name(self, name):
       if not isinstance(name, str):
           raise TypeError("name must be a string")
       self.name = name

   def get_age(self):
       return self.age

   def set_age(self, age):
       if not isinstance(age, int):
           raise TypeError("age must be an integer")
       if age < 0 or age > 150:
           raise ValueError("age must be between 0 and 150")
       self.age = age

p = Person("kira", 40) # 实例化
print(p.get_name()) # kira
print(p.get_age()) # 40
p.set_name("勇哥")
p.set_age(35)
print(p.get_name()) # 勇哥
print(p.get_age()) # 35

# 使用@property装饰器
class Person:
   def __init__(self, name, age):
       self.name = name
       self.age = age

   @property
   def name(self):
       return self._name

   @name.setter
   def name(self, name):
       if not isinstance(name, str):
           raise TypeError("name must be a string")
       self._name = name

   @property
   def age(self):
       return self._age

   @age.setter
   def age(self, age):
       if not isinstance(age, int):
           raise TypeError("age must be an integer")
       if age < 0 or age > 150:
           raise ValueError("age must be between 0 and 150")
       self._age = age

p = Person("kira", 30)
print(p.name) # kira
print(p.age) # 30
p.name = "勇哥"
p.age = 35
print(p.name) # 勇哥
print(p.age) # 35

从上面的代码,我们就可以了解到@property 可以做的事就很多了,比如实现属性管理,数据验证,类型转换,缓存... 可读性和安全性也不错.

slots属性

来到本文的最后一个要分享的 __slots__ 属性,这个事用来指定一个类可以有那些属性的语法,可以用来替代一下__dict__ 来节省类的内存空间,因为他避免了给每个实例创建一个 __dict__ 属性来存储所有属性和值。一般用于内存优化。

例,指定一个类实例时只有指定的属性,代码:

# 不使用__slots__属性
class Person:
   def __init__(self, name, age):
       self.name = name
       self.age = age

p = Person("勇哥", 90)
print(p.__dict__) 
print(p.__sizeof__()) 

输出

{'name': '测试玩家勇哥', 'age': 90}
32
# 使用__slots__属性
class Person:
    # 指定该类只能拥有name和age两个属性
    __slots__ = ("name", "age")

    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 20)
print(p.__sizeof__()) 
print(p.__dict__) 

输出

Traceback (most recent call last):
 File "D:\app\apitest\debug\ts.py", line 82, in <module>
   print(p.__dict__) # AttributeError: 'Person' object has no attribute '__dict__'
AttributeError: 'Person' object has no attribute '__dict__'
32

很明显,这个类已经没有了__dict__属性了,也就是可以减少内存占用,提高访问速度这种玩意,但是也有不好的地方,比如不能多重继承了,也不能添加新属性。要打印出来你限制的属性也就只能dir 或者 getter来获取了。总之小伙伴们看着使用吧。

Last updated