虽然 Python 可以写函数式编程,但是本质上是一门面对对象编程语言 (object-oriented programming language),简称 oop。面对对象编程是把代码包装成一个对象 Object, 然后做对象与对象之间的交互。这么做的好处是可以把复杂的代码逻辑嵌入对象内部 (Abstraction),而调用对象的时候仅需要了解其界面 (Interface)。

这篇教程有一定的难度,所以需要先看完之前的三篇教程:多多教Python:Python 基本功: 3. 数据类型​zhuanlan.zhihu.com多多教Python:Python 基本功: 7. 介绍函数​zhuanlan.zhihu.com多多教Python:Python 基本功: 9. 是非逻辑​zhuanlan.zhihu.com

教程需求:

类 Class

Python 中通过类(Class)来定义一对象,Object。因为对象是用来包装代码的,所以类是一个外部看上去简单,但是内部很复杂的结构(就像教程标图中的金字塔)。一个类有自己的语境(Context),属于自己的属性(Property), 属于自己的成员(Member), 属于自己的方法(Methods),和属于自己的界面(Interface)。下面我们写代码来创建一个简单的类:

import numpy as np

class Sample:

max_sample = 100 # 类属性

def __init__(self, name='weight sample'):

"""类的初始化,一般用来初始化类成员, 不一定需要重写。self 关键字是指被创建后的类自己,每一个 self 代表一个被创建的类。"""

self.name = name # 创建类成员

self.samples = [] # 创建类成员

return

def __repr__(self):

"""重写类的表达式,不一定需要重写。"""

return "My name is:" + str(self.name) + ", and I have " + str(len(self.samples)) + " samples"

def __getitem__(self, index):

"""重写并且添加原本不支持的类索引,不一定需要重写。"""

try:

return self.samples[index]

except IndexError as error:

print("No sample with index: " + str(index) + ", err: " + str(error))

return None

@property

def sample_size(self):

"""类的方法变成员,通过 @property变成员的类方法不能传除了 self 的参数"""

return len(self.samples)

@classmethod

def calculate_average(cls, samples):

"""静态类方法,不需要创建类就可以调用这里注意不是 self 参数,而是 cls 参数,cls 参数直接通过类名字 Sample 就可以调用比如: Sample.calculate_average()"""

return np.mean(samples)

def collect_samples(self, samples):

"""动态类方法"""

for sample in samples:

if self.sample_size >= self.max_sample:

print("Sample size larger than: " + str(self.max_sample))

break

self.samples.append(sample)

return

def _analyze(self):

"""隐藏动态类方法不应该从外部调用的类方法,下划线开头的名称表示专门用于类的内部调用但是 Python 不阻止你从外部调用"""

average_weight = self.calculate_average(self.samples)

std_weight = np.std(self.samples)

return self.sample_size, average_weight, std_weight

def get_analytics_report(self):

"""动态类方法"""

results = self._analyze()

print("Number of samples: " + str(results[0]))

print("Average weight: " + "{0:.2f}".format(results[1]))

print("Standard deviation of weight: " + "{0:.2f}".format(results[2]))

return

def __del__(self):

"""释放类, 不一定要重写"""

print(self.name + " with sample size " + str(self.sample_size) + " is being released")

读完代码,注意其中的注释,然后我们来介绍一下这个类里面的几个重要元素:类名 Class Name: 这里 Class 是创建类的关键字,后面跟随着类名,然后冒号之后就进入类的语境。

类属性 Class Property: 这里进入类之后先定义了类属性,max sample, 这里类属性可以被初始化,并且可以在类不被创建的时候直接调用如:Sample.max_sample。

类成员 Class Member: 类成员通常在类的初始化中定义,并且赋予一个默认数值。当然也有在其他地方新建,定义类成员的,但是不建议这么做,而且 IDE 像 PyCharm 会警告。这里的类成员包括了一个字符串 name,和一个空列表 samples。

