前言

前两天一位小伙伴问了这样一个问题:虽然已经使用python一年多了,也用python写过很多脚本,代码量从几十行到上千行的也有,但从未使用过类(class),似乎用函数(def)就能解决所有问题,使用类有什么好处?我什么时候该用类呢?

关于这个问题,算是困惑了许多刚接触python的同学,那么本文就尝试从多个角度来解读这个问题。首先还是先来看看官方给出类与函数的解释



提供了一种组合数据和功能的方法。 创建一个新类意味着创建一个新的对象类型,从而允许创建一个该类型的新实例 。 每个类的实例可以拥有保存自己状态的属性。 一个类的实例也可以有改变自己状态的(定义在类中的)方法。

函数的本质就是一段有特定功能、可以重复使用的代码,这段代码已经被提前编写好了,并且为其起一个“好听”的名字。在后续编写程序过程中,如果需要同样的功能,直接通过起好的名字就可以调用这段代码。



很显然,这样的答案并没有让人搞明白类和函数到底不一样在哪里。但是里面提到了类是创建一个对象所以类是面向对象程序设计(Object Oriented Programming)。也就是我们常说的OOP。而OOP高度关注的是代码的组织可重用性封装

第一个例子

上面的官方解释上去还是很抽象,那么我们开始说人话。简单来说当Python中没有可以完全表达我们要表示的内容的数据类型时,那么就需要使用一个类。来看下面的例子。

如果我正在计算某人的年龄,则只需使用int 因为它可以满足我的需求。如果我们需要在游戏中表示像敌人之类的东西,则可以创建一个类则可以创建一个类Enemy,其中包含诸如health和armor的数据,并包含诸如fire_weapon射击时的功能。然后,我们还可以创建另一个类FlyingEnemy,Enemy该类从该类继承所有内容,但又具有一个fly方法,因此具有其他功能。


第二个例子

我们再来看一个例子。假设我们需要编写一个音乐播放器。在这个播放器中,我们有关于不同类型数据的信息,如歌曲、专辑、艺术家和播放列表。还有一些可以播放歌曲、播放专辑、播放艺术家或播放播放列表的功能。我们将每种数据存储在字典中,不同类型的数据有不同的字段名,因为每个play函数需要做不同的事情,所以我们就有四个不同的函数

some_song = {
    "title""Yellow Submarine",
    "artist": the_beatles, # 指向到包含该艺术家的词典
    "album": yellow_submarine_album, # 指向包含此相册的dict的链接
    "duration": insert_time_object_here,
    "filepath""path/to/file/on/disk"
}

# 其他数据类型的结构也类似

# 一些函数
def play_song(song):
    # 获取歌的路径
    path = song["filepath"]
    # 播放路径
    call_some_library_function(path)

def play_album(album):
    # 找到专辑里所有的歌曲
    # 分别调用play_song

def play_artist(artist):
    # 找到这位艺术家所有的专辑
    # 分别调用play_album

def play_playlist(playlist):
    # 找到播放列表中的所有歌曲
    # 分别调用play_song
这样写有什么不好?我们有四个非常相似的函数,每个函数都与特定类型的数据相关。你必须把它们叫做不同的东西,而不仅仅是play,你必须确保你把正确的数据传递给它们。虽然这四种不同的类型都可以“播放”,但是没有一种通用的方法可以在不知道它是什么的情况下播放任何东西。那么在OOP下,怎么实现呢:
class Song:
    def __init__(self, title, artist, album, duration, filepath):
        self.title = title
        self.artist = artist
        self.album = album
        self.duration = duration
        self.filepath = filepath

    def play(self):
        path = self.filepath
        call_some_library_function(path)
这样就定义了如何创建一个新的Song对象。该方法将字段值作为参数,并将它们作为对象的属性赋值。self是一个特殊参数(名称不保留;它可以被称为任何东西),它是对对象本身的引用。是一种从同一对象的其他方法内部访问属性和方法的方法。当我们从对象外部访问它们时(要使用play方法时将执行此操作),则可以使用在该范围内为对象指定的任何名称。
那么在之前:
# some_song是上面定义的歌
play_song(some_song)
在使用class之后:
# self参数没有在这里传递;它会自动添加
some_song = Song("Yellow Submarine",
                the_beatles,
                yellow_submarine_album,
                insert_time_object_here,
                "path/to/file/on/disk"
            )
some_song.play()
为什么这样更好?如果我们有一个对象,则不必知道它是什么就可以播放,因为现在播放任何内容的语法都是相同的:anyobject.play()即对象“知道”如何使用“自己的”数据进行处理的设计思想无需从外部检查对象是否具有某些字段并决定如何处理这些内部字段,而是调用play对象提供的方法,并在每个类内部定义该类型的对象应如何实现此功能。

第三个例子

我们继续看下面两段代码来实现输出一些学生的成绩,首先是使用类
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)

# 定义一些学生
john = Student("John"12"male"6, {"math":3.3})
jane = Student("Jane"12"female"6, {"math":3.5})

# 现在我们可以很容易地得到分数
print(john.getGPA())
print(jane.getGPA())
再来看看用函数怎么实现
def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
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}

print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

这两段代码都实现了输出学生的成绩,但是在使用函数的时候,我们需要记住学生是谁,成绩存储在哪里,似乎不是很困难(如果需要输出的学生更多呢),但是OOP避免了这一点。并且代码也更加pythonic

结束语

最后,让我们回到刚开始的问题上来,上面说了这么多类的好处所以我们就应该更多的去使用类吗?并不是!


©著作权归作者所有:来自51CTO博客作者mb5fe18e32e4691的原创作品,如需转载,请注明出处,否则将追究法律责任

更多相关文章

  1. hive窗口函数/分析函数详细剖析
  2. 【Python】一文说清楚类与函数的选择
  3. 字符串处理函数
  4. PHP中字符串处理的一些常用函数
  5. PHP自定义函数+系统函数库(代码示例)
  6. PHP 跨域之header函数(代码示例)
  7. 使用socket系列函数实现连接TCP服务
  8. php中array_replace、array_splice与str_replace函数的比较
  9. PHP函数库之类与对象详解

随机推荐

  1. android eclipse xml不自动代码提示
  2. android AVD 4.0 下解决中文输入问题
  3. Android(安卓)service与Thread
  4. Andriod学习笔记(二)
  5. Android(安卓)实用技巧 --- 命令godir
  6. ANDROID自动化测试工具:ROBOTIUM
  7. Android(安卓)第三方开源SwipeToDismiss:
  8. Android(安卓)Studio自动生成带系统签名
  9. Android(安卓)Camera 使用小结
  10. Android(安卓)事件传递机制