程序员文章、书籍推荐和程序员创业信息与资源分享平台

网站首页 > 技术文章 正文

10个你没有充分利用的令人惊叹的 Python 特性

hfteth 2025-05-21 13:54:36 技术文章 8 ℃


Python 的简单性和多功能性使其成为全球开发人员的最爱。每天有超过 1000 万开发者使用 Python 进行从网络开发、机器学习到网络脚本等各种开发,Python 的功能非常强大。然而,我们中的许多人可能并没有充分利用这些功能。在这篇博客中,我将介绍一些你可能没有充分利用的神奇 Python 功能。

1.Pathlib

-- 将杂乱无章的文件路径转化为流畅的面向对象操作

Python 中的 pathlib 模块提供了一种面向对象的方法来处理文件系统路径。与 os.path 等传统方法相比,它提供了一种更直观、更易读的方法来处理文件路径。

有了 pathlib,路径被视为对象,从而可以更轻松地执行连接路径、检查文件是否存在、读/写文件等操作。

from pathlib import Path

# Create a Path object
path = Path('/path/to/directory')

# Access parts of the path
print(path.name)       # 'directory'
print(path.parent)     # '/path/to'
print(path.suffix)     # ''

# Join paths
path = Path('/path/to')
new_path = path / 'directory' / 'file.txt'

# Checking if path exits 
if path.exists():
    print("The file exists")
else:
    print("The file does not exist")

# Reading from a file
path = Path('/path/to/file.txt')
content = path.read_text()
print(content)

# Listing Directory Contents
for file in path.iterdir():
    print(file)

官方文档:
https://docs.python.org/3/library/pathlib.html

2. Dataclass

-- 让 Python 来处理繁重的工作,而你则专注于数据

在 Python 3.7 中引入的 Python 数据类模块提供了一个装饰器 (@dataclass),它能为类自动生成特殊方法,如 __init__(), __repr__(), __eq__() 等。它通过减少模板代码,简化了主要用于存储数据的类的创建。

数据类减少了对以数据为中心的类中模板代码的需求,使开发人员能够专注于逻辑而不是编写多余的代码。它能自动处理初始化实例、比较对象和打印类的可读表示等常见任务。此外,数据类还支持默认值、类型提示和不变性,只需极少的工作量。

from dataclasses import dataclass

@dataclass
class Product:
    name: str
    price: float
    quantity: int = 0
    category: str = "General"
    
    # Method to restock the product
    def restock(self, amount: int) -> None:
        self.quantity += amount
        print(f"Restocked {amount} units of {self.name}. Total now: {self.quantity}")
    
    # Method to sell the product
    def sell(self, amount: int) -> None:
        if self.quantity >= amount:
            self.quantity -= amount
            print(f"Sold {amount} units of {self.name}. Remaining: {self.quantity}")
        else:
            print(f"Not enough stock to sell {amount} units. Only {self.quantity} available.")
    
    # Method to display product info
    def display_info(self) -> None:
        print(f"Product: {self.name}")
        print(f"Category: {self.category}")
        print(f"Price: ${self.price}")
        print(f"Stock: {self.quantity} units")

# Example usage
product = Product(name="Laptop", price=999.99, quantity=5, category="Electronics")

# Restocking the product
product.restock(10)

# Selling the product
product.sell(3)

# Displaying product information
product.display_info()

3.海象操作符(Walrus)

walrus 操作符 (:=) 是 Python 3.8 中引入的赋值表达式。它允许你为表达式中的变量赋值。在引入之前,赋值只允许在独立语句中进行,但 walrus 操作符允许在循环、条件和函数调用等表达式中进行赋值。

海象操作符将赋值和求值合并为一个步骤,从而减少了冗余,这在循环和条件语句中尤其有用。它提高了可读性和效率,无需单独的赋值步骤,从而减少了代码行数。此外,当需要评估和赋值一个将被多次使用的值时,它还有助于简化代码。

while True:
    user_input = input("Enter a valid string (non-empty): ")
    if len(user_input) > 0:
        break
print(f"Valid input received: {user_input}")

在上述代码中,字符串被评估了两次,一次是在读取输入时,另一次是在 if 条件中检查其长度。

我们看看改用海象操作符的效果:

while (user_input := input("Enter a valid string (non-empty): ")) and len(user_input) == 0:
    print("Invalid input, try again.")