类方法 Class Methods: 类方法就是在类中定义并且可以被调用的函数。这里定义类方法的关键字就是 def ,当然在代码的注释中我们看到了不同的类方法种类,包括了:重写的类方法:在创建类的时候,Python 已经为你创建的类自动设置了一些类的方法,这些类方法是以 “__” 开头和 "__" 结尾的名字。这里重写的时候就覆盖了默认的类方法,加入了新建类的内容,比如上面例子的重写初始化,表达式,索引和释放。

动态类方法:动态类方法是完全新设计的函数,需要在类创建之后再调用,可以从外部或者内部调用。

静态类方法:静态类方法也是新设计的函数,但是可以在类创建之前使用,使用方法将在后文提到。

隐藏的类方法:Python 中不会强制性的隐藏类方法,但是以下划线开头的一般不直接从外部调用。这么设计你的类方法名字也可以帮助你避免一些错误。

变成员的类方法:@property这个关键字是把类方法转换成类成员,这样在外部调用某一个类成员的时候,实际上是在调用一个方法,但是这个方法不能加入其他参数。类界面 Class Interface: 这里的类界面包括了所有可以从外部调用的类方法,类成员的界面,并且通过重写 "__getitem__" 添加了索引界面。

这是一个针对于 专业性/挑战性学习的教程,虽然这是在基本功系列里写的第一个类,但是结构已经比较复杂了。下面我们来和这个类做一些互动,来认识一下这个新建的类可以通过其界面做哪些事情:

In [2]:Sample.max_sample

Out[2]:100

In [3]:sample = Sample(name='weight sample')

In [4]:sample.sample_size

Out[4]:0

In [5]:sample.collect_samples([120, 110, 140, 130, 200, 170, 166])

In [6]:print(sample)

Out [6]:My name is:weight sample, and I have 7 samples

In [7]:sample[1]

Out[7]:110

In [8]:sample.get_analytics_report()

Out [8]:Number of samples: 7

Average weight: 148.00

Standard deviation of weight: 29.59

In [9]:sample.calculate_average([100, 200, 300])

Out[9]:200.0

In [10]:Sample.calculate_average([100, 200, 300])

Out[10]:200.0

In [11]:sample._analyze()

Out[11]:(7, 148.0, 29.587642207999128)

In [12]:del sample

Out [12]:weight sample with sample size 7 is being released

同样的,我们来一行行解释:在 Jupyter 笔记本的第一个单元格定义类 Sample,如何定义参见上一段代码。

在未创建类的情况下,直接访问其属性 max sample,得到100。

创建一个类 Sample, 名字叫 'weight sample',然后赋予一个新变量 sample。

查看变成员 sample size 的类方法,得到 0 个sample。

调用动态类方法 collect samples,传入一个列表,7个 samples。

打印类,这里类会调用我们重写的 "__repr__",返回我们定义的字符串,确认了名字和样本数量。

通过索引,直接在类中查找第二个(第一个是0) sample 样本,返回 110。

调用动态类方法获得样本分析报告。

调用静态类方法计算一个列表的平均数,注意这里是通过已经创建的类 sample 调用。

这里通过定义但是未创建的类 Sample 来直接调用静态方法,返回结果一样。

这里调用了类的隐藏方法,仍然可以获得结果,但是不建议这么做,因为内部使用的方法一般返回的数据结构不明确,容易出错,就像这里返回的是一个 Tuple 类, 参考: 多多教Python:Python 基本功: 3. 数据类型。

内存释放创建的 sample 类,Python 调用了 重写的 "__del__" 方法,并且打印出了效果。

继承 Inheritance

一个类不仅可以从头开始写,也可以从另外一个类直接进行拓展或者重写,这就是继承。继承避免你去写重复代码,而且在一些 Python 的模块例如 多线程 (Threading),你必须通过继承的方法来实现模块的运用。子类继承父类的所有,并且进行拓展或者重写覆盖

上图就表达了 Python 中比较简单的继承关系,你可以无限的往下继承,或者扩展子类,但是除非是必要的我不建议这么做,因为这样会导致继承关系过于复杂,限制了子类的延展性。下面我们来继续上文写一个继承类的例子:

# In [13]:

class RemoveLastSample(Sample):

def _analyze(self):

"""重写内部调用类方法,在分析的时候去除最后一个样本元素。"""

samples_to_analyze = self.samples[0:-1]

average_weight = self.calculate_average(samples_to_analyze)

std_weight = np.std(samples_to_analyze)

return max(0, self.sample_size -1), average_weight, std_weight

这个 RemoveLastSample 类继承了 Sample 类,通过第一行语法,然后其内部只是重写了一个类方法 _analyze。注意既然是重写,那父类 (Sample Class) 的 _analyze 界面要保持一致。随后我们进行内部功能的实现,就像注释里所说的,我们在分析前去除了最后一个样本。

# In [14]:

class PlusOneSample(Sample):

def _analyze(self):

"""重写内部调用类方法,在分析的时候对每个样本的数值+1"""

samples_to_analyze = [sample + 1 for sample in self.samples]

average_weight = self.calculate_average(samples_to_analyze)

std_weight = np.std(samples_to_analyze)

return self.sample_size, average_weight, std_weight

这里 PlusOneSample 类是另一个继承了 Sample 的子类。内部的实现方法是分析时对每一个样本的数值+1,注意这里 "[sample + 1 for sample in self.samples]" 用到了 Python 的生成器语法,我们在之后的教程会讲到。现在我们对两个子类进行互动:

In [15]:remove_l_sample = RemoveLastSample(name='remove last weight sample')

In [16]:remove_l_sample.collect_samples([120, 110, 140, 130, 200, 170, 166])

In [17]:remove_l_sample.get_analytics_report()

Out [17]:Number of samples: 6

Average weight: 145.00

Standard deviation of weight: 30.96

In [18]:plus_1_sample = PlusOneSample(name='plus one weight sample')

In [19]:plus_1_sample.collect_samples([120, 110, 140, 130, 200, 170, 166])

In [20]:plus_1_sample.get_analytics_report()

Out [20]:Number of samples: 7

Average weight: 149.00

Standard deviation of weight: 29.59

我们分别创建了两个子类,并且传入了一样的样本。在调用了 "get_analytics_report()" 之后,我们发现了两个不同的分析结果,我们发现在实现相似的功能时候,利用继承可以省去许多多余的代码,使得代码结构更加简洁明了。

小结:

在 Python 中,绝大部分功能不需要定义类来实现,通过函数式编程或者脚本式就够了。但是当做到大型项目,或者调用模块 (多线程 Threading 模块)时,你不得不通过类来实现。类可以帮助你的代码更加合理有效,但是过分的使用类来封装,继承来延展就会让结构变得过于冗长复杂。所以这里有一个权衡 Tradeoff:不创建类,不用继承 --> 代码没有结构,可随意修改容易出错。

创建太多类,使用太多继承 --> 代码结构复杂,界面刻板缺乏延展性。

下面来两个教程外部的链接,有兴趣的小伙伴可以继续深入,当然后面的教程都会学到:Python中的多态与虚函数 - Tony_Wong的专栏 - CSDN博客​blog.csdn.net

Python 多线程模块,利用到类的继承:python 多线程编程之threading模块(Thread类)创建线程的三种方法 - 风雨一肩挑 - 博客园​www.cnblogs.com

