面向对象编程

https://m.toutiao.com/is/JnNfhMM/

面向对象编程(OOP)对于初学者来说可能是一个很难理解的概念。很多书籍都是从解释OOP开始,讨论三大术语:封装、继承和多态性,但是解释的效果往往让人失望。

本文希望让程序员、数据科学家和python爱好者们更容易理解这个概念。我们去掉所有的行话,通过一些例子来做解说。

这篇文章是关于解释OOP的外行方式。

什么是对象和类

简单地说,Python中的一切都是对象,类是对象的蓝图。所以当我们写下:

a = 2b = 'Hello!'

我们正在创建一个int类的对象a,该对象的值为2,str类的对象b的值为“Hello!”(在默认情况下,用两个引号来提供字符串)。

另外,我们常常无意识地使用到了类和对象的概念,例如在使用scikit-learn模型时,我们实际上是在使用一个类。

clf = RandomForestClassifier()clf.fit(X,y)

这里的分类器clf是一个对象,fit是在RandomForestClassifier类中定义的一个方法。

为什么要使用类

为什么我们经常使用类呢?我们可以用函数实现同样的功能吗?

可以。但是与函数相比,类为我们提供了更多功能。举个例子,str类有很多为对象定义的函数,只需按tab键就可以访问这些函数。我们也可以编写这些函数,但是只按tab键不能使用自己编写的函数。

类的这个属性被称为封装。封装是指将数据与操作该数据的方法捆绑在一起,或者限制对对象某些组件的直接访问。

所以这里str类绑定了数据(“Hello!”)以及所有对数据进行操作的方法。同样,'RandomForestClassifier’类将所有的方法(fit、predict等)捆绑在一起。

除此之外,使用类还可以使代码更加模块化和易于维护。假设我们要创建一个像Scikit-Learn这样的库,就需要创建许多模型,每个模型都有一个fit和predict方法,如果不使用类,我们需要为每个模型提供许多函数,例如:

RFCFitRFCPredictSVCFitSVCPredictLRFitLRPredict and so on.

这种代码结构简直是一场噩梦,因此Scikit Learn将每个模型定义为一个具有fit和predict方法的类。

创建类

现在我们已经了解了为什么要使用类,以及它们为何如此重要。那么如何开始使用它们呢?创建一个类非常简单,下面是编写任何类的样板代码:

class myClass:    def __init__(self, a, b):        self.a = a        self.b = b    def somefunc(self, arg1, arg2):        # 这里有些代码

这里有很多新的关键字。主要是class__init__self。这些是什么呢?

假设你在一家有很多账户的银行工作。我们可以创建一个名为account的类,用于处理任何帐户。例如,下面我创建了一个基本的帐户,它为用户存储数据,即帐户名和余额,它还为我们提供了两种银行存款/取款的方法。请通读一遍以下代码,它遵循与上面代码相同的结构。

class Account: def __init__(self, account_name, balance=0): self.account_name = account_name self.balance = balance def deposit(self, amount): self.balance += amount def withdraw(self,amount): if amount <= self.balance: self.balance -= amount else: print('Cannot Withdraw amounts as no funds!!!')

我们使用以下方法创建一个名为Rahul、金额为100的帐户:

myAccount = Account('Rahul',100)

使用以下方法访问此帐户的数据:

但是,如何将这些属性balance和account_name分别设置为100和“Rahul”?我们从来没有调用过__init__方法,为什么对象会获得这些属性?答案是,只要我们创建对象,它就会运行。因此,当我们创建myAccount时,它会自动运行函数__init__

现在让我们试着存一些钱到我们的账户里:

我们的余额上升到200英镑。你有没有注意到,函数deposit需要两个参数,即self和amount,但我们只提供了一个参数,而且仍然有效。

那么这个self是什么?下面我调用属于类account的同一个函数deposit,并向它提供myAccount对象和amount。现在函数需要两个参数。

我们的账户余额如预期增加了100。所以这是我们调用的同一个函数。只有self和myAccount是完全相同的对象时,才会发生这种情况。Python为函数调用提供与参数self相同的对象myAccount。这就是为什么self.balance在函数定义中真正指的是myAccount.balance.

