cs61a对象的抽象学习笔记

目录

  • Special Methods
    • True and false value
    • Sequence operations
      • __len__方法
      • __getitem__方法
    • Callable objects
    • Arithmetic
  • Multiple Representations
    • Generic Functions
      • Type dispatching
    • coercion

对象抽象中的一个核心概念是泛型函数,它是一个可以接受多种不同类型的值的函数。我们将考虑实现泛型函数的三种不同技术:共享接口、类型调度和类型强制。Python对象系统支持创建泛型函数的特性。

Special Methods

只要对象被构造,__init__ 方法就会被调用;
只要打印,__str__方法就会被调用,使用这个方法得到是人能理解的;
只要涉及显示与交互会话,__repr__ 方法就会被调用, 使用这个方法得到的编译器能理解的;

True and false value

所有对象的布尔逻辑应该都是True,特殊方法 __bool__ 方法被定义返回True。用户自定义的类从布尔逻辑来说应该是True,但要实现这个, __bool__方法必须被重载。比如,我们认为银行账户的余额为0是False,需要给这个类添加一个 __bool__方法以实现这个行为。

>>> Account.__bool__ = lambda self: self.balance != 0

还可以直接调用bool构造器查看其逻辑值:

>>> bool(Account('Jack'))
False
>>> if not Account('Jack'):print('Jack has nothing')
Jack has nothing

Sequence operations

__len__方法

常使用len来求一个序列的长度,如:

>>> len('Go Bears!')
9

这是因为所有的内建序列类型都使用了__len__方法。上面的操作等价于下面的操作:

>>> 'Go Bears!'.__len__()
9

当没有提供__bool__方法时,序列的布尔逻辑值由长度决定。长度为0是为false,不为0时为true。

>>> bool('')
False
>>> bool([])
False
>>> bool('Go Bears!')
True

__getitem__方法

这个方法等价于一个元素选择器,也是可以被直接调用的。如下:

>>> 'Go Bears!'[3]
'B'
>>> 'Go Bears!'.__getitem__(3)
'B'

Callable objects

在Python中,函数是一级对象,因此它们可以作为数据传递,并具有类似于任何其他对象的属性。Python还允许我们通过包含一个call方法来定义类似于“调用”函数的对象。使用此方法,我们可以定义一个行为类似于高阶函数的类。
如下例子,这是一个高阶函数:

>>> def make_adder(n):def adder(k):return n + kreturn adder
>>> add_three = make_adder(3)
>>> add_three(4)
7

可以创建一个类,定义一个__call__方法达到一样的效果:

>>> class Adder(object):def __init__(self, n):self.n = ndef __call__(self, k):return self.n + k
>>> add_three_obj = Adder(3)
>>> add_three_obj(4)
7

data和function的分界还可以变得更加模糊。

Arithmetic

特殊方法还可以将定义内置运算符的行为用于用户定义对象的。为了提供这种通用性,Python遵循特定的协议来应用每个操作符。例如,要计算包含+运算符的表达式,Python会检查表达式的左操作数和右操作数上是否都有特殊方法。首先,Python在左操作数的值上检查add方法,然后在右操作数的值上检查radd方法。如果找到其中一个,则调用该方法,并将另一个操作数的值作为其参数。

Multiple Representations

这里不适用Python内部定义的复数,而是自己实现一个复数类型,并实现复数的加法和乘法。
首先定义一个Number类,然后使用通过add和mul方法抽象出该类的实例是怎么相加和相乘的。这个类没有__init__方法,它被设计为不能直接实例化,而是作为特殊数值类型的超类,比如复数。

>>> class Number:def __add__(self, other):return self.add(other)def __mul__(self, other):return self.mul(other)

复数有两种表达方式:(1)使用实部和虚部表示;(2)使用模和角度表示。
复数加法的实现使用方式(1)的表达更易求得:复数实部相加,虚部相加;
复数乘法的实现使用方式(2)的表达更易求得:将一个复数的长度乘以另一个复数的长度,然后将其旋转穿过另一个复数的角度得到的向量。
定义一个继承自Number的类Complex 类表示复数,并描述复数的运算如下:

>>> class Complex(Number):def add(self, other):return ComplexRI(self.real + other.real, self.imag + other.imag)def mul(self, other):magnitude = self.magnitude * other.magnitudereturn ComplexMA(magnitude, self.angle + other.angle)

