我已经在python中编程了大约两年;主要是数据(pandas,mpl,numpy),还有自动脚本和小型网络应用程序。我试图成为一个更好的程序员并增加我的python知识,困扰我的一件事是我从未使用过类(除了为小型Web应用程序复制随机烧瓶代码之外)。我一般都明白它们是什么,但我似乎无法理解为什么我需要它们来完成一个简单的功能。
为我的问题添加特异性:我写了大量自动报告,这些报告总是涉及从多个数据源(mongo,sql,postgres,apis)中提取数据,执行大量或少量数据修改和格式化,将数据写入csv / excel / html,通过电子邮件发送出去。脚本范围从~250行到~600行。我是否有理由使用课程来完成这项工作?为什么?
答案 0 :(得分:82)
课程是Object Oriented Programming的支柱。 OOP高度关注代码组织,可重用性和封装。
首先,免责声明:OOP与Functional Programming形成对比,后者是Python中使用的不同范例。并非每个使用Python(或肯定是大多数语言)编程的人都使用OOP。你可以在不太面向对象的Java 8中做很多事情。如果您不想使用OOP,那么请不要。如果您只是编写一次性脚本来处理您再也不会使用的数据,那么请继续按照您的方式编写。
但是,使用OOP的原因有很多。
一些原因:
组织: OOP定义了在代码中描述和定义数据和过程的众所周知的标准方法。数据和过程都可以存储在不同的定义级别(在不同的类中),并且有关于这些定义的标准方法。也就是说,如果您以标准方式使用OOP,它将帮助您以后的自己和其他人理解,编辑和使用您的代码。此外,您可以命名数据结构并方便地引用它们,而不是使用复杂的,任意的数据存储机制(可以使用复杂的,任意的数据存储机制或表格,或其他任何内容)。您可以为数据结构命名并方便地引用它们。
状态:OOP可帮助您定义和跟踪状态。例如,在一个典型的例子中,如果您正在创建一个处理学生的程序(例如,成绩计划),您可以在一个地方(姓名,年龄,性别,年级,课程,成绩,教师,同龄人,饮食,特殊需要等),只要对象存活,并且易于访问,这些数据就会持久存在。
Encapsulation: 通过封装,过程和数据一起存储。方法(函数的OOP术语)与它们操作和生成的数据一起定义。在Java等允许access control或Python的语言中,根据您描述公共API的方式,这意味着可以向用户隐藏方法和数据。这意味着,如果您需要或想要更改代码,您可以执行任何您想要的代码实现,但保持公共API不变。
Inheritance:
继承允许您在一个位置(在一个类中)定义数据和过程,然后稍后覆盖或扩展该功能。例如,在Python中,我经常看到人们创建dict
类的子类以添加其他功能。一个常见的更改是覆盖当从不存在的字典请求密钥以基于未知密钥提供默认值时抛出异常的方法。这允许您现在或以后扩展自己的代码,允许其他人扩展您的代码,并允许您扩展其他人的代码。
可重用性:所有这些原因和其他原因都允许代码具有更高的可重用性。面向对象的代码允许您编写一次实体(已测试)代码,然后一遍又一遍地重复使用。如果需要针对特定用例调整某些内容,则可以从现有类继承并覆盖现有行为。如果您需要更改某些内容,则可以在保留现有公共方法签名的同时更改所有内容,并且没有人更明智(希望如此)。
同样,有几个原因不使用OOP,你不需要。但幸运的是,使用像Python这样的语言,你可以使用一点点或者很多,这取决于你。
学生用例的例子(不保证代码质量,只是一个例子):
面向对象
class Student(object):
def __init__(self, name, age, gender, level, grades=None):
self.name = name
self.age = age
self.gender = gender
self.level = level
self.grades = grades or {}
def setGrade(self, course, grade):
self.grades[course] = grade
def getGrade(self, course):
return self.grades[course]
def getGPA(self):
return sum(self.grades.values())/len(self.grades)
# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})
# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())
标准字典
def calculateGPA(gradeDict):
return sum(gradeDict.values())/len(gradeDict)
students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}
students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}
# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))
答案 1 :(得分:11)
每当你需要维持你的函数状态时,它就无法用生成器来完成(函数可以产生而不是返回)。发电机保持自己的状态。
如果要覆盖任何标准运算符,则需要一个类。
每当您使用访客模式时,您都需要课程。使用生成器,上下文管理器(也可以更好地实现为生成器而不是类)和POD类型(字典,列表和元组等),可以更有效,更干净地完成其他所有设计模式。
如果你想编写“pythonic”代码,你应该更喜欢上下文管理器和生成器而不是类。它会更清洁。
如果你想扩展功能,你几乎总能用包容而不是继承来完成它。
每条规则都有例外。如果要快速封装功能(即编写测试代码而不是库级可重用代码),可以将状态封装在类中。它很简单,不需要重复使用。
如果您需要C ++样式析构函数(RIIA),您肯定不想使用类。你想要上下文管理器。
答案 2 :(得分:9)
我认为你做得对。当您需要模拟某些业务逻辑或困难的现实流程以及困难的关系时,类是合理的。 例如:
我还建议您观看this classic video
答案 3 :(得分:4)
一个类定义一个真实世界的实体。如果您正在处理单独存在且具有与其他逻辑分离的逻辑的事物,则应为其创建一个类。例如,一个封装数据库连接的类。
如果不是这种情况,则无需创建类
答案 4 :(得分:0)
这取决于您的想法和设计。如果您是优秀的设计师,那么OOP会以各种设计模式的形式自然出现。 对于简单的脚本级别,处理OOP可能是开销。 简单考虑一下OOP的基本好处,例如可重用和可扩展,并确定是否需要它们。 OOP使复杂的事情变得越来越简单。 使用OOP或不使用OOP都可以使事情简单。使用哪个更简单。