python面向对象

摘要

  • 面向过程:根据业务逻辑从上到下写垒代码
  • 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可
  • 面向对象:对函数进行分类和封装,让开发“更快更好更强…”
  • 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用
  • 类是一个模板,模板中包装了多个“函数”供使用(可以讲多函数中公用的变量封装到对象中)
  • 对象,根据模板创建的实例(即:对象),实例用于调用被包装在类中的函数
  • 面向对象三大特性:封装、继承和多态

面向过程 VS 面向对象

编程范式

编程是 程序员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 , 一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。 不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。 两种最重要的编程范式分别是面向过程编程和面向对象编程。

面向过程编程(Procedural Programming)

Procedural programming uses a list of instructions to tell the computer what to do step-by-step.
面向过程编程依赖 - 你猜到了- procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。

举个典型的面向过程的例子, 数据库备份, 分三步,连接数据库,备份数据库,测试备份文件可用性。

这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改, 举个例子,如果程序开头你设置了一个变量值 为1 , 但如果其它子过程依赖这个值 为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程 , 那就会发生一连串的影响,随着程序越来越大, 这种编程方式的维护难度会越来越高。
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。

面向过程的程序设计:核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么……面向过程的设计就好比精心设计好一条流水线,是一种机械式的思维方式。

  • 优点是:复杂度的问题流程化,进而简单化(一个复杂的问题,分成一个个小的步骤去实现,实现小的步骤将会非常简单)
  • 缺点是:一套流水线或者流程就是用来解决一个问题,生产汽水的流水线无法生产汽车,即便是能,也得是大改,改一个组件,牵一发而动全身。
  • 应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等

面向对象编程

面向对象的三大特性是指:封装、继承和多态。

OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

面向对象的程序设计:核心是对象二字

  • 优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
  • 缺点:
    • 编程的复杂度远高于面向过程,不了解面向对象而立即上手基于它设计程序,极容易出现过度设计的问题。一些扩展性要求低的场景使用面向对象会徒增编程难度,比如管理linux系统的shell脚本就不适合用面向对象去设计,面向过程反而更加适合。
    • 无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法准确地预测最终结果。
  • 应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方

面向对象的几个核心特性如下

Class 类

一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法

类即类别、种类,是面向对象设计最重要的概念,对象是特征与技能的结合体,而类则是一系列对象相似的特征与技能的结合体

  • 在现实世界中:先有对象,再有类
  • 在程序中:务必保证先定义类,后产生对象

这与函数的使用是类似的,先定义函数,后调用函数,类也是一样的,在程序中需要先定义类,后调用类

不一样的是,调用函数会执行函数体代码返回的是函数体执行的结果,而调用类会产生对象,返回的是对象

属性查找

类有两种属性:数据属性和函数属性

  • 类的数据属性是所有对象共享的
  • 类的函数属性是绑定给对象用的

类的工作原理

  • 定义,类定义之后加载到内存中,类的名字执行内存中的地址
  • 实例化
    • 类实例化赋值给一个变量的过程实际是
    • 将新定义的变量名传给类(self接收)
    • 在内存中开辟内存,变量名执行内存地址(self也执行了这个内存地址)
    • 给实例变量添加静态属性
  • 新变量通过类实例化之后变成对象

语法

1
2
3
4
5
6
7
class Role(object):
classname = "role" # 类变量
def __init__(self,name):
self.name = name # 实例变量,静态方法,属性

def Echo(self): # 类的功能,动态方法
print("self.name")
  • 方法
  • 私有方法,私有属性
    • __xxx
  • 类变量
    • 共有的属性
    • 节省开销
  • init
    • 构造函数
    • 实例化的时候类的初始化工作
  • self
    • 属性
    • 实例变量
    • 作用域是实例本身
  • del
    • 析构函数
    • 在实例释放、销毁的时候自动执行
    • 通常用于做一些收尾工作
    • 关闭数据库连接,关闭打开的临时文件
    • del r1
    • 默认有,写了相当于重构

特性

  • class
    • 类实例化生成一个对象
  • object
    • 由类实例化而来
  • 封装
    • 隐藏实现细节
    • 代码模块化
  • 继承
    • 扩展已存在的代码
    • A.init(self)
    • super(B,self).init() 新式类写法
    • 多继承
  • 多态
    • 接口重用
    • 允许将子类类型的指针赋值给父类类型的指针
    • python不直接支持多态,可以间接实现