上面的代码中假定存在两个类:

  • 通过实部和虚部对构造虚数ComplexRI类;
  • 通过长度和角度构造虚数的ComplexMA类。

两个或多个属性值之间保持固定关系的要求是一个新问题。一种解决方案是只为一个表示存储属性值,并在需要时计算另一个表示。Python有一个从零参数函数动态计算属性的简单特性。@property修饰器允许在不使用调用表达式语法(表达式后面的括号)的情况下调用函数。
下面实现ComplexRI类存储real和imag属性,并根据需要计算大小和角度:

>>> from math import atan2
>>> class ComplexRI(Complex):def __init__(self, real, imag):self.real = realself.imag = imag@propertydef magnitude(self):return (self.real ** 2 + self.imag ** 2) ** 0.5@propertydef angle(self):return atan2(self.imag, self.real)def __repr__(self):return 'ComplexRI({0:g}, {1:g})'.format(self.real, self.imag)>>> ri = ComplexRI(5, 12)
>>> ri.real
5
>>> ri.magnitude
13.0
>>> ri.real = 9
>>> ri.real
9
>>> ri.magnitude
15.0

同理,通过模和角度定义ComplexMA类,也能实现相互转化:

>>> from math import sin, cos, pi
>>> class ComplexMA(Complex):def __init__(self, magnitude, angle):self.magnitude = magnitudeself.angle = angle@propertydef real(self):return self.magnitude * cos(self.angle)@propertydef imag(self):return self.magnitude * sin(self.angle)def __repr__(self):return 'ComplexMA({0:g}, {1:g} * pi)'.format(self.magnitude, self.angle/pi)>>> ma = ComplexMA(2, pi/2)
>>> ma.imag
2.0
>>> ma.angle = pi
>>> ma.real
-2.0

最终能够实现两个虚数的加法和乘法:

>>> from math import pi
>>> ComplexRI(1, 2) + ComplexMA(2, pi/2)
ComplexRI(1, 4)
>>> ComplexRI(0, 1) * ComplexRI(0, 1)
ComplexMA(1, 1 * pi)

编码多个表示的接口方法具有吸引人的特性。每个表示的类可以单独开发;它们只能在共享属性的名称以及这些属性的任何行为条件上达成一致。如果另一个程序员想在同一个程序中添加第三个复数表示,他们只需要创建另一个具有相同属性的类。
使用数据抽象,我们能够在不改变程序含义的情况下改变数据类型的实现。通过接口和消息传递,我们可以在同一个程序中有多个不同的表示。在这两种情况下,一组名称和相应的行为条件定义抽象使得类的定义变得灵活。

Generic Functions

泛型函数是应用于不同类型参数的方法或函数。上面的例子中Complex.add方法是通用的,因为它可以将ComplexRI或ComplexMA作为other的值。这种灵活性是通过确保ComplexRI和ComplexMA共享一个接口而获得的。使用接口和消息传递只是实现泛型函数的几种方法之一。另外两个问题:类型分派( type dispatching)和类型强制(type coercion)。
假设除了复数类之外,我们还可以实现了一个有理类来精确地表示分数:

>>> from fractions import gcd
>>> class Rational(Number):def __init__(self, numer, denom):g = gcd(numer, denom)self.numer = numer // gself.denom = denom // gdef __repr__(self):return 'Rational({0}, {1})'.format(self.numer, self.denom)def add(self, other):nx, dx = self.numer, self.denomny, dy = other.numer, other.denomreturn Rational(nx * dy + ny * dx, dx * dy)def mul(self, other):numer = self.numer * other.numerdenom = self.denom * other.denomreturn Rational(numer, denom)

我们通过add和mul方法实现了超类Number的接口。因此,我们可以使用熟悉的运算符来加和乘有理数。

>>> Rational(2, 5) + Rational(1, 10)
Rational(1, 2)
>>> Rational(1, 4) * Rational(2, 3)
Rational(1, 6)

Type dispatching

那么怎么实现复数加上有理数这种跨类型操作?为了不严重违反抽象障碍的情况下支持它。我们希望能够向有理数添加一个复数,并且我们希望使用一个泛型的add方法来这样做,并希望这样能够正确地处理所有数值类型。
我们知道isinstance方法可以得到当前对象是否属于某个类:

>>> c = ComplexRI(1, 1)
>>> isinstance(c, ComplexRI)
True
>>> isinstance(c, Complex)
True
>>> isinstance(c, ComplexMA)
False

简单实用一个is_real函数就能实现类型分派( type dispatching):

>>> def is_real(c):"""Return whether c is a real number with no imaginary part."""if isinstance(c, ComplexRI):return c.imag == 0elif isinstance(c, ComplexMA):return c.angle % pi == 0
>>> is_real(ComplexRI(1, 1))
False
>>> is_real(ComplexMA(2, pi))
True

还可以实用一个类型标签的属性,把复数和分数分开:

>>> Rational.type_tag = 'rat'
>>> Complex.type_tag = 'com'
>>> Rational(2, 5).type_tag == Rational(1, 2).type_tag
True
>>> ComplexRI(1, 1).type_tag == ComplexMA(2, pi/2).type_tag
True
>>> Rational(2, 5).type_tag == ComplexRI(1, 1).type_tag
False

由于分数可以转为浮点数,复数和分数的加可以这么算:

>>> def add_complex_and_rational(c, r):return ComplexRI(c.real + r.numer/r.denom, c.imag)

复数在复平面中,模总是一个正值,角度为0表示整数,角度为pi表示负数。因此,复数与分数的乘法可以这么算:

>>> def mul_complex_and_rational(c, r):r_magnitude, r_angle = r.numer/r.denom, 0if r_magnitude < 0:r_magnitude, r_angle = -r_magnitude, pireturn ComplexMA(c.magnitude * r_magnitude, c.angle + r_angle)

参数顺序相反也成立:

>>> def add_rational_and_complex(r, c):return add_complex_and_rational(c, r)
>>> def mul_rational_and_complex(r, c):return mul_complex_and_rational(c, r)

最后重写Number超类,__add____mul__方法中能够实现类型分派。需要类型标签属性type_tag实现分派。原理如下:如果两个相加的参数type_tag是一样的,直接相加,如果不一样,则要查看是否有两种不同的类型相加的定义,这里的定义了一个adders做怎样的事情。对于乘法,同理。
实现如下:

>>> class Number:def __add__(self, other):if self.type_tag == other.type_tag:return self.add(other)elif (self.type_tag, other.type_tag) in self.adders:return self.cross_apply(other, self.adders)def __mul__(self, other):if self.type_tag == other.type_tag:return self.mul(other)elif (self.type_tag, other.type_tag) in self.multipliers:return self.cross_apply(other, self.multipliers)def cross_apply(self, other, cross_fns):cross_fn = cross_fns[(self.type_tag, other.type_tag)]return cross_fn(self, other)adders = {("com", "rat"): add_complex_and_rational,("rat", "com"): add_rational_and_complex}multipliers = {("com", "rat"): mul_complex_and_rational,("rat", "com"): mul_rational_and_complex}

最终能够实现如下操作:

>>> ComplexRI(1.5, 0) + Rational(3, 2)
ComplexRI(3, 0)
>>> Rational(-1, 2) * ComplexMA(4, pi/2)
ComplexMA(2, 1.5 * pi)

online python tutor 在线调试:这里

完整代码:

def add_complex_and_rational(c, r):return ComplexRI(c.real + r.numer/r.denom, c.imag)def mul_complex_and_rational(c, r):r_magnitude, r_angle = r.numer/r.denom, 0if r_magnitude < 0:r_magnitude, r_angle = -r_magnitude, pireturn ComplexMA(c.magnitude * r_magnitude, c.angle + r_angle)def add_rational_and_complex(r, c):return add_complex_and_rational(c, r)
def mul_rational_and_complex(r, c):return mul_complex_and_rational(c, r)class Number:def __add__(self, other):if self.type_tag == other.type_tag:return self.add(other)elif (self.type_tag, other.type_tag) in self.adders:return self.cross_apply(other, self.adders)def __mul__(self, other):if self.type_tag == other.type_tag:return self.mul(other)elif (self.type_tag, other.type_tag) in self.multipliers:return self.cross_apply(other, self.multipliers)def cross_apply(self, other, cross_fns):cross_fn = cross_fns[(self.type_tag, other.type_tag)]return cross_fn(self, other)adders = {("com", "rat"): add_complex_and_rational,("rat", "com"): add_rational_and_complex}multipliers = {("com", "rat"): mul_complex_and_rational,("rat", "com"): mul_rational_and_complex}class Complex(Number):type_tag = "com"def add(self, other):return ComplexRI(self.real + other.real, self.imag + other.imag)def mul(self, other):magnitude = self.magnitude * other.magnitudereturn ComplexMA(magnitude, self.angle + other.angle)from math import atan2
class ComplexRI(Complex):def __init__(self, real, imag):self.real = realself.imag = imag@propertydef magnitude(self):return (self.real ** 2 + self.imag ** 2) ** 0.5@propertydef angle(self):return atan2(self.imag, self.real)def __repr__(self):return 'ComplexRI({0:g}, {1:g})'.format(self.real, self.imag)    from math import sin, cos, pi
class ComplexMA(Complex):def __init__(self, magnitude, angle):self.magnitude = magnitudeself.angle = angle@propertydef real(self):return self.magnitude * cos(self.angle)@propertydef imag(self):return self.magnitude * sin(self.angle)def __repr__(self):return 'ComplexMA({0:g}, {1:g} * pi)'.format(self.magnitude, self.angle/pi)from fractions import gcd
class Rational(Number):def __init__(self, numer, denom):g = gcd(numer, denom)self.type_tag = "rat"self.numer = numer // gself.denom = denom // gdef __repr__(self):return 'Rational({0}, {1})'.format(self.numer, self.denom)def add(self, other):nx, dx = self.numer, self.denomny, dy = other.numer, other.denomreturn Rational(nx * dy + ny * dx, dx * dy)def mul(self, other):numer = self.numer * other.numerdenom = self.denom * other.denomreturn Rational(numer, denom)print(ComplexRI(1.5, 0) + Rational(3, 2))
print(Rational(-1, 2) * ComplexMA(4, pi/2))

coercion

在完全不相关操作作用于完全不相关类型的一般情况下,实现显式的跨类型操作(尽管可能很麻烦)是我们所能期望的最好的方法。幸运的是,我们有时可以利用类型系统中潜在的附加结构来做得更好。通常不同的数据类型并不是完全独立的,可能有一些方法可以将一种类型的对象视为另一种类型的对象。这个过程叫做强迫。例如,如果要求我们将有理数与复数进行算术组合,我们可以将有理数视为虚数部分为零的复数。这样做之后,我们可以使用Complex.add和Complex.mul来组合它们。
一般来说,我们可以通过设计强制函数来实现这一思想,强制函数将一种类型的对象转换为另一种类型的等效对象。下面是一个典型的强制函数,它将有理数转换为具有零虚部的复数:

>>> def rational_to_complex(r):return ComplexRI(r.numer/r.denom, 0)

Number类的替代定义通过尝试将两个参数强制为同一类型来执行跨类型操作。强制字典通过一对类型标记索引所有可能的强制。


>>> class Number:def __add__(self, other):x, y = self.coerce(other)return x.add(y)def __mul__(self, other):x, y = self.coerce(other)return x.mul(y)def coerce(self, other):if self.type_tag == other.type_tag:return self, otherelif (self.type_tag, other.type_tag) in self.coercions:return (self.coerce_to(other.type_tag), other)elif (other.type_tag, self.type_tag) in self.coercions:return (self, other.coerce_to(self.type_tag))def coerce_to(self, other_tag):coercion_fn = self.coercions[(self.type_tag, other_tag)]return coercion_fn(self)coercions = {('rat', 'com'): rational_to_complex}

与定义显式跨类型操作的方法相比,我们只需要为每对类型编写一个函数,而不是为每一组类型和每个泛型操作编写一个不同的函数。。
一些更复杂的强制方案不只是尝试将一种类型强制为另一种类型,而是尝试将两种不同的类型分别强制为第三种公共类型。考虑一个菱形和一个矩形:两者都不是另一个的特例,但都可以看作四边形。
强制的另一个扩展是迭代强制,其中一个数据类型通过中间类型强制转换为另一个数据类型。假设整数可以转换为实数,首先将其转换为有理数,然后将该有理数转换为实数。以这种方式链接强制可以减少程序所需强制函数的总数。
但是强制函数在应用时可能会丢失信息。在上述例子中,有理数是精确的表示,但是当它们被转换成复数时就变成了近似值。

参考:http://composingprograms.com/pages/27-object-abstraction.html