print(f"Valid input received: {user_input}")

这段代码的关键部分是 user_input := input(...),其中的 := 就是“海象操作符”。它的作用是同时赋值和返回值。这意味着 user_input := input(...) 这个表达式会把 input() 的结果赋给 user_input,同时这个表达式的返回值也是 input() 的结果。所以,在 while 循环条件中,不仅仅是检查 user_input,而且还会在检查的同时给 user_input 赋值。

换句话说,这里我们通过海象操作符让代码可以在一行里完成输入和赋值,并立即对输入进行检查。

4. Getter & Setters

-- 代码的无声保护者,巧妙地保护着数据。

Getterssetters 是用于获取(检索)和设置(修改)类中私有属性值的方法。它们控制对对象内部状态的访问,执行规则和验证,同时保持实际数据的封装。

在 Python 中,@property 装饰器创建了一种 Pythonic 方法来实现GettersSetters,而无需像普通方法那样调用它们。

  • 它确保只能设置有效值,避免不必要的错误。
  • 它允许你在不影响外部代码的情况下更改内部实现。

我们看下面这个例子,我希望创建一个余额永远不会为负的 BankAccount 类:

class BankAccount:
    def __init__(self, balance):
        self.balance = balance

account = BankAccount(100)
account.balance = -50 ## Negative Input

使用上述方法可能会导致出现负的账户余额,从而破坏代码和逻辑,而使用 setters 可以确保余额始终为非负,从而提供安全性和验证。

class BankAccount:
    def __init__(self, balance):
        self._balance = None  # private attribute
        self.balance = balance  # invoke setter

    @property
    def balance(self):
        return self._balance

    @balance.setter
    def balance(self, value):
        if value < 0:
            raise ValueError("Balance cannot be negative!")
        self._balance = value

# Usage
account = BankAccount(100)
print(account.balance)  # 100
account.balance = -50  # Raises ValueError: Balance cannot be negative!

经常使用 GetterSetter 非常重要,因为让用户随意修改你的数据,就像让小孩开车一样危险!

5. Memory Slots

在 Python 中,__slots__ 特性通过限制对象的属性来减少内存的使用。通常,Python 使用动态字典 (__dict__)来存储对象的属性,这样做有一定的灵活性,但会消耗更多的内存。

想象一下,在一个每个用户只需要几个属性(姓名、电子邮件)的系统中,你要创建数百万个轻量级用户对象。减少内存使用量可以大大提高性能。

class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email