Object 对象

一个对象即是一个类的实例化后实例,一个类必须经过实例化后方可在程序中调用,一个类可以实例化多个对象,每个对象亦可以有不同的属性,就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同

Encapsulation 封装

在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法

封装是面向对象的特征之一,是对象和类概念的主要特性。

封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

Inheritance 继承

一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承

面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

  • 通过继承创建的新类称为“子类”或“派生类”。
  • 被继承的类称为“基类”、“父类”或“超类”。
  • 继承的过程,就是从一般到特殊的过程。
  • 要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。

在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。

继承概念的实现方式主要有2类:实现继承、接口继承。

  • 实现继承是指使用基类的属性和方法而无需额外编码的能力;
  • 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法);

在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。

抽象类仅定义将由子类创建的一般属性和方法。

OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。

Polymorphism 多态

多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。

编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。

对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。

多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定

多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。

Pyhon 很多语法都是支持多态的,比如 len(),sorted(), 你给len传字符串就返回字符串的长度,传列表就返回列表长度。

面向对象编程(Object-Oriented Programming )介绍

对于编程语言的初学者来讲,OOP不是一个很容易理解的编程方式,大家虽然都按老师讲的都知道OOP的三大特性是继承、封装、多态,并且大家也都知道了如何定义类、方法等面向对象的常用语法,但是一到真正写程序的时候,还是很多人喜欢用函数式编程来写代码,特别是初学者,很容易陷入一个窘境就是“我知道面向对象,我也会写类,但我依然没发现在使用了面向对象后,对我们的程序开发效率或其它方面带来什么好处,因为我使用函数编程就可以减少重复代码并做到程序可扩展了,为啥子还用面向对象?”。 对于此,我个人觉得原因应该还是因为你没有充分了解到面向对象能带来的好处,今天我就写一篇关于面向对象的入门文章,希望能帮大家更好的理解和使用面向对象编程。

无论用什么形式来编程,我们都要明确记住以下原则:

  • 写重复代码是非常不好的低级行为
  • 你写的代码需要经常变更

开发正规的程序跟那种写个运行一次就扔了的小脚本一个很大不同就是,你的代码总是需要不断的更改,不是修改bug就是添加新功能等,所以为了日后方便程序的修改及扩展,你写的代码一定要遵循易读、易改的原则(专业数据叫可读性好、易扩展)。

如果你把一段同样的代码复制、粘贴到了程序的多个地方以实现在程序的各个地方调用 这个功能,那日后你再对这个功能进行修改时,就需要把程序里多个地方都改一遍,这种写程序的方式是有问题的,因为如果你不小心漏掉了一个地方没改,那可能会导致整个程序的运行都 出问题。 因此我们知道 在开发中一定要努力避免写重复的代码,否则就相当于给自己再挖坑。

还好,函数的出现就能帮我们轻松的解决重复代码的问题,对于需要重复调用的功能,只需要把它写成一个函数,然后在程序的各个地方直接调用这个函数名就好了,并且当需要修改这个功能时,只需改函数代码,然后整个程序就都更新了。

其实OOP编程的主要作用也是使你的代码修改和扩展变的更容易,那么小白要问了,既然函数都能实现这个需求了,还要OOP干毛线用呢? 呵呵,说这话就像,古时候,人们打仗杀人都用刀,后来出来了枪,它的主要功能跟刀一样,也是杀人,然后小白就问,既然刀能杀人了,那还要枪干毛线,哈哈,显而易见,因为枪能更好更快更容易的杀人。函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。

Python新式类与经典类的区别

  • 主要是继承顺序
  • 广度优先还是深度优先,新式类广度优先
    • py3 都是广度优先
    • py2
      • 经典类 深度
      • 新式类 广度
      • 是否继承自object
  • 搜索树(树的遍历算法相关)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A:
def __init__(self):
self.n = 'A'

class B(A):
# def __init__(self):
# self.n = 'B'
pass

class C(A):
def __init__(self):
self.n = 'C'

class D(B,C):
# def __init__(self):
# self.n = 'D'
pass

obj = D()

print(obj.n)

经典类多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class D:

def bar(self):
print 'D.bar'


