Python方法链调用的优雅之处

前言

在日常的Python开发中,我们经常会遇到需要对一个对象进行一系列的操作,然后得到最终结果的情况。

而方法链调用(method chaining)就是一种十分优雅的解决方案。

例如:字符串的各种方法

print("公众号:海哥python".strip().lower().title())  # 公众号:海哥Python

方法链接是一种编程风格,其中多个方法调用按顺序发生。它消除了在每个中间步骤分配变量的痛苦,因为每个调用都对同一对象执行操作,然后将该对象返回到下一个调用。

  • 它可以减少整个代码的长度,因为不必创建无数变量
  • 由于方法是按顺序调用的,因此可以提高代码的可读性。

怎么实现

其实实现起来简单,就像这样,唱着,跳着就实现了:

def hai_ge(arg,*args, **kwargs):
    print(arg)
    return hai_ge

hai_ge("唱")("跳")("rap")("篮球")

# 唱
# 跳
# rap
# 篮球

开个玩笑,这种方式比较少用,下面介绍常见用法。


原理:方法链调用的实现依赖于Python中函数的返回值。当一个函数需要返回一个对象时,我们可以选择返回对象本身,也可以返回对象的某个属性或方法的结果,从而实现链式调用

这种方式使得我们可以在一个对象上一连串地调用多个方法,而不需要每次都创建中间变量来保存中间结果,大大简化了代码逻辑。

普通方式实现方法链调用

from functools import reduce


class DataProcessor:
    def __init__(self, data):
        self.data = data

    def filter(self, condition):
        self.data = [x for x in self.data if condition(x)]
        return self  # 返回自身对象,实现链式调用

    def map(self, func):
        self.data = [func(x) for x in self.data]
        return self  # 返回自身对象,实现链式调用

    def reduce(self, func, initial):
        return reduce(func, self.data, initial)


# 使用方法链调用
result = DataProcessor([1, 2, 1, 6, 8, 3, 3]).filter(lambda x: x > 0).map(lambda x: x * 2).reduce(lambda x, y: x + y, 0)
# 等价于
# dp = DataProcessor([1, 2, 1, 6, 8, 3, 3])
# dp.filter(lambda x: x > 0)
# dp.map(lambda x: x * 2)
# result = dp.reduce(lambda x, y: x + y, 0)

print(result)  # 48

上面的代码演示了一个简单的数据处理类,通过方法链调用实现了数据的筛选、转换和聚合操作。这样的代码更加直观,清晰地展现了数据处理的流程。同时,我们也可以随时在方法链中加入新的操作,而不需要改动其他部分的代码,极大地提高了代码的灵活性和可维护性。

基于装饰器方式实现方法链

from functools import reduce

def chainable_method(method):
    def wrapper(self, *args, **kwargs):
        method(self, *args, **kwargs)
        return self
    return wrapper

class DataProcessor:
    def __init__(self, data):
        self.data = data
        self._result = None

    @property
    def result(self):
        return self._result

    @chainable_method
    def filter(self, condition):
        self.data = [x for x in self.data if condition(x)]

    @chainable_method
    def map(self, func):
        self.data = [func(x) for x in self.data]

    def reduce(self, func, initial):
        self._result = reduce(func, self.data, initial)
        return self._result

# 使用方法链调用
dp = DataProcessor([1, 2, 1, 6, 8, 3, 3])
result = dp.filter(lambda x: x > 0).map(lambda x: x * 2).reduce(lambda x, y: x + y, 0)
print(result)  # 48

# 使用属性获取结果
print(dp.result)  # 48

在上面的代码中,chainable_method装饰器用于将类方法转换为可链式调用的方法。它将被修饰的方法包装在一个函数中,该函数调用原始方法并返回self,以便可以连续调用其他方法。

