Python科学计算与数据处理7:类和对象

class

类与对象

Python是面向对象的语言(object-oriented),同样面向对象的语言还有C++,Java等;与之相对的是面向过程的语言(procedural),例如C语言。前面的教程中,我们也主要是采用了面向过程的编程方式,在这一节中,将为大家介绍面向对象的编程方法,其实现途径就是使用类(class)和对象(object)。

在面向对象的编程中,我们主要处理的不是前面介绍的那样传统的一行行的代码,而是来模拟现实世界中的不同的事物,即对象。一个对象可以有属性和行为,也可以和其他对象相联系和发生相互作用。例如,一只小狗可以有属性(年龄、体重等)和行为(吠、跑、打滚等)。面向对象的编程方式基于以下四个核心特征:

  • 多态(Polymorphism)
  • 继承(Inheritance)
  • 抽象(Abstraction)
  • 封装(Encapsulation)

核心概念

这里我们简要介绍一下上述这些重要的概念。

  • :类是创建对象的蓝图,我们可以把类理解为一个生产对象的工厂。类提供了创建对象的模板,通过方法来指定对象的行为,通过属性来指定其状态。
  • 对象:对象就是类的一个实例,一个对象可以有状态(属性)和行为。
  • 继承:在面向对象的编程中,子类可以从父类继承属性和方法。继承可以分为单一继承和多重继承。单一继承是指一个子类只继承一个父类,而多重继承指一个子类可以继承多个父类。
  • 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用基类的引用指向子类的对象。在这里我们可以理解为,同一个方法有不同的输出。
  • 抽象:这个不知道具体该怎么表达,直接引用一段"Learn Python in 7 Days (Mohit, Bhaskar N. Das)"里的原文“Here, we hide the necessary details and are only interested in showing the relevant details to the other intended user. Here, by other intended user we mean another software application, or another class, or other client who will be the end users of the program.”
  • 封装:封装是指将类的方法的细节进行隐藏,对外界来说,类就像一个“黑盒子”。封装是保证软件部件具有优良的模块性的基础,封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。

本节的部分内容直接参考了菜鸟教程

创建类

Python中创建类的语法非常简单:

class <class name >(<parent class name>):
'''statements'''
<method definition-1>
<method definition-n>

空类

空类是最简单的类,我们在这里创建一个空类:

class Student():
    '''An empty class.'''
    pass

Student
<class __main__.Student at 0x7f1e7803c668>