class C(D):

def bar(self):
print 'C.bar'


class B(D):

def bar(self):
print 'B.bar'


class A(B, C):

def bar(self):
print 'A.bar'

a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> D --> C
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()

新式类多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class D(object):

def bar(self):
print 'D.bar'


class C(D):

def bar(self):
print 'C.bar'


class B(D):

def bar(self):
print 'B.bar'


class A(B, C):

def bar(self):
print 'A.bar'

a = A()
# 执行bar方法时
# 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
# 所以,查找顺序:A --> B --> C --> D
# 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
a.bar()
  • 经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
  • 新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
  • 注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了

抽象接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import abc

class Alert(object):
'''报警基类'''
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def send(self):
'''报警消息发送接口'''
pass



class MailAlert(Alert):
pass


m = MailAlert()
m.send()

python中关于OOP的常用术语

抽象/实现

抽象指对现实世界问题和实体的本质表现,行为和特征建模,建立一个相关的子集,可以用于 绘程序结构,从而实现这种模型。抽象不仅包括这种模型的数据属性,还定义了这些数据的接口。

对某种抽象的实现就是对此数据及与之相关接口的现实化(realization)。现实化这个过程对于客户 程序应当是透明而且无关的。

封装/接口

封装描述了对数据/信息进行隐藏的观念,它对数据属性提供接口和访问函数。通过任何客户端直接对数据的访问,无视接口,与封装性都是背道而驰的,除非程序员允许这些操作。作为实现的 一部分,客户端根本就不需要知道在封装之后,数据属性是如何组织的。在Python中,所有的类属性都是公开的,但名字可能被“混淆”了,以阻止未经授权的访问,但仅此而已,再没有其他预防措施了。这就需要在设计时,对数据提供相应的接口,以免客户程序通过不规范的操作来存取封装的数据属性。

注意:封装绝不是等于“把不想让别人看到、以后可能修改的东西用private隐藏起来”

真正的封装是,经过深入的思考,做出良好的抽象,给出“完整且最小”的接口,并使得内部细节可以对外透明

(注意:对外透明的意思是,外部调用者可以顺利的得到自己想要的任何功能,完全意识不到内部细节的存在)

合成

合成扩充了对类的 述,使得多个不同的类合成为一个大的类,来解决现实问题。合成 述了 一个异常复杂的系统,比如一个类由其它类组成,更小的组件也可能是其它的类,数据属性及行为, 所有这些合在一起,彼此是“有一个”的关系。

派生/继承/继承结构

派生描述了子类衍生出新的特性,新类保留已存类类型中所有需要的数据和行为,但允许修改或者其它的自定义操作,都不会修改原类的定义。
继承描述了子类属性从祖先类继承这样一种方式
继承结构表示多“代”派生,可以述成一个“族谱”,连续的子类,与祖先类都有关系。

泛化/特化

基于继承
泛化表示所有子类与其父类及祖先类有一样的特点。
特化描述所有子类的自定义,也就是,什么属性让它与其祖先类不同。

多态与多态性

多态指的是同一种事物的多种状态:水这种事物有多种不同的状态:冰,水蒸气

多态性的概念指出了对象如何通过他们共同的属性和动作来操作及访问,而不需考虑他们具体的类。

冰,水蒸气,都继承于水,它们都有一个同名的方法就是变成云,但是冰.变云(),与水蒸气.变云()是截然不同的过程,虽然调用的方法都一样

自省/反射

自省也称作反射,这个性质展示了某对象是如何在运行期取得自身信息的。如果传一个对象给你,你可以查出它有什么能力,这是一项强大的特性。如果Python不支持某种形式的自省功能,dir和type内建函数,将很难正常工作。还有那些特殊属性,像dict,namedoc

面向对象的软件开发

很多人在学完了python的class机制之后,遇到一个生产中的问题,还是会懵逼,这其实太正常了,因为任何程序的开发都是先设计后编程,python的class机制只不过是一种编程方式,如果你硬要拿着class去和你的问题死磕,变得更加懵逼都是分分钟的事,在以前,软件的开发相对简单,从任务的分析到编写程序,再到程序的调试,可以由一个人或一个小组去完成。但是随着软件规模的迅速增大,软件任意面临的问题十分复杂,需要考虑的因素太多,在一个软件中所产生的错误和隐藏的错误、未知的错误可能达到惊人的程度,这也不是在设计阶段就完全解决的。