在实际应用中,方法链调用可以极大地简化代码(一个变量名想半天)。例如,在数据处理的过程中,我们可能需要进行一系列的筛选、转换和聚合操作,通过方法链调用,可以非常直观地表达出数据的处理流程,而且可以方便地进行调试和修改。

应用场景

链式调用在Python中通常用于构建流畅的API接口或简化操作流程。

numpy中的链式调用

import numpy as np

chained_numpy = np.arange(1, 9, 2).reshape(2, 2).clip(3, 6)
print(chained_numpy)

"""
[[3 3] 
 [5 6]]
"""

在此示例中,不同的NumPy方法被链接起来创建一个2x2矩阵。

  • .arange()方法:创建一个从19的矩阵,步长为2;
  • .reshape()方法:将矩阵调整为2x2配置;
  • .clip()方法:用于将矩阵中的最小元素设置为9,最大元素设置为25

pandas中的链式调用示例

import pandas as pd

# 创建一个 DataFrame
data = {'A': [1, 2, 3, 4, 5],
        'B': [10, 20, 30, 40, 50],
        'C': [100, 200, 300, 400, 500]}
df = pd.DataFrame(data)

print(df)

# 链式调用示例:选择列、过滤数据、计算平均值
result = (df[['A', 'B']]            # 选择列 A 和 B
          .query('A > 2')            # 过滤 A 列大于 2 的行
          .apply(lambda x: x * 2)    # 对选定的行进行元素乘以2的操作
          .mean())                   # 计算均值
print("\n")
print(result)

结果如下:

自定义数据处理流水线

方法链在对集合或数据结构执行操作(例如过滤映射减少数据)时非常有用。

假设我们现在有个txt文件,需要对其中的数据进行清洗转换: data --> NEW DATA

# data.txt

data1
data2
data3

实现代码示例:

# 假设这些函数是用于数据处理的辅助函数
def load_data_from_file(file_path):
    with open(file_path, "r") as file:
        return [line.strip() for line in file]

def clean_data(data):
    return [item.upper() for item in data]

def transform_data(data):
    return [item.replace("DATA", "NEW DATA") for item in data]

def save_results_to_file(data, file_path):
    with open(file_path, "w") as file:
        for item in data:
            file.write(f"{item}\n")

class DataPipeline:
    def __init__(self):
        self.data = None

    def load_data(self, file_path):
        self.data = load_data_from_file(file_path)
        return self

    def clean_data(self):
        if self.data is not None:
            self.data = clean_data(self.data)
        return self

    def transform_data(self):
        if self.data is not None:
            self.data = transform_data(self.data)
        return self

    def save_results(self, file_path):
        if self.data is not None:
            save_results_to_file(self.data, file_path)
        return self

# 测试数据
file_path = "data.txt"

# 测试代码
pipeline = DataPipeline()
pipeline.load_data(file_path).clean_data().transform_data().save_results("results.txt")

结果为:

# results.txt

NEW DATA1
NEW DATA2
NEW DATA3

Pillow中的方法链调用

你是否想顺滑地使用当下最火热的大模型 ChatGPT-4、Midjourney V6、Dall·E 3、文心一言 4.0 和 Gemini Pro?

想要破除上网魔法?想要降低使用成本?想在同一个平台上使用多个大语言模型?我推荐以下这个好用的工具,它都能满足:

这个服务刚刚推出,推广期间优惠价 298,即将涨价到 498。扫描上方二维码,即可免费试用,大家觉得效果满意后,再考虑付费。

现在的需求是要将其转换成灰度图,旋转,应用高斯模糊。

from PIL import Image, ImageFilter

Image.open('Hulu-AI.png').convert('L').rotate(180).filter(ImageFilter.GaussianBlur()).save('公众号:海哥python2.jpg', quality=95)

# 等价于
# im = Image.open('Hulu-AI.png')
# im = im.convert('L')
# im = im.rotate(180)
# im = im.filter(ImageFilter.GaussianBlur())
# im.save('公众号:海哥python.jpg', quality=95)