python释放类对象_Python 基本功: 10. 面对对象-类 Class相关推荐

  1. python多程优化_Python 基本功: 13. 多线程运算提速

    小编在前两天开通了一个 Python 金融的专栏,顺便用 TuShare 下载了几只 A股的数据,有兴趣的小伙伴可以去看一下: 多多教Python:Python 金融: TuShare API 获取股 ...

  2. python的类和对象_Python面向对象之类和对象实例详解

    本文实例讲述了Python面向对象之类和对象.分享给大家供大家参考,具体如下: 类和对象(1) 对象是什么? 对象=属性(静态)+方法(动态): 属性一般是一个个变量:方法是一个个函数: #类的属性 ...

  3. python源码剖析_Python源码剖析 - 对象初探

    01 前言 对象是 python 中最核心的一个概念,在python的世界中,一切都是对象,整数.字符串.甚至类型.整数类型.字符串类型,都是对象. 02 什么是PyObject Python 中凡事 ...

  4. Python进阶-----面对对象4.0(面对对象三大特征之--继承)

    目录 前言: Python的继承简介 1.什么是继承 2.继承的好处 3.object类 继承的相关用法 1.继承的定义与法则 2.对继承的重写 3.(单继承)多层继承 4.多继承 5.多继承重写时调 ...

  5. python哪些是可变对象_python的不可变对象与可变对象及其妙用与坑

    先上图. 图里,分别用三个整数进行了验证.可以发现当a和b值相同时,a与b地址也一致.改变a的值,a的地址也跟着改变了. 原因 python的宗旨之一,万物皆对象.(单身狗狂喜) 而对象又被分为可变对 ...

  6. python if高级用法_Python高级用法总结--元类

    type() 动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义,而是运行时动态创建的. 比方说我们要定义一个 Hello 的 class ,就写一个hello.py 模块: class ...

  7. python怎么创建变量_Python中通过函数对象创建全局变量

    标签: 先看下面这段代码,显然无法work. 因为代码试图在TestVariableScope()中引用一个没有被定义的变量a.所以必须报错,如下图-1. 不过如果你将第2行代码注释掉.代码就能跑通了 ...

  8. python中全部注释_python中的所有对象(学习注释1),Python,一切,皆,笔记

    前言 以前自学Python的时候很多都是大概浏览一下,进阶教程也有看过一些但是都是粗浅略过由于当时也是刚刚入门懵懵懂懂的.现在在运用一些框架的时候,只知道依葫芦画瓢却不知其意,现在准备自己在回顾一下P ...

  9. python中不可变对象_Python中的可变对象与不可变对象、浅拷贝与深拷贝

    Python中的对象分为可变与不可变,有必要了解一下,这会影响到python对象的赋值与拷贝.而拷贝也有深浅之别. 不可变对象 简单说就是某个对象存放在内存中,这块内存中的值是不能改变的,变量指向这块 ...

最新文章

  1. 简述Linux C下线程池的使用
  2. 进程间通信 - 动态链接库实现
  3. mysql mydump还原_用mydump对所有数据库进行备份,还原具体案例
  4. hadoop(05)、使用Eclipse连接远程Hadoop集群
  5. MongoDB 空指针引用拒绝服务漏洞
  6. LeetCode算法题-Design LinkedList(Java实现)
  7. VB选择文件夹并取文件夹名
  8. iOS开发之网络编程--使用NSURLConnection实现大文件断点续传下载+使用输出流代替文件句柄...
  9. Python编程实现点到直线距离计算
  10. 4、配置虚拟机IP地址
  11. 中国麻将:世界上最早的区块链项目
  12. Windows新建文本文档的快捷键设置
  13. Exception | 优雅的输出Exception异常信息
  14. linux环境下如何安装DHCP服务器及示例
  15. 小米加密兔正式内测上线,网易星球莱茨狗又多了新对手
  16. 骨龄预测代码学习(二)
  17. 【转载】如何用Python发送Email邮件?
  18. linux加载虚拟sriov网卡,网卡直通SR-IOV技术
  19. 服务器程序框架 - Linux C++网络编程(十三)
  20. go-ethereum相关

热门文章

  1. 将某表一行数据的某些字段插入到该表
  2. 很安逸的离线API文档查询工具Dash和Zeal
  3. C#------编码规范
  4. Web前端之html_day2
  5. 六 Lync Server 2013 部署指南-OWA服务器部署
  6. 在 App Store 三年學到的 13 件事(下)
  7. windows 2003 活动目录的更改域名后缀技巧
  8. 保护您的IE浏览器安全
  9. Java基础篇:面向对象
  10. 程序员天花板:产后半年加薪升职,这位程序员妈妈绝了!