但是仍然存在一些问题

我们知道如何创建类,但是还有一个重要的问题我还没有提到。

假设你正在与苹果iPhone部门合作,且必须为每种iPhone型号创建一个不同的类。对于这个例子,假设我们的iPhone的第一个版本目前只做一件事——打电话并存储。可以这样写:

class iPhone: def __init__(self, memory, user_id): self.memory = memory self.mobile_id = user_id def call(self, contactNum): # 这里有些实现

现在,苹果计划推出iPhone1,这款iPhone机型引入了一项新功能——拍照功能。一种方法是复制粘贴上述代码并创建一个新的类iPhone1,如下所示:

class iPhone1:    def __init__(self, memory, user_id):         self.memory = memory         self.mobile_id = user_id         self.pics = []    def call(self, contactNum):         # 这里有些实现    def click_pic(self):         # 这里有些实现         pic_taken = ...         self.pics.append(pic_taken)

但正如你所看到的,这是大量不必要的代码重复(上面用粗体显示),Python有一个消除代码重复的解决方案。编写iPhone1类的一个好方法是:

Class iPhone1(iPhone): def __init__(self,memory,user_id): super().__init__(memory,user_id) self.pics = [] def click_pic(self): # 这里有些实现 pic_taken = ... self.pics.append(pic_taken)

这就是继承的概念,继承是将一个对象或类基于另一个保留类似实现的对象或类的机制。简单地说,iPhone1现在可以访问类iPhone中定义的所有变量和方法。

在本例中,我们不必进行任何代码复制,因为我们已经从父类iPhone继承(获取)了所有方法。因此,我们不必再次定义调用函数。另外,我们不使用super在函数中设置mobile_uid和内存。

super().__init__(memory,user_id)是什么?

在现实生活中,你的初始函数不是这些漂亮的函数。你将需要在类中定义许多变量/属性,并且复制并粘贴子类(这里是iphone1),会很麻烦。因此存在super(),这里super().__init__()实际上是调用父iPhone类的__init__方法。因此当类iPhone1的__init__函数运行时,它会自动使用父类的__init__函数设置类的memory和user_id。

在ML/DS/DL中的哪里可以看到?下面我们创建PyTorch模型,此模型继承了nn.Module类,并使用super调用该类的__init__函数。

class myNeuralNet(nn.Module):    def __init__(self):        super().__init__()        # 在这里定义所有层        self.lin1 = nn.Linear(784, 30)        self.lin2 = nn.Linear(30, 10)    def forward(self, x):        # 在此处连接层输出以定义前向传播        x = self.lin1(x)        x = self.lin2(x)        return x

那么多态又是什么?看下面的类:

import mathclass Shape: def __init__(self, name): self.name = name def area(self): pass def getName(self): return self.nameclass Rectangle(Shape): def __init__(self, name, length, breadth): super().__init__(name) self.length = length self.breadth = breadth def area(self): return self.length*self.breadthclass Square(Rectangle): def __init__(self, name, side): super().__init__(name,side,side)class Circle(Shape): def __init__(self, name, radius): super().__init__(name) self.radius = radius def area(self): return pi*self.radius**2

这里我们有基类Shape和其他派生类-Rectangle和Circle。另外,看看我们如何在Square类中使用多个级别的继承,Square类是从Rectangle派生的,而Rectangle又是从Shape派生的。每个类都有一个名为area的函数,它是根据形状定义的。

因此,通过Python中的多态性,一个同名函数可以执行多个任务。事实上,这就是多态性的字面意思:“具有多种形式的东西”。所以这里我们的函数area有多种形式。

多态性与Python一起工作的另一种方式是使用isinstance方法。因此,使用上面的类,如果我们这样做:

对象mySquare的实例类型是方形、矩形和形状,因此对象是多态的,有很多好的特性。例如,我们可以创建一个与Shape对象一起工作的函数,它将通过使用多态性完全处理任何派生类(Square、Circle、Rectangle等)。

更多信息