处理后的图像如下所示:

配置工具(Configuration Tools)

方法链用于在对象上设置各种配置选项,其中每个方法调用都会修改特定设置。

class Config:
    def __init__(self):
        self.options = {}

    def set_option(self, key, value):
        self.options[key] = value
        return self  # 返回 self,以支持链式调用

    def get_option(self, key):
        return self.options.get(key, None)


# 使用示例
config = Config()
config.set_option('debug', True).set_option('log_level', 'info').set_option('max_connections', 10)

print(config.get_option('debug'))  # 输出: True
print(config.get_option('log_level'))  # 输出: info
print(config.get_option('max_connections'))  # 输出: 10

在这个示例中Config类代表了一个配置对象,set_option方法用于设置配置选项,同时返回self以支持链式调用。这样,可以连续调用多个set_option方法来设置多个配置选项,使代码更加简洁和易读。

复杂对象的构建

构建器模式是一种设计模式,它可以提高代码的灵活性、可读性和可维护性,特别是当需要创建具有许多属性的复杂对象时。

class Product:
    def __init__(self, name, price, category):
        self.name = name
        self.price = price
        self.category = category

    def __repr__(self):
        return f"Product(name={self.name}, price={self.price}, category={self.category})"


class ProductBuilder:
    def __init__(self):
        self.name = None
        self.price = None
        self.category = None

    def set_name(self, name):
        self.name = name
        return self

    def set_price(self, price):
        self.price = price
        return self

    def set_category(self, category):
        self.category = category
        return self

    def build(self):
        if not all([self.name, self.price, self.category]):
            raise ValueError("Incomplete product information.")
        return Product(self.name, self.price, self.category)


# 测试数据
product_data = {
    "name": "Product1",
    "price": 100,
    "category": "Electronics"
}

# 测试代码
builder = ProductBuilder()
product = builder.set_name(product_data["name"]).set_price(product_data["price"]).set_category(product_data["category"]).build()
print(product)  # Product(name=Product1, price=100, category=Electronics)

这段代码定义了Product类来表示产品,以及ProductBuilder 类来构建产品对象。然后,使用给定的产品数据,我们创建了一个产品构建器并构建了一个产品对象,并将其打印出来。

简易计算器

class Calculator:
    def __init__(self):
        self.result = None

    def operate(self, operator, value):
        if self.result is None:
            self.result = value
        else:
            if operator == '+':
                self.result += value
            elif operator == '-':
                self.result -= value
            elif operator == '*':
                self.result *= value
            elif operator == '/':
                self.result /= value
        return self

    def add(self, value):
        return self.operate('+', value)

    def sub(self, value):
        return self.operate('-', value)

    def mul(self, value):
        return self.operate('*', value)

    def div(self, value):
        return self.operate('/', value)

    @property
    def get_result(self):
        return self.result


# 创建 Calculator 实例并使用链式调用
chain_obj = Calculator().add(4).sub(2).mul(6).div(4).get_result
print(chain_obj)  # 输出结果为: 3.0

查询数据库

方法链通常在查询生成器或对象关系映射(ORM)库中用于构造复杂的数据库查询,这里以django自带的ORM为例。

from django.db import models

# 创建学生表
class Student(models.Model):
    name = models.CharField(max_length=32, verbose_name='名字')
    age = models.IntegerField(verbose_name='年龄')
    height = models.DecimalField(max_digits=5, decimal_places=2, verbose_name='身高', null=True)  # 长度为5,两位小数
    birthday = models.DateField(verbose_name='生日')

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'student'
        verbose_name = '学生'
        verbose_name_plural = '学生'  # 显示的是后台模块的名字

# 添加数据
student = Student(name='海哥python', age=3, birthday='2023-2-16')
student.save()

通过ORM链式调用:

# 查询数据库所有数据
all_students = Student.objects.all()
print(all_students)

