好记性不如烂笔头,对之前阅读书籍进行梳理与总结,此文为《Python3面向对象编程》阅读笔记。
第一章 面向对象设计
几个概念
组合: 将几个对象收集在一起生成一个新对象的行为。
聚合:聚合几乎和组合概念相同,区别在于聚合的对象可以独立存在,比如棋盘上的位置不可能与另外一个棋盘产生关联,因此我们说棋盘是由位置组成的,但是棋子可以独立于棋具存在,因此称它与棋具之间为聚合关系。
多态:多态是根据子类的不同实现而区别对待父类的能力,Python中的这种多态通常被称之为“鸭子类型”,“如果它走路像鸭子,游泳像鸭子,那么它就是一只鸭子”。我们不关心它是否真的是一只鸭子,只需要知道它可以游泳或者走路即可。
第二章 Python对象
如何在Python中创建类和继承对象
如何为Python对象添加属性和行为
如何将类组织成包和模块
如何建议人们不要乱动我们的数据
类名必须遵循标准的python变量名规则(必须以字母/下划线开头,并且只能由字母,下划线,数字组成),建议类名采用驼峰格式(CamelCase)命名。
类中方法与正常函数之间的区别之一是,所有方法都有一个必要的参数,依照惯例,这个参数通常命名为self,表示对方法所调用对象的引用。
大多数面向对象编程语言都有构造函数的概念,是创建对象时进行创建和初始化的特定方法。Python有一点不一样,它同时拥有构造函数和初始化方法。
python的初始化方法和其他方法一样,除了拥有一个特定的名字__init__,开头和结尾的双下划线意味着这是一个特殊的方法(永远不要以双下划线开头和结尾来定义你自己的方法,因为未来python设计者可能将其作为特殊用途)。
Python通过字符串文档进行文档注释,试着输入或者加载(记住,python -i filename.py)这个文件到交互解释器中,然后在Python提示符处输入help(Point) enter。就可以看到这个类完善的格式文档。
![bounding box位置预测](https://i-blog.csdnimg.cn/blog_migrate/aac161f457cc908094eeb437ad797da9.png#pic_center)
Python不像c++或者java一样,拥有访问权限的概念,一些属性和方法可以被标记为私有。python严格来说,类的所有方法和属性都是对外公开的,如果我们想要说明,某个方法不应该对外公开,那么可以在他的文档字符串里面表明这个方法仅内部使用(更好的方法是,介绍对外公开的API如何使用),依照惯例,我们也可以在属性和方法前面加一个下划线字符,Python程序员就会明白“这是一个内部变量,直接访问它之前请务必三思”,当然还有另外一种方式可以更加强势地表明外部对象不能访问某个属性或方法:用双下划线作为前缀。
第三章 对象相似时
基本的继承
从内置类型继承
多重继承
多态与鸭子类型
可以根据自己的需要,对内置对象进行扩展,比如list,set,dict,file,str等等,在其基础上,新增自己想要的功能。
下面的super函数,其实是执行Contact父类的__init__方法,它返回父类实例化得到的对象,让我们可以直接调用父类的方法,然后执行自己的初始化过程,也就是设置phone属性。
class Friend(Contact):
def __init__(self, name, email, phone):
super().__init__(name, mail)
self.phone = phone
可以在任何方法内调用super(),不仅仅是__init__。这意味着所有的方法都可以通过重写和调用super来进行修改,在方法的任何位置都可以调用super。
钻石型继承
抽象基类
虽然鸭子类型很有用,但是事先知道这个类能否满足你所需要的协议不是一件容易的事,因此,Python引入抽象基类的概念。抽象基类(Abstract base class) ABCs,定义一组必须被类的鸭子类型实例实现的方法和属性。可以继承抽象基类本身的类来作为类的实例,但是必须提供所有合适的方法。
鸭子类型可以不用继承关系就能创建“是一个”的关系,这是比传统多态更棒的原因。
class Ogg():
ext = '.ogg'
def play(self):
print("this will play an ogg file")
>>> issubclass(Ogg, MediaLoader)
True
>>>isinstance(Ogg(), MediaLoader)
>True
这儿定义的Ogg并没有真的继承自MediaLoader,但是Ogg是MediaLoader的子类,因为它实现了该抽象父类申明的必须实现的方法(play)以及属性(ext)。
第四章 异常捕获
如何找到异常出现的原因
遇到异常时如何修复
如何以不同的方式处理不同的异常
遇到异常时如何清理
创建新的异常类型
在控制流中使用异常语法
异常只是一个对象,有许多不同的异常类,都继承自同一个内置类BaseException
Python程序员倾向于追随“请求谅解,而不是许可”的原则,可就是说,他们先执行代码,然后解决错误。
第五章 何时使用面向对象编程
如何识别对象
再一次讨论数据和行为
用属性将数据包裹在行为中
用行为约束行为
不要重复你自己的准则
找出重复代码
不要仅仅因为你可以使用对象就着急用,但是也永远不要在你需要用到类的时候,疏于使用。
Python提供了一个property关键字用于将方法变得看起来像属性。
class Class(object):
def __init__(self,rgb_value, name):
self.rgb_value = rgb_value
self._name = name
def _set_name(self, name):
if not name:
raise Exception("Invalid Name")
self._name = name
def _get_name():
return self._name
name = property(_get_name, _set_name)
将name变为(半)私有的_name属性,然后顺序通过两个私有的方法来获取设置这个变量,并在赋值过程中完成验证操作。
记住即使对于name属性,前面的代码也不是百分之百安全,人们仍然可以通过直接访问_name属性将其设置为空字符串。但是如果她们访问了一个我们通过下划线表示为私有的变量,那么他们就应该负责这样的状况,而不是我们。
可以将property函数看作返回了一个对象。这个对象代理任何通过指定方法来设置或者访问属性值的请求,property关键字就像是这个对象的构造函数,而这个对象则被设定为改属性的一个公共成员。
装饰器:另一种创建属性的方法
属性方法可以用于装饰器语法,将一个取值函数转化为一个属性:
class Foo:
@property
def foo(self):
return "bar"
这儿将property函数作为一个装饰器,与前面foo = property(foo)语法是等价的,主要区别在可读性上,这里我们在方法的上部将foo函数标记为一个属性,而不是定义完之后再装话,这样它很容易被忽略。同时意味着我们不需要再为定义一个个属性而创建以下划线开头的私有方法。
class Foo:
@property
def foo(self):
return self._foo
@foo.setter
def foo(self, value):
self._foo = value
首先,将foo方法装饰为取值函数,然后将第二个同名方法用第一个被装饰的fool方法的setter属性进行装饰!property返回一个对象,这个对象有一个setter属性,它可以作为其他函数的装饰器。
通过使用内置的property能够模糊行为和数据之间的界限,有时候可能会让人困惑,不知道该选用哪一个。property一个常见的用途是一些类中的数据可能需要添加一些行为。除此之外,标准属性和property之间的区别在于,当property被读取,赋值或者删除时,自动执行某些特定的操作。
from urllib.request import urlopen
class WebPage(object):
def __init__(self,url):
self.url = url
self._content = None
@property
def content(self):
if not self._content:
print("*****")
self._content = urlopen(self.url).read()
return self._content
>>> import time
>>> webpage = WebPage("http://ccphillips.net/")
>>>now = time.time()
Retriving New Page...
>>>time.time() - now
22.4
>>> now = time.time()
>>> content2 = webpage.content
>>>time.time() - now
1.92
>>> content2 == content1
True
自定义取值方法对于需要根据其他对象属性实时计算的属性也很有用。如,我们想要计算一个整数列表的平均值:
class AverageList(list):
@property
def average(self):
return sum(self) / len(self)
相比于定一个行为calculate_average();用average的property既容易写也容易读
Don’t Repeat Youself DRY原则
第六章 Python数据结构
元组和命名元组
字典
列表和集合
如何以及为何扩展内置对象
3种类型的队列
Python禁止向object以及其他几个内置类型添加任意属性
命名元组
from collections import namedtuple
Stock = namedtuple("stock", "symbol current high low")
stock = Stock("FB", 75.00, high=75.03,low=74.90)
namedtuple构造函数接受两个参数,第一个是命名元组的名称,第二个是以空格为分割符的属性字符串,这些属性就是命名元组可以有的属性。
命名元组非常适合表示“只有数据”的情况,但并不是对所有情况来说都非常理想,和元组以及字符串一样,命名元组也是不可变的,因此一旦设定了属性值之后,就不能更改,如果需要修改存储的数据,可能用字典类型更加合适。
setdefault方法
defaultdict数据结构 (collections里面)
Counter计数器 (collections里面)
队列
FIFO队列 Queue
LIFO队列 LifoQueue 来表示栈,按照惯例,对栈的操作被称之为pop与push,但是python使用的queue模版,使用的是与FIFO一样的put()与get(),只不过在LIFO队列中,这些方法作用于栈的顶部而不是队伍的前后端。
为什么栈不用标准列表的append()和pop()方法,虽然说LifeQueue在底层使用的是一个标准的列表,但是LifeQueue支持多线程并发访问,如果你需要在并发环境下进行类栈行为,你就不应该使用列表。第二点,LifoQueue强制使用栈接口。你无法随意地向LifeQueue中的错误位置插入值。
优先级队列:优先级队列的实现都是基于heap数据结构的。