所以软件的开发其实一整套规范,我们所学的只是其中的一小部分,一个完整的开发过程,需要明确每个阶段的任务,在保证一个阶段正确的前提下再进行下一个阶段的工作,称之为软件工程

面向对象的软件工程包括下面几个部:

面向对象分析(object oriented analysis ,OOA)

软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,贵南出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。

建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。

面向对象设计(object oriented design,OOD)

根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。

首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。

在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述

面向对象编程(object oriented programming,OOP)

根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python

面向对象测试(object oriented test,OOT)

在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。

面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。

面向对象维护(object oriendted soft maintenance,OOSM)

正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。

由于使用了面向对象的方法开发程序,使用程序的维护比较容易。

因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。

在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。

现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。

面向对象进阶

字段

字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,

  • 普通字段属于对象
  • 静态字段属于类
  • 静态字段在内存中只保存一份
  • 普通字段在每个对象中都要保存一份

应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段

方法

方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。

  • 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
  • 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
  • 静态方法:由类调用;无默认参数;

  • 相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。

  • 不同点:方法调用者不同、调用方法时自动传入的参数不同。

方法的定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Foo:

def __init__(self, name):
self.name = name

def ord_func(self):
""" 定义普通方法,至少有一个self参数 """

# print self.name
print '普通方法'