# 筛选条件
filtered_students = Student.objects.filter(birthday='2023-2-16')
print(filtered_students)

# 修改id为1的人的名字为[公众号:海哥python]
Student.objects.filter(id=1).update(name='公众号:海哥python')

自定义一个简易ORM(玩具)

现在,其实我们可以自己来实现简单的玩具版ORM~

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

    def __repr__(self):
        return f"User(name={self.name}, age={self.age}, status={self.status})"

class QueryBuilder:
    def __init__(self, model):
        self.model = model
        self.query = []

    def filter(self, **kwargs):
        self.query.append(kwargs)
        return self

    def exclude(self, **kwargs):
        self.query.append({'NOT_' + key: value for key, value in kwargs.items()})
        return self

    def order_by(self, *args):
        self.query.append(('ORDER_BY', args))
        return self

    def values_list(self, *args):
        self.query.append(('VALUES_LIST', args))
        return self

    def execute(self, dataset):
        filtered_data = dataset
        for condition in self.query:
            if isinstance(condition, dict):
                filtered_data = [item for item in filtered_data if all(getattr(item, key) == value for key, value in condition.items())]
            elif condition[0] == 'ORDER_BY':
                filtered_data.sort(key=lambda x: getattr(x, condition[1][0]))
            elif condition[0] == 'VALUES_LIST':
                # filtered_data = [[getattr(item, field) for field in condition[1]] for item in filtered_data]
                filtered_data = ([getattr(item, field) for field in condition[1]] for item in filtered_data)
        return filtered_data

# 测试数据
users = [
    User("Alice", 25, "active"),
    User("Bob", 30, "inactive"),
    User("Charlie", 25, "active"),
    User("David", 35, "inactive"),
]

# 测试代码
query_builder = QueryBuilder(User)
result = query_builder.filter(age=25).filter(name="Charlie").values_list("age", "name").execute(users)
#result = query_builder.filter(age=25, name="Charlie").filter().values_list("age", "name").execute(users)
print(f"查询参数列表为:{query_builder.query}", "\n")
for user in result:
    print(f"查询到用户:{user}")

输出结果为:

我们将查询的方法链转换为相应的查询条件,并在最终执行查询时将其应用于模拟的数据集合。

函数式编程与方法链

方法链和函数式编程中的链式调用类似,它们都是一种通过在对象或函数之间连续调用方法或函数来构建复杂操作的技术。两者的主要区别在于:

  1. 方法链:通常用于面向对象编程中,其中对象具有一系列方法,每个方法都返回对象本身或者另一个相关对象。通过在对象上连续调用方法,可以实现一系列操作,最终得到期望的结果。方法链通常与面向对象编程语言中的流畅接口(fluent interface)一起使用。

  2. 函数式编程中的链式调用:则是通过在函数之间连续调用函数来实现类似的目的。在函数式编程中,函数是一等公民,可以作为参数传递给其他函数,也可以作为返回值返回。通过在函数之间连续调用函数,可以构建数据处理流水线,实现复杂的数据转换和过滤操作。

举个栗子

有下面这样一段函数式编程的代码:

result = list(
    map(lambda x: x + 1,
        filter(lambda x: x > 5,
               map(lambda x: x * 2, range(10))))
)
print("函数式编程链式调用结果:", result)  # 函数式编程链式调用结果: [7, 9, 11, 13, 15, 17, 19]

哈哈,小朋友,你是不是有很多问号???

咋一看感觉不是很直观呢。

既然我们学会了方法链,那让我们来改写下,看看有啥区别!!

class ChainIterator:
    def __init__(self, data):
        self.data = data

    def map(self, func):
        self.data = map(func, self.data)
        return self

    def filter(self, func):
        self.data = filter(func, self.data)
        return self

    @property
    def to_list(self):
        return list(self.data)


