* ,MMM8&&&. *
MMMM88&&&&& .
MMMM88&&&&&&&
* MMM88&&&&&&&&
MMM88&&&&&&&&
'MMM88&&&&&&'
'MMM8&&&' *
|\___/| /\___/\
) ( ) ~( . '
=\ /= =\~ /=
)===( ) ~ (
/ \ / \
| | ) ~ (
/ \ / ~ \
\ / \~ ~/
jy__/\_/\__ _/_/\_/\__~__/_/\_/\_/\_/\__ww
| | | |( ( | | | )) | | | | | |
| | | | ) ) | | |//| | | | | | |
| | | |(_( | | (( | | | | | | |
| | | | | | | |\)| | | | | | |
| | | | | | | | | | | | | | |
在表示输入和输出上,采用了 ‘In - Out‘ 和 ‘>>>‘ 多种表达方式,后面统一采用IPython 中的 ‘>>>‘ 来表示输入 In。
7.1 对象魔法
提到面向对象,总是离不开几个重要的术语:多态(Polymorphism),继承(Inheritance)和封装(Encapsulation)。Python也是一种支持OOP的动态语言,本文将简单阐述Python对面向对象的支持。
7.1.1 多态
可对不同类型的对象执行相同的操作,且这些操作能正常运行。
比如:
def add(x,y):
return x+y
In :add(1,2)
Out:3
In :add('Love ','you')
Out:'Love you'
#repr时多态的集大成者之一,可用于任何对象
In [1]: def length_message(x):
...: print(repr(x),'的长度为 ',len(x))
In [2]: length_message('love')
'love' 的长度为 4
In [3]: length_message(['df',12])
['df', 12] 的长度为 2
In [4]: length_message({'a':1,'b':{'c':3,'d':4}})
{'a': 1, 'b': {'c': 3, 'd': 4}} 的长度为 2
多态形式是Python编程方式的核心,有时称为鸭子类型。”如果走起来像鸭子,叫起来像鸭子,那么它就是鸭子。”
7.1.2 封装
定义好类后,对外部隐藏有关工作原理的细节。
class = class()
In :class.add(1,5)
Out:6
In :class.mutiply(2,3)
Out:6
In:class.add([4,5,'a'],{'a':1,'b':2} )
Out:Wrong!不同于多态,多态隐藏的是对象所属的类(对象类型),而封装隐藏的是内部原理和细节。
7.1.3 继承
可基于通用类创建专用类。
class1 = add()
#class1方法 class1.sum()
class2 = add_print(class1)
#调用class1中的方法
class2.sum()
7.2 类
面向对象(OOP)术语 | 解释 |
---|---|
类 | 对具有相同数据和方法的一组对象的描述或定义。 |
对象 | 对象是一个类的实例。 |
实例(instance) | 一个对象的实例化实现。 |
标识(identity) | 每个对象的实例都需要一个可以唯一标识这个实例的标记。 |
实例属性(instance attribute) | 一个对象就是一组属性的集合。 |
实例方法(instance method) | 所有存取或者更新对象某个实例一条或者多条属性的函数的集合。 |
类属性(classattribute) | 属于一个类中所有对象的属性,不会只在某个实例上发生变化 |
类方法(classmethod) | 那些无须特定的对性实例就能够工作的从属于类的函数。 |
7.2.1 类是什么
类对象 | 类方法 | 类属性 |
---|---|---|
菠萝 | 吃 | 泡盐水 |
凤梨 | 吃; 引发“凤梨是菠萝吗“的话题。 | 不泡盐水 |
举例:菠萝和凤梨,菠萝是凤梨的超类,凤梨是菠萝的子类。
菠萝只有一个方法“吃“,凤梨新增了一个方法:让人们辩论凤梨是不是菠萝?
我们称菠萝为“类对象“,a = 菠萝,b = 菠萝,称 a 和 b 是类对象“菠萝“的两个实例;类对象中的方法称之为“类方法“;泡盐水和不泡盐水又称“类属性“。
7.2.2 创建自定义类
简单示例:
class person:
def set_name(self,name):
self.name = name
def get_name(self):
return self.name
def greet(self):
print('Hello,world! I\'m {}.'.format(self.name))
这个示例包含三个方法定义,它们类似于函数定义,但位于class语句内。
person是类的名称。
foo = person()
foo.set_name("Wei Wu")
foo.greet()
Out:Hello,world! I'm Wei Wu.
其中,self 指向对象 foo 本身。开始我有一种想法,那么 foo.greet()等同于 person.greet(person())吗?实验发现不行,类本身不能作为对象,foo.greet() 可以用下面的形式表达。
person.greet(foo)
Out:Hello,world! I'm Wei Wu.
7.2.3 属性、函数和方法
方法 | 关联的方法,将参数 self 关联到了该方法所属的实例 |
---|---|
函数 | 无需关联到实例 |
区别在用代码展示如下:
# 展示一个类中的函数,虽然有self函数,但是其并没有关联到类对象
class Class:
def method():
print("This Function is not to associated with the instance it belongs to!")
----------
In : c = Class()
.... c.method()
Out: This Function is not to associated with the instance it belongs to!
7.2.4 再谈隐藏
在默认情况下,允许从外部访问对象的属性。如下:
# 通过示例,
class Class:
def set_name(self, name):
self.name = name
def get_name(self):
return self.name
----------
In : c = Class()
.... c.set_name('Wei Wu') # 调用方法访问并更改属性
.... c.name
Out: 'Wei Wu'
In : c.name = 'Ling Yu' # 可以从外部直接访问并更改属性
.... c.name
Out: 'Ling Yu'
有些程序员认为,这违反了封装原则。他们认为英爱对外部完全隐藏对象的状态,即不能从外部访问他们。
因此,可以将属性定义为私有。私有属性不能从对象外部访问,而只能通过存取器方法来访问
Python没有为私有属性提供直接的支持,但是可以通过小花招实现类似的效果(仍无法禁止别人访问)。如下示例。
class Secretive: # 遮遮掩掩的
def __inaccessible(self): # 难以达到的
print('Bet you can\'t see me ...') # 赌你看不见我
def accessible(self):
print('The secret message is :')
self.__inaccessible()
----------
In : s = Secretive()
.... s.accessible()
Out: The secret message is :
.... Bet you can't see me ...
就同前面所说,这只是一个类似方法,从外部依然可以访问,比如,修改代码,在开头加一个下划线和类名。
class Secretive:
# 注意这里
def _Secretive__inaccessible(self):
print(' Bet you can\'t see me ...')
def accessible(self):
print('The secret message is :')
self.__inaccessible()
----------
In : s = Secretive()
.... s._Secretive__inaccessible()
Out: Bet you can't see me ...
总之,你无法禁止别人访问对象的私有方法和属性,但是这种名称修改方式(对两个下划线打头的名称进行转换,开头加上一个下划线和类名)发出了强烈的信号,让他们不要这样做。比如, from module import * 不会导入以一个下划线打头的名称。
7.2.5 类的命名空间
在 class 语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的
,而类的所有成员都可以访问这个命名空间。类定义其实就是要执行的代码段。比如:
class C:
print('class C being defined...')
----------
Out: class C being defined ...
在下面的代码中,在类作用域内定义了一个变量,所有的成员(实例)都可以访问它,这里使用它来计算类实例的数量。使用 init 来初始化所有实例。
# 每个实例都可以访问类作用域内的变量
class MemberCounter:
members = 0
def init(self):
MemberCounter.members += 1
----------
In : m1 = MemberCounter()
.... m1.init()
.... m1.members
Out: 1
In : m2 = MemberCounter()
.... m2.init()
.... m2.members
Out: 2
In : MemberCounter.members
Out: 2
如果在一个实例中给属性 members 赋值。
# 局部变量遮盖了全局变量
In : m1.members = 'Two'
In : m1.members
Out: 'Two'
In : m2.members
Out: 1
7.2.6 指定超类
子类拓展了超类的定义。要制定超类,可在 class 语句中的类名后面加上超类名,并将其用圆括号括起。
# 定义了一个过滤序列的通用类
# 返回不为空的元素
class Filter:
def init(self):
self.blocked = []
def filter(self, sequence ):
# 返回不在blocked列表中的元素
return [x for x in sequence if x not in self.blockedd]
#返回不为 ‘SPAM‘的元素
class SPAMFilter(Filter): #是FIlter的子类
def init(self): # 重写超类 Filter 的方法 init
self.blocked = ['SPAM'] # 定义列表元素为‘SPAM‘
----------
# 超类从空列表中返回了所有元素
>>> f = Filter
... f.init()
... f.filter([1,2,3])
[1,2,3]
# 子类从列表 blocked 中返回了不为 ‘SPAM‘ 的元素
>>> f = SPAMFilter()
... f.init()
... f.filter([1,2,3,'SPAM','SPAM'])
[1,2,3]
在 SPAMFilter 类的定义中有两个要点:
- 以提供新定义的方式重写了 FIlter 类中方法 init 的定义;
- 直接从 Filter 类继承了方法 filter 的定义,因此无需重新编写其定义。
7.2.7 深入探讨继承
要确定一个类是否是另一个类的子类,可使用内置方法 issubclass。
>>> issubclass(SPAMFIlter, Filter)
True
>>> issubclass(Filter, SAMPFilter)
>False
如果你有一个类,想知道它的基类(超类),可以访问其特殊属性 bases (前后双下划线)。
>>> SPAMFilter.__bases__
(<class__main__.Filter at 0x171e40>,)
>>> FIlter.__bases__
(<class 'object'>,)
要确定对象是否是特定类的实例,可以使用 isinstance。
# s 是 SPAMFilter 的实例(直接)
>>> s = SPAMFilter()
>>> isinstance(s, SPAMFilter)
True
# s 也是 Filter 的实例(间接),因为 SPAMFilter 是 FIlter 的子类
>>> isinstance(s, Filter)
True
# s 不是 字符串的实例,即 s 不是字符串类型
>>> isinstance(s, str)
False
如果要获悉对象属于哪个类,可以使用属性 class 。
>>> s.__class__
<class__main__.SPAMFilter at 0x1707c0>
7.2.8 多个超类
子类可以继承多个超类。
# eval : eval("x") ➡️ x, x可为字符串、字典、元组等.
# 注:若函数内参数有引号,则 x两侧的引号不能与其相同
class Calculator: # 计算器
def calculate(self, expression):
self.value = eval(expression)
class Talker:
def talk(self):
print('Hi,my value is ',self.value)
# 方法解析顺序(MRPO)
# 先访问 Calculator,再访问 Talker。 顺序反过来的话读取顺序也随之改变。
class TalkingCalulator(Calculator, Talker):
pass
----------
>>> tc = TalkingCalulator()
>>> tc.calulate('1 + 2 * 3')
>>> tc.talk()
Hi,my value is 7
子类 TalkingCalulator 本身无所作为,其所有行为都是从超类那里继承的。这被称为多重继承。然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症“。
使用多重继承时,如果 Calculator 类包含方法 talk,那么这个方法将覆盖 Talker 类的方法 talk。因此,当多个超类以不同的方式实现了同一个方法,必须在 class 语句中小心排列这些超类。
Python搜索——广度搜索和深度搜索。
class A: # Python3,不需要加 (object)
def test(self):
print('from A')
class B(A):
def test(self):
print('from B')
class C(A):
def test(self):
print('from C')
class D(B):
def test(self):
print('from D')
class E(C):
def test(self):
print('from E')
class F(D,E):
# def test(self):
# print('from F')
pass
f1=F()
f1.test()
print(F.__mro__)
Out:from D
(<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
#疑问,这里到底是广度搜索还是深度搜索?看着是深度搜索,当构建一颗圆满的二叉树(4层15个节点)时,是按照深度搜索做的。
7.2.9 接口和内省
函数 | 描述 | 语法 | 参数 | 返回值 |
---|---|---|---|---|
hasattr | 用于判断对象是否包含对应的属性。 | hasattr(object, name) | object – 对象 name – 字符串,属性名。 | 如果对象有该属性返回 True,否则返回 False |
getattr | 用于返回一个对象属性值 | getattr(object, name[, default]) | object – 对象。name – 字符串,对象属性。default – 默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError。 | 返回对象属性值。 |
callable | 用于检查一个对象是否是可调用的。如果返回True,object仍然可能调用失败;但如果返回False,调用对象ojbect绝对不会成功 | callable(object) | object – 对象 | 可调用返回 True,否则返回 False |
处理多态对象时,只关心其接口(协议)——对外暴露的方法和属性。通常,要求对象遵循特定的接口(即实现特定的方法),在有需要时,检查方法是否存在,如果不存在,就改弦易辙。
# 经检测,在实例 tc 中包含方法 talk
>>> hasattr(tc, 'talk)
True
# 经检测,在实例 tc 中不包含方法 hah
>>> hasattr(tc, 'hah')
False
还可以检查属性 talk 是否可被调用
>>> callable(getattr(tc,'talk',None))
True
>>> callable(gettatts(tc,'hah',None))
False
setattr 与 getattr 功能相反,可用于设置对象的属性
>>> setattr(tc, 'name', 'Wei Wu')
>>> tc.name
'Wei Wu'
7.2.10 抽象基类
什么是抽象类?
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
为什么要有抽象类
- 从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
- 从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。
个人理解
抽象类,定义了在此类中必须实现方法。如果子类中没有实现,那么就会报错。
# 定义抽象类 All_file
#[注:本代码黏自博客园qianxiamo](https://www.cnblogs.com/asaka/p/6758426.html)
from abc import ABC,abstractmethod #利用abc模块实现抽象类
class All_file(ABC):
all_type='file'
@abc.abstractmethod #定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能'
pass
@abc.abstractmethod #定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能'
pass
# class Txt(All_file):
# pass
# t1=Txt() #报错,子类没有定义抽象方法
# 派生子类,实现抽象类方法
#[注:本代码黏自博客园qianxiamo](https://www.cnblogs.com/asaka/p/6758426.html)
class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(All_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
----------
>>> wenbenwenjian=Txt()
>>> yingpanwenjian=Sata()
>>> jinchengwenjian=Process()
>>> wenbenwenjian.read()
文本数据的读取方法
>>> yingpanwenjian.write()
硬盘数据的读取方法
>>> jinchengwenjian.read()
进程数据的读取方法
>>> print(wenbenwenjian.all_type)
file
>>> print(yingpanwenjian.all_type)
file
>>> print(jinchengwenjian.all_type)
file
7.3 关于面向对象设计的一些思考
见书