@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """

print '类方法'

@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""

print '静态方法'


# 调用普通方法
f = Foo()
f.ord_func()

# 调用类方法
Foo.class_func()

# 调用静态方法
Foo.static_func()

实例方法(普通方法)

  • 最常用的方法

静态方法

通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了,它与类唯一的关联就是需要通过类名来调用这个方法

  • @staticmethod
  • 不需要传self,可以不接收参数
  • 只是名义上归类管理
  • 实际上在静态方法里面访问不了类或者实例中的任何属性
  • 实际上可以说跟类没有紧密的关系了

类方法

  • @classmethod
  • 第一个参数为类本身
  • 只能访问类变量,不能访问实例变量
  • 类方法不能访问实例变量
  • 通过self访问的是类变量

属性

基本使用

  • 在普通方法的基础上添加@property装饰器
  • 定义时,属性仅有一个self参数
  • 把一个方法变成静态属性
  • 调用的时候不用加括号,不能传参数
  • 可以给属性赋值
  • 很多场景是不能简单通过 定义 静态属性来实现的
  • 属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
  • 属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
  • 属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回

定义方式

属性的定义有两种方式:

  • 装饰器 即:在方法上应用装饰器
  • 静态字段 即:在类中定义值为property对象的静态字段

装饰器方式:在类的普通方法上应用@property装饰器

经典类,具有一种@property装饰器

1
2
3
4
5
6
7
8
9
# ############### 定义 ###############    
class Goods:

@property
def price(self):
return "wupeiqi"
# ############### 调用 ###############
obj = Goods()
result = obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值

新式类,具有三种@property装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ############### 定义 ###############
class Goods(object):

@property
def price(self):
print '@property'

@price.setter
def price(self, value):
print '@price.setter'

@price.deleter
def price(self):
print '@price.deleter'

# ############### 调用 ###############
obj = Goods()

obj.price # 自动执行 @property 修饰的 price 方法,并获取方法的返回值

obj.price = 123 # 自动执行 @price.setter 修饰的 price 方法,并将 123 赋值给方法的参数

del obj.price # 自动执行 @price.deleter 修饰的 price 方法

注:经典类中的属性只有一种访问方式,其对应被 @property 修饰的方法

新式类中的属性有三种访问方式,并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法

静态字段方式,创建值为property对象的静态字段

当使用静态字段的方式创建属性时,经典类和新式类无区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Foo

def get_bar(self):
return 'wupeiqi'

# *必须两个参数
def set_bar(self, value):
return return 'set value' + value

def del_bar(self):
return 'wupeiqi'

BAR = property(get_bar, set_bar, del_bar, 'description...')

obj = Foo()

obj.BAR # 自动调用第一个参数中定义的方法:get_bar
obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入
del Foo.BAR # 自动调用第三个参数中定义的方法:del_bar方法
obj.BAE.__doc__ # 自动获取第四个参数中设置的值:description...

property的构造方法中有个四个参数

  • 第一个参数是方法名,调用 对象.属性 时自动触发执行方法
  • 第二个参数是方法名,调用 对象.属性 = XXX 时自动触发执行方法
  • 第三个参数是方法名,调用 del 对象.属性 时自动触发执行方法
  • 第四个参数是字符串,调用 对象.属性.doc ,此参数是该属性的描述信息

类成员的修饰符

  • 公有成员,在任何地方都能访问
  • 私有成员,只有在类的内部才能方法,私有成员命名时,前两个字符是下划线

  • 静态字段

    • 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
    • 私有静态字段:仅类内部可以访问;
  • 普通字段
    • 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
    • 私有普通字段:仅类内部可以访问;
    • 如果想要强制访问私有字段,可以通过 【对象._类名私有字段明 】访问(如:obj._Cfoo),不建议强制访问私有成员。
  • 方法、属性的访问于上述方式相似,即:私有成员只能在类内部使用
  • ps:非要访问私有属性的话,可以通过 对象._类__属性名

类的特殊成员、属性方法

  • doc : 类的描述信息
  • module : 表示当前操作的对象在那个模块
  • class : 表示当前操作的对象的类是什么
  • init : 构造函数,通过类创建对象时,自动触发执行
  • del : 析构函数,当对象在内存中被释放时,自动触发执行
    • 此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
  • call :实例化之后()调用 d()
  • dict : 查看类或者对象中的所有成员
    • 不实例化打印类里面的所有属性,不包括实例属性
    • 实例化之后打印实例化的对象的所有属性
  • str : 如果一个类中定义了str方法,那么在打印 对象 时,默认输出该方法的返回值
  • getitem : 用于索引操作,变成字典,获取
  • setitem : 用于索引操作,变成字典,设置
  • delitem : 用于索引操作,变成字典,删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Foo(object):

def __getitem__(self, key):
print('__getitem__',key)

def __setitem__(self, key, value):
print('__setitem__',key,value)

def __delitem__(self, key):
print('__delitem__',key)


obj = Foo()

result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'alex' # 自动触发执行 __setitem__
del obj['k1']
  • getslicesetslicedelslice : 该三个方法用于分片操作,如:列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Foo(object):

def __getslice__(self, i, j):
print '__getslice__',i,j

def __setslice__(self, i, j, sequence):
print '__setslice__',i,j

def __delslice__(self, i, j):
print '__delslice__',i,j

obj = Foo()

obj[-1:1] # 自动触发执行 __getslice__
obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__
del obj[0:2] # 自动触发执行 __delslice__
  • iter : 用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 iter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/env python
# -*- coding:utf-8 -*-

class Foo(object):

def __init__(self, sq):
self.sq = sq

def __iter__(self):
return iter(self.sq)

obj = Foo([11,22,33,44])

for i in obj:
print i

#############
#!/usr/bin/env python
# -*- coding:utf-8 -*-

obj = iter([11,22,33,44])

for i in obj:
print i
#############
# For循环语法内部

#!/usr/bin/env python
# -*- coding:utf-8 -*-

obj = iter([11,22,33,44])

while True:
val = obj.next()
print val
  • newmetaclass : 类也是对象,来自type类
    • metaclass : 表示这个类是由谁来实例化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MyType(type):
def __init__(self,*args,**kwargs):

print("Mytype __init__",*args,**kwargs)

def __call__(self, *args, **kwargs):
print("Mytype __call__", *args, **kwargs)
obj = self.__new__(self)
print("obj ",obj,*args, **kwargs)
print(self)
self.__init__(obj,*args, **kwargs)
return obj

def __new__(cls, *args, **kwargs):
print("Mytype __new__",*args,**kwargs)
return type.__new__(cls, *args, **kwargs)

print('here...')
class Foo(object,metaclass=MyType):


def __init__(self,name):
self.name = name

print("Foo __init__")

def __new__(cls, *args, **kwargs):
print("Foo __new__",cls, *args, **kwargs)
return object.__new__(cls)

f = Foo("Alex")
print("f",f)
print("fname",f.name)

创建类的方式

obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例

  • 普通方式
1
2
3
4

class Foo(object):
def __init__(self,name):
self.name = name
  • 特殊方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def func(self):
print("hello %s"%self.name)

def __init__(self,name,age):
self.name = name
self.age = age
Foo = type('Foo',(object,),{'func':func,'__init__':__init__})

f = Foo("jack",22)
f.func()

#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员

类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?

答:类中有一个属性 metaclass,其用来表示该类由 谁 来实例化创建,所以,我们可以为 metaclass 设置一个type类的派生类,从而查看 类 创建的过程。

自定义元类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class MyType(type):
def __init__(self,*args,**kwargs):

print("Mytype __init__",*args,**kwargs)

def __call__(self, *args, **kwargs):
print("Mytype __call__", *args, **kwargs)
obj = self.__new__(self)
print("obj ",obj,*args, **kwargs)
print(self)
self.__init__(obj,*args, **kwargs)
return obj

def __new__(cls, *args, **kwargs):
print("Mytype __new__",*args,**kwargs)
return type.__new__(cls, *args, **kwargs)

print('here...')
class Foo(object,metaclass=MyType):


def __init__(self,name):
self.name = name

print("Foo __init__")

def __new__(cls, *args, **kwargs):
print("Foo __new__",cls, *args, **kwargs)
return object.__new__(cls)

f = Foo("Alex")
print("f",f)
print("fname",f.name)

类的生成 调用 顺序依次是 new –> init –> call

metaclass 详解文章 得票最高那个答案写的非常好

反射

通过字符串映射或修改程序运行时的状态、属性、方法, 有以下4个方法

  • hasattr,判断一个对象是否有字符串对应的方法
  • getattr,根据字符串获取对象里面的对应的方法的内存地址
  • setattr,通过字符串设置新的属性,可以实现动态内存装配
  • delattr,删除属性

  • obj.name

  • obj.dict[‘name’]
  • getattr(obj, ‘name’)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Foo(object):

def __init__(self):
self.name = 'cass'

def func(self):
return 'func'

obj = Foo()

# #### 检查是否含有成员 ####
hasattr(obj, 'name')
hasattr(obj, 'func')

# #### 获取成员 ####
getattr(obj, 'name')
getattr(obj, 'func')

# 调用
getattr(obj, 'func')()

# #### 设置成员 ####
setattr(obj, 'age', 18)
setattr(obj, 'show', lambda num: num + 1)

# #### 删除成员 ####
delattr(obj, 'name')
delattr(obj, 'func')

动态导入模块

1
2
3
4
5
6
7
import importlib

#这是解释器自己内部用的
__import__('import_lib.metaclass')

#与上面这句效果一样,官方建议用这个
importlib.import_module('import_lib.metaclass')

其他

isinstance(obj, cls)

检查是否obj是否是类 cls 的对象

1
2
3
4
5
6
class Foo(object):
pass

obj = Foo()

isinstance(obj, Foo)

##issubclass(sub, super)

检查sub类是否是 super 类的派生类

1
2
3
4
5
6
7
class Foo(object):
pass

class Bar(Foo):
pass

issubclass(Bar, Foo)

单例模式

单例模式Singleton Pattern是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。

比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。如果在程序运行期间,有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象,而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。

在Python中,我们可以用多种方法来实现单例模式

  • 单利模式存在的目的是保证当前内存中仅存在单个实例,避免内存浪费!!!
  • 单例,顾名思义单个实例
  • 对于一般的实例,每个请求到来,都需要在内存里创建一个实例,再通过该实例执行指定的方法。

实现单例模式的几种方式

使用模块

其实,Python的模块就是天然的单例模式,因为模块在第一次导入时,会生成.pyc文件,当第二次导入时,就会直接加载.pyc文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做:

1
2
3
4
5
6
mysingleton.py

class Singleton(object):
def foo(self):
pass
singleton = Singleton()

将上面的代码保存在文件mysingleton.py中,要使用时,直接在其他文件中导入此文件中的对象,这个对象即是单例模式的对象

1
from a import singleton

使用装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def Singleton(cls):
_instance = {}

def _singleton(*args, **kargs):
if cls not in _instance:
_instance[cls] = cls(*args, **kargs)
return _instance[cls]

return _singleton


@Singleton
class A(object):
a = 1

def __init__(self, x=0):
self.x = x


a1 = A(2)
a2 = A(3)

使用类

1
2
3
4
5
6
7
8
9
10
class Singleton(object):

def __init__(self):
pass

@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance

一般情况,大家以为这样就完成了单例模式,但是这样当使用多线程时会存在问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Singleton(object):

def __init__(self):
import time
time.sleep(1)
# pass

@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance

import threading

def task(arg):
obj = Singleton.instance()
print(obj)

for i in range(10):
t = threading.Thread(target=task,args=[i,])
t.start()

按照以上方式创建的单例,无法支持多线程

解决办法:加锁!未加锁部分并发执行,加锁部分串行执行,速度降低,但是保证了数据安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import time
import threading
class Singleton(object):
_instance_lock = threading.Lock()

def __init__(self):
time.sleep(1)

@classmethod
def instance(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
with Singleton._instance_lock:
if not hasattr(Singleton, "_instance"):
Singleton._instance = Singleton(*args, **kwargs)
return Singleton._instance


def task(arg):
obj = Singleton.instance()
print(obj)
for i in range(10):
t = threading.Thread(target=task,args=[i,])
t.start()
time.sleep(20)
obj = Singleton.instance()
print(obj)

这种方式实现的单例模式,使用时会有限制,以后实例化必须通过obj = Singleton.instance()

如果用obj=Singleton(),这种方式得到的不是单例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ########### 单例类定义 ###########
class Foo(object):

__instance = None

@staticmethod
def singleton():
if Foo.__instance:
return Foo.__instance
else:
Foo.__instance = Foo()
return Foo.__instance

# ########### 获取实例 ###########
obj = Foo.singleton()

对于Python单例模式,创建对象时不能再直接使用:obj = Foo(),而应该调用特殊的方法:obj = Foo.singleton()

基于new方法实现(推荐使用,方便)

当我们实现单例时,为了保证线程安全需要在内部加入锁

我们知道,当我们实例化一个对象时,是先执行了类的__new__方法(我们没写时,默认调用object.__new__),实例化对象;然后再执行类的__init__方法,对这个对象进行初始化,所有我们可以基于这个,实现单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import threading
class Singleton(object):
_instance_lock = threading.Lock()

def __init__(self):
pass

def __new__(cls, *args, **kwargs):
if not hasattr(Singleton, "_instance"):
with Singleton._instance_lock:
if not hasattr(Singleton, "_instance"):
Singleton._instance = object.__new__(cls, *args, **kwargs)
return Singleton._instance

obj1 = Singleton()
obj2 = Singleton()
print(obj1,obj2)

def task(arg):
obj = Singleton()
print(obj)

for i in range(10):
t = threading.Thread(target=task,args=[i,])
t.start()

基于metaclass方式实现

  • 类由type创建,创建类时,type的__init__方法自动执行,类()执行type的__call__方法(类的__new__方法,类的__init__方法)
  • 对象由类创建,创建对象时,类的__init__方法自动执行,对象()执行类的__call__方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class Foo:
def __init__(self):
pass

def __call__(self, *args, **kwargs):
pass

obj = Foo()
# 执行type的 __call__ 方法,调用 Foo类(是type的对象)的 __new__方法,用于创建对象,然后调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。

obj() # 执行Foo的 __call__ 方法

####

class SingletonType(type):
def __init__(self,*args,**kwargs):
super(SingletonType,self).__init__(*args,**kwargs)

def __call__(cls, *args, **kwargs): # 这里的cls,即Foo类
print('cls',cls)
obj = cls.__new__(cls,*args, **kwargs)
cls.__init__(obj,*args, **kwargs) # Foo.__init__(obj)
return obj

class Foo(metaclass=SingletonType): # 指定创建Foo的type为SingletonType
def __init__(self,name):
self.name = name
def __new__(cls, *args, **kwargs):
return object.__new__(cls)

obj = Foo('xx')

#####
# 实现单例模式
import threading

class SingletonType(type):
_instance_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if not hasattr(cls, "_instance"):
with SingletonType._instance_lock:
if not hasattr(cls, "_instance"):
cls._instance = super(SingletonType,cls).__call__(*args, **kwargs)
return cls._instance

class Foo(metaclass=SingletonType):
def __init__(self,name):
self.name = name


obj1 = Foo('name')
obj2 = Foo('name')
print(obj1,obj2)