result = (
    ChainIterator(range(10))
    .map(lambda x: x * 2)
    .filter(lambda x: x > 5)
    .map(lambda x: x + 1)
    .to_list
)
print("方法链调用结果:", result)  #  法链调用结果: [7, 9, 11, 13, 15, 17, 19]

虽然两者在概念上相似,但在实现方式和语义上有所不同。方法链通常与面向对象编程和流畅接口一起使用,而函数式编程中的链式调用则更强调函数之间的组合和转换。

小彩蛋:“属性链”

我们“精通”了方法链,我们本能的想到还有“属性链”

来玩一下…

没错,使用python中的magic method实现属性链

抖机灵玩法一

class Chain:
    def __init__(self):
        self._value = 0

    def __getitem__(self, item):
        self._value += item
        return self

    def __call__(self, *args, **kwargs):
        return self


c2 = Chain()
c2[1][3][2][4][5][6]
print(c2._value)

没难度啊~

抖机灵玩法二

class Chain(object):
    def __init__(self, path=''):
        self._path=path
 
    def __getattr__(self, path):
        print(f"do __getattr__: {path}")
        return Chain( '%s/%s' %(self._path, path))
 
    def __str__(self):
        return self._path
 
    def __call__(self, user):
        print(f"do __call__: {user}")
        return Chain('%s/%s' %(self._path, user))
 
    __repr__=__str__


print('GET',Chain().users('user').u1111)

咱还是学过魔术方法的~

组合拳

class Chain:
    def __init__(self):
        self._value = 0

    def __add__(self, other):
        self._value += other
        return self
    
    def add(self, other):
        self._value += other
        return self

    def __getitem__(self, item):  # []
        print(f"do __getitem__: {item}")
        if isinstance(item, int):
            self._value += item
        elif isinstance(item, slice):
            if item == slice(None, None, None):  # 空切片处理
                pass  # 空切片不执行任何操作
            else:
                start = item.start if item.start is not None else 0
                stop = item.stop if item.stop is not None else 0
                step = item.step if item.step is not None else 1
                self._value += sum(range(start, stop, step))
        elif isinstance(item, list):
            self._value += sum(item)

        return self
    
    def __getattr__(self, item):
        print(f"do __getattr__: {item}")
        return self

    def __call__(self, *args, **kwargs):
        for arg in args:
            if isinstance(arg, int):
                self._value += arg
            elif isinstance(arg, list):
                self._value += sum(arg)
        return self

    def __iter__(self):
        return iter(self._value)

    def __repr__(self):
        return str(self._value)


c2 = Chain()
c2[:][1:4][2][4][5][6].a.b([1, 2, 3]).c[3].d[:][:]
print(c2._value)

啊,这是什么黑魔法~

提高了代码的不可读性哈哈~~

有了这些黑魔法,可以更好的写防御性代码啦。这样你的不可替代性增强了,可以更好的堆ss代码啦!!

小结

方法链调用作为一种优雅的编程风格,能够很好地支持函数式编程,简化代码,提高可读性。在数据处理函数式编程查询数据库配置设置构建流畅的 API等场景下都有着广泛的应用。

在使用方法链调用时,也需要注意避免链式调用过长、逻辑不清晰等问题。适当地使用方法链调用,能够提高代码的可读性和可维护性,但过度使用可能会使代码难以理解。

因此,在实际的编程工作中,我们应当充分发挥方法链调用的优势,以提高代码质量和开发效率。

希望本文的介绍能够帮助大家更好地理解方法链调用的概念和价值,进一步提升Python编程的技巧和水平。

最后

今天的分享就到这里。

如果觉得不错,点赞在看关注安排起来吧。

参考

https://nikhilakki.in/understanding-method-chaining-in-python
https://www.tutorialspoint.com/Explain-Python-class-method-chaining
https://github.com/mCodingLLC/VideosSampleCode/blob/master/videos/095_method_chaining_and_self/method_chaining_and_self.py

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海哥python

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值