# Define a class representing a bank accountclassBankAccount:def__init__(self, account_id, customer_name, initial_balance=0.0):# Initialize account properties
self.account_id = account_id
self.customer_name = customer_name
self.balance = initial_balance
# Method to deposit money into the accountdefdeposit(self, amount):"""Deposit money into the account."""if amount >0:# Update balance and print deposit information
self.balance += amount
print(f"Deposited ${amount}. New balance: ${self.balance}")else:# Print message for invalid deposit amountprint("Invalid deposit amount. Please deposit a positive amount.")# Method to withdraw money from the accountdefwithdraw(self, amount):"""Withdraw money from the account."""if0< amount <= self.balance:# Update balance and print withdrawal information
self.balance -= amount
print(f"Withdrew ${amount}. New balance: ${self.balance}")elif amount > self.balance:# Print message for insufficient fundsprint("Insufficient funds. Withdrawal canceled.")else:# Print message for invalid withdrawal amountprint("Invalid withdrawal amount. Please withdraw a positive amount.")# Method to check the current balance of the accountdefcheck_balance(self):"""Check the current balance of the account."""print(f"Current balance for {self.customer_name}'s account (ID: {self.account_id}): ${self.balance}")技术支持
例如,假设我们有一个名为 Shape 的父类,其中包含一个计算图形面积的方法。您可能有两个子类:Circle 与 Square。它们都会有名为 Area 的方法,但每种图形的方法定义各不相同。比较下面代码块中不同图形的面积方法。
# Define the parent class ShapeclassShape:# Initialize the attributes for the shapedef__init__(self, name):
self.name = name
# Define a generic method for calculating the areadefarea(self):print(f"The area of {self.name} is unknown.")# Define the child class Circle that inherits from ShapeclassCircle(Shape):# Initialize the attributes for the circledef__init__(self, name, radius):# Call the parent class constructorsuper().__init__(name)
self.radius = radius
# Override the area method for the circledefarea(self):# Use the formula pi * r^2
area =3.14* self.radius **2print(f"The area of {self.name} is {area}.")# Define the child class Square that inherits from ShapeclassSquare(Shape):# Initialize the attributes for the squaredef__init__(self, name, side):# Call the parent class constructorsuper().__init__(name)
self.side = side
# Override the area method for the squaredefarea(self):# Use the formula s^2
area = self.side **2print(f"The area of {self.name} is {area}.")技术支持
# Object-Oriented Programming (OOP) - Class DefinitionclassMathOperation:def__init__(self, operation):
self.operation = operation
defapply_operation(self, number):return self.operation(number)# Functional Programming (FP) - Higher-Order Functionsdeffilter_odd_numbers(numbers):returnlist(filter(lambda x: x %2!=0, numbers))defsquare_operation(number):return number **2defprocess_numbers(numbers, operation_function):returnlist(map(operation_function, numbers))# Example Usage:# Define an OOP class representing a mathematical operation (square in this case)
square_operation_instance = MathOperation(square_operation)# Sample list of numbers
numbers =[1,2,3,4,5,6,7,8,9,10]# Use FP to filter odd numbers
odd_numbers = filter_odd_numbers(numbers)# Use OOP to apply the square operation to odd numbers
result = process_numbers(odd_numbers, square_operation_instance.apply_operation)# Display the resultprint(result)技术支持
良好的结构是高质量代码的标志。而这种结构的性质取决于您采用的编程范式。编程范式是一套连贯的原则与技术,用来界定在设计和编写计算机程序时所采取的方法。
编程范式有很多,比如过程式、命令式、声明式、面向对象以及函数式等。有些语言只支持少数几种范式。例如,SQL 就是一种声明式语言。不过,许多现代语言(包括 Python)支持多种编程范式,从而提供了更大的灵活性。
练习编写纯函数,来自我们“编程范式入门”课程的上机练习。
许多程序员会默认使用自己偏好的范式,通常也是他们最先学会的那一种。更为资深的程序员则了解各类范式的优劣,并能在不同情景下发挥各自所长。下面我们来看看数据科学中最常用的两种编程范式:函数式编程与面向对象编程。
什么是函数式编程(FP)?
在函数式编程中,代码通过定义和使用函数来构建,这些函数决定程序应如何运行。它属于声明式编程范式的一个子类型。
函数是一段接收参数并返回输出的代码。例如,一个简单的函数可以接收一个数字列表作为参数,并仅返回其中的偶数值。
查看这些课程,学习在Python与R中编写函数的最佳实践。
函数式编程以其可追踪性与可预测性而著称。这是因为所用的函数是不可变的,即它们不能被更改。函数会被定义(通常在代码的单独部分,甚至在不同文件中),然后在需要的地方被调用。这一特性使我们易于理解某段代码在做什么,因为函数的行为和调用方式在各处都是一致的。
不可变性的概念也延伸到函数式编程中的数据结构。在 FP 中,更倾向于在更改数据状态时创建其副本。这样就为数据的所有变换建立了一条“虚拟纸质痕迹”,增强了可追踪性并简化了问题定位。但需要注意的是,这通常会增加内存消耗,因为每次状态变化都需要复制整个数据集。
函数式编程中还会用到一类高阶函数。高阶函数是指以函数为参数或将函数作为结果返回的函数。比如,您可能需要一个高阶函数来将某个函数执行多次(如下例所示),或在特定场景下选择合适的函数来使用。
函数式编程在数据分析中的优势
函数式编程为减少代码重复提供了结构。其目标是通过使用高级“动词”,更轻松地表达重复动作。
例如,假设您需要在代码中的多个位置将字符串修改为特定格式:去除空格、标点与数字,并将所有字符转换为小写。您可以在程序的不同部分复制粘贴多行代码来完成,但这种重复会让代码臃肿。更好的做法是定义一个函数来清洗字符串,并在合适的位置调用它。这样能让您的代码更精炼、可读性更强、输入更快捷。
通过函数组合构建复杂的数据管道
在处理大型数据集或数据管道时,函数式编程非常有用,因为它允许我们将复杂的数据处理任务拆分为更小、更易管理的步骤。通常,管道中的每一步至少会对应一个函数。如果您写下了管道所需步骤的流程图,就能大致勾勒出所需的函数。总体而言,每一步至少需要一个函数。
大多数语言还提供了大量内置函数和可下载的扩展函数库。R 语言中的 purrr 包就是很好的例子。这使我们能更快搭建管道,并尽量减少自定义函数的数量。
运用 FP 原则编写表达力强、可读性高的代码
函数式编程的目标是让代码非常易读。以下是构建具有表达力和可读性的函数式代码的一些准则:
什么是面向对象编程(OOP)?
面向对象编程是一种以对象为中心的范式。对象是自包含的实体,既包含数据,也包含对这些数据进行操作的方法。与函数式编程中数据与操作常被分离不同,OOP 将数据与其操作放在一起,这有助于更直观地理解数据发生了什么。
在面向对象编程中,数据与方法被组织进由类定义的对象中。类被设计用来规定每个对象应如何表现,然后对象在该类内被实例化。参阅 DataCamp 的教程,了解 Python 中类的工作机制。
OOP 示例
例如,您可以有一个名为 BankAccount 的类,来描述每个账户应如何运作。该类可以包含余额、账号以及客户姓名等属性;还可以包含描述类内信息如何被影响的方法,如取款、存款与查询余额。
定义好该类后,您就可以创建对象(即某个人的账户)。对象创建后,您可以为各属性填充数据,并通过方法来修改账户。见下例。
封装
将数据与行为捆绑到由类定义的对象中的过程称为封装。它可帮助您创建一个结构清晰的对象,使其与其余代码之间的交互更加容易。存储在对象中的变量称为属性,决定对象行为的函数称为方法。
继承
减少代码重复的一种方式,是在创建新类时使用继承。也就是通过创建新类来保留父类的功能。在我们的银行账户示例中,您可以有一个适用于所有账户的父类,以及针对储蓄账户与支票账户的子类,分别定义具体差异。
多态
多态是一个概念,允许为不同的方法使用相同的名称,而其具体行为会根据输入而改变。这通常与继承配合使用。
例如,假设我们有一个名为 Shape 的父类,其中包含一个计算图形面积的方法。您可能有两个子类:Circle 与 Square。它们都会有名为 Area 的方法,但每种图形的方法定义各不相同。比较下面代码块中不同图形的面积方法。
学习面向对象编程的资源有很多,包括 DataCamp 的Python OOP 课程、R 语言 OOP 课程以及Python OOP 教程。
面向对象编程在数据分析中的优势
面向对象编程可为数据分析带来多方面优势:
OOP 与 FP:如何选择合适的范式
在为数据项目选择 OOP 或 FP 时,需要考虑许多因素。有些数据团队可能强烈偏好其中一种范式,这种情况下,与团队保持一致往往是更好的选择。在其他情况下,数据或应用的性质可能会让某一方更占优势。
何时在数据任务中采用 OOP
当您需要对实体进行建模时,面向对象编程非常出色。典型示例包括设计一个图书馆管理系统,或创建一个为投资组合中的每家公司打分的系统。创建封装对象的过程使得跟踪这些实体更为容易。当内存有限时也更偏向 OOP,因为相比之下 FP 往往需要更多内存。
何时在数据任务中采用 FP
函数式编程在需要并行处理的数据分析任务中表现优异。其对不可变性与避免副作用的强调,使得对数据集的操作更易并行化。函数可以在数据的不同部分上并发应用而互不干扰。
FP 对不可变性的关注也使其非常适合数据转换管道。函数可以组合起来,创建一系列不改变状态的转换步骤,从而使代码更易读、更易维护。
此外,FP 通常更受科学家与学者的青睐,因为它更贴近纯数学的思维方式。
结合使用 OOP 与 FP 技术
在一些实际应用中,可以在同一程序中结合使用 OOP 与 FP。这使开发者能够发挥两种范式的优势。OOP 的封装性与 FP 的表达力都可用于构建机器学习管道或 Web 应用。
不过,如此混合使用也会带来挑战,必须谨慎进行。跨越多种范式的代码可能更难理解,组件的平滑集成也更具挑战性。
请看下面的示例。该 Python 代码结合了 OOP 与 FP,对一个数字列表执行数学运算。MathOperation 类充当由 FP 创建的特定数学运算的容器。filter_odd_numbers 与 square_operation 两个函数分别用于筛选奇数并计算平方。通过将这些组件结合使用,代码能高效地从给定列表中过滤出奇数并求其平方,展示了 OOP 与 FP 如何协同工作来简化并组织数据分析任务。
想要查看更多结合 OOP 与 FP 的示例,请查看这个GitHub 代码库。
结论
并不存在“最佳”编程范式的绝对答案,选择取决于上下文、目标、语言以及程序员的偏好。不过,您可以遵循一些通用指南来判断哪种范式更适合您的情境。
当您需要用多个实体与交互来建模复杂系统,并希望将数据与行为封装成可复用组件时,请使用 OOP。当您需要在简单输入与输出之间进行纯计算,并且需要避免副作用或状态变化时,请使用 FP。当您需要同时借助两种范式的优势,并需要适应语言的特性与限制时,可以采用混合方式。
优秀的程序员不会被某一种范式所局限;他们会根据目标在不同范式之间自如切换。通过“编程范式入门”课程深入探索多种范式。尝试做一个数据项目,并同时运用 OOP 与 FP 来解决问题。不断练习各类范式,将帮助您形成判断哪一种更适合项目的直觉。