cs61a 课时笔记 对象的抽象相关推荐

  1. cs61a 课时笔记 递归对象和集合

    对象可以将其他对象作为属性值.当某个类的对象具有该类的属性值时,它就是递归对象. Linked List class 定义一个类实现Linked list,使用special method定义它的行为 ...

  2. CS61A 课时笔记 efficiency

    CS61A Spring 学习笔记 原文地址: http://composingprograms.com/pages/28-efficiency.html Measuring Efficiency 计 ...

  3. [python][算法][CS61a]python列表,python抽象数据类型使用练习

    提示:本文内容来源于UCB CS61A 2020 Summer课程,详情请点击CS 61A: Structure and Interpretation of Computer Programs 文章目 ...

  4. HALCON 21.11:深度学习笔记---对象检测, 实例分割(11)

    HALCON 21.11:深度学习笔记---对象检测, 实例分割(11) HALCON 21.11.0.0中,实现了深度学习方法. 本章介绍了如何使用基于深度学习的对象检测. 通过对象检测,我们希望在 ...

  5. 面向对象概念及对象、抽象、类的解释

    具有相同或相似性质的一组对象的抽象就是类,类是对一类事物的描述,是抽象的.概念上的意义: 对象是实际存在的该类事物的每个个体,因此也称为实例: 对象的抽象是类,类的具体化就是对象,也可以说类的实例就是 ...

  6. 类是对象的抽象,对象是类的实例

    类是对象的抽象,对象是类的实例 是 正确的 看:https://www.cnblogs.com/marlanchen/p/11563929.html[转载]

  7. [CS61A]Week03笔记1

    week03笔记 List List(列表)是python内置的数据类型,它被应用于python各处. List通常至少有以下性质: 使用[]来包围参数来声明一个List 也可以使用list()来构造 ...

  8. [CS61A]Week01笔记

    week01笔记 开篇 课程是Summer2020的 https://inst.eecs.berkeley.edu/~cs61a/su20/ 我水平有限,这也就只能当笔记看看,无任何指导价值,样例多来 ...

  9. 十六章、渗透测试(千峰网络安全300课时笔记)

    十六章.渗透测试 引言:千峰网络安全300课时学习,视频学习过程中的学习笔记.笔记中借鉴了CSDN博主「beglage」文章中的图片记录,感谢千峰网络的视频和beglage博主的博文笔记记录分享. B ...

  10. 读书笔记--对象、实例、原型、继承

    创建对象的设计模式 工厂模式 抽象了创建具体对象的过程,用函数封装以特定接口创建对象的细节 解决了创建多个相似对象的问题,没有解决对象识别的问题 function createPerson(name, ...

最新文章

  1. Linux sendmail 服务器
  2. 判断IE浏览器的版本号
  3. 《数据库SQL实战》查找所有已经分配部门的员工的last_name和first_name
  4. 使用Eclipse在Amazon Ec2中部署Java Web应用程序的完整指南
  5. 逆波兰计算器android源码简书,汪都能理解的逆波兰计算器(C++实现)
  6. 二 关键词---关键词的选择(二)
  7. HDU4706 Children's Day
  8. exce小技巧,Mac Excel单元格内换行快捷键
  9. 4.看板方法---在五个季度内,从最差变为最好
  10. oss读取txt文件
  11. 《AngularJS高级程序设计》学习笔记
  12. android A工程引用B工程
  13. springboot+vue网络课程教学网站系统java源码介绍
  14. java 大于或等于_java大于等于怎么表示
  15. 基于android的智慧停车app
  16. 校园人脸识别系统开发需求有哪些
  17. 字节跳动校招内推开始了
  18. 使用python获取美股行情数据
  19. 电商API店铺订单接口(代码对接教程)
  20. UML入门1:事物和事物关系简介

热门文章

  1. es6-es12简单总结
  2. springboot幼儿园幼儿基本信息管理系统毕业设计源码201126
  3. 传智播客黑马程序员_新程序员的最佳播客,以及聆听他们的最佳工具
  4. Android之应用市场排行榜、上架、首发(非原创)
  5. mysql导出表所有数据库,plsql导出所有数据库表
  6. 三款免费的直播推流软件介绍
  7. oracle exadata效果,exadata成功案例与性能测试-oracle.pdf
  8. Excel 2010高级应用-饼图(四)
  9. 群晖NAS系统DSM入门
  10. 【stata】一些关于数据处理的基础知识(备查代码)