为什么有些函数名或属性名以单下划线和双下划线开头?有时我们想让类中的属性和函数私有化,而不允许用户看到它们,这是封装的一部分,我们希望“限制对对象某些组件的直接访问”。例如,假设我们不想让用户看到我们的iPhone创建后的memory(RAM)。在这种情况下,我们使用变量名中的下划线创建属性。

因此,当我们以下面的方式创建iPhone类时,你将无法访问你的memory或iphone私有函数,因为该属性现在使用_

但你仍然可以使用(尽管不建议使用)更改变量值。

你还可以使用私有函数myphone._privatefunc()。如果要避免这种情况,可以在变量名前面使用双下划线。例如,在调用print(myphone.__memory)下面抛出一个错误。此外,你无法使用myphone更改对象的内部数据myphone.__memory = 1

但是,正如你所见,你可以在类定义中的函数setMemory中访问和修改self.__memory

结论

希望本文对你理解类很有用。总结一下在这篇文章中我们学习的OOP和创建类以及OOP的各种基础知识:

  • 封装:对象包含自身的所有数据;

  • 继承:创建一个类层次结构,其中父类的方法传递给子类;

  • 多态:函数有多种形式,或者对象可能有多种类型。

我们以一个练习结束本文,让你去实现:创建一个类,使你可以使用体积和曲面面积管理三维对象(球体和立方体)。基本样板代码如下所示:

import mathclass Shape3d:    def __init__(self, name):        self.name = name    def surfaceArea(self):        pass    def volume(self):        pass    def getName(self):        return self.nameclass Cuboid():    passclass Cube():    passclass Sphere():    pass

如果你想了解更多关于Python的知识,可以参考密歇根大学(universityofmichigan)的一门关于学习中级Python的优秀课程:https://bit.ly/2XshreA。

(0)

相关推荐

  • 关于def __init__(self)的一些知识点

    def __init__(self)在Python里面很常见,Python中的self在Python中的类Class的代码中,常看到函数中的第一个参数,都是self.以及Class中的函数里面,访问对 ...

  • Python教程:面向对象编程的一些知识点总结

    类与实例 类是对象的定义,而实例是"真正的实物",它存放了类中所定义的对象的具体信息. 类.属性和方法命名规范 类名通常由大写字母打头.这是标准惯例,可以帮助你识别类,特别是在实例 ...

  • 面向对象编程(中)

    继承性(inheritance) 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类 中, 那么多个类无需再定义这些属性和行为,只要继承那个类即可 多个类称为子类(派生类),单独的这个类称为父类 ...

  • java 面向对象编程

    什么是面向对象 回顾方法的定义与调用 方法的定义 import java.io.IOException; //Demo01 类 public class Demo01 { //main方法 publi ...

  • Go 是面向对象编程风格的编程语言吗?

    Go编程时光 零基础 Go入门教程连载中... 43篇原创内容 公众号 01 介绍 Golang 语言是面向对象语言吗?Golang 语言官方的回答是 Yes and no.什么意思呢?Golang ...

  • Python 面向对象编程(一)

    虽然Python是解释性语言,但是它是面向对象的,能够进行对象编程.下面就来了解一下如何在Python中进行对象编程. 一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类 ...

  • Python面向对象编程(二)

    在前面一篇文章中谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装.下面就来了解一下另外两大特征:继承和多态. 在Python中,如果需要的话,可以让一个类去继承一个类,被继承的 ...

  • 面向对象编程(二)

    使用__slots__ 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性.先定义class: class Student ...

  • Python 面向对象编程的核心概念知识点简介

    本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理. 以下文章来源于 无量测试之道 ,作者: 无量测试之道 面向对象编程的核心概念:封装,抽象,多态.继 ...

  • 高级篇||PLC的面向对象编程

    面向对象编程是计算机高级语言的一种先进的编程模式,在工业控制系统的PLC程序中也可以采用这种设计思想,虽然我们无法实现面向对象的很多优秀特点如"继承",甚至于它根本就不具备面向对象 ...

  • 走进面向对象编程的世界

    InfoQ2021-03-06 15:37:00 面向对象编程(Object-Oriented Programming,OOP)是一种编程范式或编程方式,它使用类和对象来解决问题. 类只是定义任何有形 ...