# Creating a million users without `__slots__`
users = [User(f"User{i}", f"@example.com">user{i}@example.com") for i in range(1000000)]

在上述情况下,Python 为每个 User 对象分配一个内部字典(__dict__),用来存储对象的属性(即 nameemail)。由于字典的灵活性,用户可以动态添加新的属性,比如 user1.age = 25。这种灵活性使得每个对象都维护一个独立的字典,而字典结构会占用额外的内存空间。因此当需要创建 100 万个对象时,每个对象都有自己的字典,整体内存占用会较大。对于内存敏感的应用,这可能成为一个问题。

为了解决这个问题,我们可以这样改写代码:

class User:
    __slots__ = ['name', 'email']  # Declare fixed attributes
    
    def __init__(self, name, email):
        self.name = name
        self.email = email

# Creating a million users with `__slots__`
users = [User(f"User{i}", f"@example.com">user{i}@example.com") for i in range(1000000)]

在优化后的版本中,__slots__ 明确指定了类的属性为 nameemail,这样 Python 不会为每个对象创建 __dict__,而是直接为每个对象分配固定的内存块来存储这些属性。这种方式省去了字典开销,内存占用显著减少。这在需要创建大量对象时可以节省大量内存。

6. functools.lru_cache

在 Python 中,functools.lru_cache 是一个装饰器,它为函数添加了一种简单但有效的缓存机制。缓存(Memoization)是一种通过存储耗时函数调用的结果,并在相同输入再次出现时返回缓存结果的技术,用于加速程序执行。

对于计算量大或经常调用相同参数的函数,这可以大大提高性能。

from functools import lru_cache
@lru_cache(maxsize=None)  # maxsize=None means the cache can grow indefinitely
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

# Calculate Fibonacci numbers
print(fibonacci(10))  # Output: 55
print(fibonacci(15))  # Output: 610

示例解释:

  • @lru_cache(maxsize=None) 修饰了 fibonacci 函数,使缓存大小不受限制(maxsize=None)。
  • fibonacci 函数以递归方式计算斐波那契数字,但通过将结果存储在缓存中避免了冗余计算。

如果不进行内存化,递归计算斐波那契数的效率会因重复计算而降低。使用 lru_cache 进行记忆化可以存储之前计算的结果,减少冗余计算,显著提高性能。

7. Callable

在 Python 中,__call__ 是一种特殊的方法,它允许类的实例像函数一样运行。在类中定义 __call__ 时,可以像函数一样调用对象本身,传递参数并接收返回值。它为对象的交互和行为方式增加了灵活性。

假设你正在为一个 API 创建一个速率限制器,需要限制函数被调用的频率。

class RateLimiter:
    def __init__(self, limit):
        self.limit = limit
    
    def is_allowed(self, requests):
        return requests <= self.limit

limiter = RateLimiter(100)
print(limiter.is_allowed(80))  # True

没有 __call__ 时,每次都需要明确调用 is_allowed,但有了 __call__ 后,RateLimiter 实例的行为就像一个函数,使代码更简单、更整洁。

class RateLimiter:
    def __init__(self, limit):
        self.limit = limit
    
    def __call__(self, requests):
        return requests <= self.limit

# Now you can call the object like a function
limiter = RateLimiter(100)
print(limiter(80))  # True
print(limiter(120))  # False

8. Generators & Yield

Generators 是 Python 中一种特殊的函数类型,使用 yield 关键字返回迭代器。Generators 不会一次性返回所有结果,而是每次生成一个值,允许你按需惰性地迭代数据。这意味着它只在需要时计算值,从而节省内存和处理时间,特别是在处理大型数据集时。

假设你正在处理一个庞大的日志文件,读取每一行并筛选特定的关键字。Generators 可以在不将整个文件载入内存的情况下处理每一行。

def process_logs(filename):
    logs = []
    with open(filename) as file:
        for line in file:
            if "ERROR" in line:
                logs.append(line)
    return logs

# This will load all lines into memory
logs = process_logs("server.log")
for log in logs:
    print(log)

这种方法会将整个日志文件加载到内存中,对于大文件来说并不理想。可以使用Generators改写

def process_logs(filename):
    with open(filename) as file:
        for line in file:
            if "ERROR" in line:
                yield line

# Using a generator to process logs lazily
for log in process_logs("server.log"):
    print(log)

9. Suppress

Suppress 是一个 Python 上下文管理器,你可以使用它选择性地忽略某些错误,而不是用多个 try-except 块来封装您的代码。如果你知道某些异常是可以忽略的,那么它就能提供更简洁、更易读的代码。

举个例子,在进行文件操作时,文件可能不存在,但这对程序并不重要。与其抛出错误或明确捕获错误,不如简单地抑制错误并继续前进。

使用try-except:

try:
    os.remove("non_existent_file.txt")
except FileNotFoundError:
    pass

改用Suppress:

from contextlib import suppress
import os

# Suppress the FileNotFoundError if the file doesn't exist
with suppress(FileNotFoundError):
    os.remove("non_existent_file.txt")

Suppress上下文管理器可以优雅地处理异常,而不需要显式异常处理,从而使代码更加简洁。

10.MappingProxy

MappingProxyType 是 Python 字典的不可变封装。它允许对字典进行只读访问,同时防止修改原始数据。这在需要保护数据但又希望数据可读时特别有用。

假设你正在管理一个系统的配置字典。多个组件都需要读取配置,但任何人都不能在配置设置后对其进行修改。如果使用普通字典:

config = {"debug": True, "version": "1.0"}

# Anyone can modify the configuration
config["version"] = "2.0"

在这种情况下,任何可以访问配置字典的人都可以更改它。而 MappingProxyType 可以将配置字典封装为只读版本,确保任何人都无法在创建后更改它。

from types import MappingProxyType

config = {"debug": True, "version": "1.0"}

# Create a read-only view of the dictionary
read_only_config = MappingProxyType(config)

# Attempt to modify will raise a TypeError
try:
    read_only_config["version"] = "2.0"
except TypeError:
    print("Cannot modify a read-only dictionary.")

# You can still access values
print(read_only_config["version"])  # 1.0

Tags:

最近发表
标签列表