上面这个类中没有任何内容,用pass关键字来起到一个占位的作用,Student后面的括号也可以省略,第一行三撇号'''里面的语句为类的文档(可选)。类可以看作是创建实例的蓝图,即使是空类也可以。实际上,空类在我们后面做科研数据处理的过程中有很重要的应用,它可以作为将要分析的数据的一个容器,我们后面会讲到。下面我们就用空类Student来创建实例:

student1 = Student()
student2 = Student()
print student1
print student2
<__main__.Student instance at 0x7fe3507474d0>
<__main__.Student instance at 0x7fe3507472d8>

从上面的输出结果我们看到所创建的两个实例(instance),而且这两个实例在内存中存储在不同的地址上。

实例变量

实例变量是指某个具体实例(对象)特有的变量。例如,针对上面创建的空类的两个实例,我们可以分别赋予它们一些变量:

# define variables for L_obj1
student1.name = 'da zhuang'
student1.gender = 'male'
# define variables for L_obj2
student2.name = 'xiao hong'
student2.gender = 'female'
student2.age = 20

print student1.__dict__.keys()
print student2.__dict__.keys()
['gender', 'name']
['gender', 'age', 'name']

在上面的例子中,我们分别为student1student2两个实例定义了变量,其中student1有两个变量namegender,而student2还多了age变量。上面代码中的__dict__是Python类的内置属性,它是一个存储了类的所有方法或对象的所有属性键值对的字典。

虽然上面的例子显示了Python定义实例变量的灵活性,但是这种方式没有充分利用到面向对象编程的优势。实际上,我们可以在定义类的时候给定变量,从而使得所有的实例都同时具有这些变量,我们将在下面介绍。

类的变量和方法

__init__方法

__init__方法是类的一种特殊方法,被陈为类的构造函数或初始化方法,当创建这个类的实例时就会自动调用该方法。下面我们重新定义一下Student类,以演示__init__方法的使用:

class Student:
    '''所有学生的基类'''
    def __init__(self, name, gender, age):
        self.name = name
        self.gender = gender
        self.age = age

student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'famale', 20)

print student1.__dict__.keys()
print student2.__dict__.keys()
['gender', 'age', 'name']
['gender', 'age', 'name']

在上面的代码中,我们利用__init__方法定义了类的三个变量namegenderage,这些变量被自动应用于类的所有实例。self代表类的实例,是在定义类方法时必须的,虽然在调用时不必为self传入任何参数。

当然,与普通的函数类似,我们也可以为这些变量指定初始值,例如:

class Student:
    '''所有学生的基类'''
    def __init__(self, name, gender, age=23):
        self.name = name
        self.gender = gender
        self.age = age        

student1 = Student('da zhuang', 'male')
print student1.__dict__
{'gender': 'male', 'age': 23, 'name': 'da zhuang'}

自定义方法

类的方法与普通函数类似,只有一个特别之处,即需要一个额外的第一个参数,就是我们上面讲的self参数(按惯例使用这个名称,但不是必须的,可以随意指定其他名称)。例如,我们将上述Student类加入一个自定义的方法:

class Student:
    '''所有学生的基类'''
    def __init__(self, name, gender, age=23):
        self.name = name
        self.gender = gender
        self.age = age
    
    def report(self):
        print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)

student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'female', 20)

student1.report()
student2.report()
Da Zhuang is a 21 years old male student.
Xiao Hong is a 20 years old female student.

类的属性

上面类中定义的类变量即为类的实例的属性,可以通过点号.访问,我们上面的例子中已经用到了。这里介绍一下类的内置属性,一共有一下几个:

  • __dict__: 类的属性(包含一个字典,由类的数据属性组成)
  • __doc__:类的文档字符串
  • __name__: 类名
  • __module__: 类定义所在的模块(类的全名是'main.className',如果类位于一个导入模块mymod中,那么className.module 等于 mymod)
  • __bases__: 类的所有父类构成元素(包含了一个由所有父类组成的元组)

例如:

print "Student.__doc__:", Student.__doc__
print "Student.__name__:", Student.__name__
print "Student.__module__:", Student.__module__
print "Student.__bases__:", Student.__bases__
print "Student.__dict__:", Student.__dict__
Student.__doc__: 所有学生的基类
Student.__name__: Student
Student.__module__: __main__
Student.__bases__: ()
Student.__dict__: {'report': <function report at 0x7f1e70f29aa0>, '__module__': '__main__', '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\xad\xa6\xe7\x94\x9f\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': <function __init__ at 0x7f1e70f29938>}

注意对类调用__dict__属性和对类的实例调用__dict__效果是不一样的。对类调用时会列出类的方法和属性,但是对实例调用时,只会列出实例的属性,而不会列出方法。

print Student.__dict__
print student1.__dict__
{'report': <function report at 0x7f1e70f29aa0>, '__module__': '__main__', '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\xad\xa6\xe7\x94\x9f\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': <function __init__ at 0x7f1e70f29938>}
{'gender': 'male', 'age': 21, 'name': 'da zhuang'}

私有属性和方法

类的私有属性

__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

类的私有方法

__private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用self.__private_methods

单下划线、双下划线、头尾双下划线说明:

  • __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似__init__()之类的。
  • _foo: 以单下划线开头的表示的是protected类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于from module import *
  • __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

类的继承

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。通过继承创建的新类称为子类派生类,被继承的类称为基类父类超类。通过继承,子类自动继承父类的所有方法和属性。创建子类的语法如下:

class DerivedClassName(BaseCalssName):
    <statement-1>
    ...
    ...
    <statement-N>

单一继承

只从一个父类来继承,例如:

class Student:
    '''所有学生的基类'''
    def __init__(self, name, gender, age=23):
        self.name = name
        self.gender = gender
        self.age = age
    
    def report(self):
        print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)
        
class Boy(Student):
    '''Boy类继承自Student类'''
    def play(self):
        print '%s is playing soccer.' % self.name.title()
boy1 = Boy('da zhuang', 'male', 12)
boy1.report()
boy1.play()
Da Zhuang is a 12 years old male student.
Da Zhuang is playing soccer.

多重继承

也可以从多个父类继承,例如:

class A:
    def a_print(self):
        print 'This is class A.'

class B:
    def b_print(self):
        print 'This is class B.'
        
class C(A, B):
    def c_print(self):
        print 'This is class C.'


c = C()
c.a_print()
c.b_print()
c.c_print()
This is class A.
This is class B.
This is class C.

方法重写

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法:

class Parent:        # 定义父类
    def myMethod(self):
        print '调用父类方法'
    
class Child(Parent): # 定义子类
    def myMethod(self):
        print '调用子类方法'
    
c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法
调用子类方法

运算符重载(operator overloading)

运算符重载有时被称为魔术方法(magic methods),指的是利用一些特殊方法来改变Python运算符的行为。特殊方法由连续的两组双下划线(__)包围,有的人也将其称为dunder方法。例如,运算符加号(+),对不同的操作变量有不同的行为:

4 + 6, 'M' + 'R'
(10, 'MR')

从输出结果我们看到了加号运算的不同行为:对于数字,加号代表两数相加;而对于字符串,加号代表连接两个字符串。根据操作对象的不同,加号运算有着不同的行为,实际上,在后台运行的就是特殊方法。对两个整数相加,+调用的是int.__add__()方法;而对于字符串,则调用str.__add__()方法。例如上面代码也可以写成:

int.__add__(4, 6), str.__add__('M', 'R')
(10, 'MR')

因此,我们可以用__add__()方法自定义加法运算。下面我们将前面定义的Student类的两个实例进行相加,看看会出现什么结果?

class Student:
    '''所有学生的基类'''
    def __init__(self, name, gender, age=23):
        self.name = name
        self.gender = gender
        self.age = age
    
    def report(self):
        print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)

student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'female', 20)

student1 + student2
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-44-c9dbee72f74f> in <module>()
     12 student2 = Student('xiao hong', 'female', 20)
     13 
---> 14 student1 + student2


TypeError: unsupported operand type(s) for +: 'instance' and 'instance'

我们看到错误提示unsupported operand type(s) for+: 'instance' and 'instance',即加号运算符不支持两个实例来进行运算。这时,我们就可以在类中对加号运算符进行重载,以使其支持两个实例的相加。比如,我们可以定义两个实例相加的运算为两个实例中age属性的相加。

class Student:
    '''所有学生的基类'''
    def __init__(self, name, gender, age=23):
        self.name = name
        self.gender = gender
        self.age = age
    
    def __add__(self, other):
        '''重载加法运算'''
        return self.age + other.age
    
    def report(self):
        print "%s is a %d years old %s student." % (self.name.title(), self.age, self.gender)

student1 = Student('da zhuang', 'male', 21)
student2 = Student('xiao hong', 'female', 20)

student1 + student2
41

从输出可见,加号实现了对两个实例的相加。

以上是关于类的一些基本内容,当然类的应用还有很多知识需要学习,这是面向对象编程的核心,如果大家在以后的使用中遇到问题,可以从网上寻找对应的解决方法。


返回目录

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,924评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,781评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,813评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,264评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,273评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,383评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,800评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,482评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,673评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,497评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,545评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,240评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,802评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,866评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,101评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,673评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,245评论 2 341

推荐阅读更多精彩内容