学习线性代数的时候,想要可视化,但是仅仅是三维坐标系,我都感觉到手画图很不直观。所以这次的python学习,一方面是把之前没学完的学完,另一方面,也是想了解下图形绘制的技术。以下内容和6.00笔记10是重复的,但和之前记录的侧重点不同,这个笔记更加完整(熟练)。
>>> import math
>>> def addPoints(p1,p2):
r = []
r.append(p1[0]+p2[0])
r.append(p1[1]+p2[1])
return r
>>> p = [1,2]
>>> q = [3,1]
>>> r = addPoints(p,q)
>>> print (r)
[4, 3]
这个知识点之前在听课的时候似乎没有记录,但这个实际上可以视为一个向量加法。但这里有一个问题,就是坐标的表示有两种方法,一个是笛卡尔坐标系,就是普通的直角坐标系;另一种是极坐标,那么如何区分这里的两个点的表达是笛卡尔坐标还是极坐标呢?而且,如果是极坐标的形式的话,也就不能用上面这种addPoints方法。
应对这个问题,就需要用class来整合对象和方法,笛卡尔坐标下有对应的向量相加方法,极坐标有它自己的方法。
>>> class cartesianPoint:
pass
>>> cp1 = cartesianPoint()
>>> cp2 = cartesianPoint()
>>> cp1
cp1 and cp2 are both instances of this type.
>>> cp1.x = 1.0
>>> cp1.y = 2.0
>>> cp2.x = 1.0
>>> cp2.y = 2.0
>>> cp1.x
1.0
cp1 points to an instance, and I have now given an internal variable name
and a value.
以下是判断两个坐标是否重合:
>>> def samePoint(p1,p2):
return (p1.x == p2.x) and (p1.y == p2.y)
>>> samePoint(cp1,cp2)
True
但是不能用python内置比较器结果是这样的:
>>> 4 is 4
True
>>> cp1 is cp2
False
这个称为shallow equality(也称为object equality), 它测试的是:Do these things point to exactly the same spot in memory, the same instance. 前面的称为deep equality(也称为value equality),这个是我们可以自己定义的。
同样的思路可以应用于极坐标。
>>> class polarPoint():
pass
>>> pp1 = polarPoint()
>>> pp2 = polarPoint()
>>> pp1.radius = 1.0
>>> pp1.angle = 0
>>> pp2.radius = 2.0
>>> pp2.angle = math.pi / 4.0
但如果用samePoint()方法来判断极坐标,会出现错误:
>>> samePoint(pp1,pp2)
Traceback (most recent call last):
File "", line 1, in
samePoint(pp1,pp2)
File "", line 2, in samePoint
return (p1.x == p2.x) and (p1.y == p2.y)
AttributeError: 'polarPoint' object has no attribute 'x'
也就是说,虽然坐标的表现形式有极坐标和笛卡尔坐标两种,但本质上都是相同的,不同的只是表现形式。但如果要判定两种坐标是否重合时,竟然要需要两个method来判定。这不科学。而class还可以进一步整合。
class cPoint:
def __init__(self, x, y):
self.x = x
self.y = y
self.radius = math.sqrt(self.x * self.x + self.y * self.y)
self.angle = math.atan2(self.y, self.x)
def cartesian(self):
return (self.x, self.y)
def polar(self):
return (self.radius, self.angle)
def __str__(self):
return '[' + str(self.x) + ',' + str(self.y) + ']'
def __cmp__(self,other):
return (self.x > other.x) and (self.y > other.y)
注意,在cPoint类里面,极坐标是从笛卡尔坐标转化来的。
>>> p = cPoint(1.0, 2.0)
>>> p.x
1.0
>>> p.cartesian()
(1.0, 2.0)
尽管我们可以通过
命令直接获取数值,但是这个方法会让数据“exposed”,数据暴露的问题在于,你不仅可以直接获取数据,也可以修改数据,比如:
>>> p.x = 'foobo'
>>> p.x
'foobo'
为了防止这种情况,需要data hiding。所谓data hiding,就是:One can only access instance values through defined methods. 但python没有提供这种获取数据的限制,因此需要自我约束。以下是另一种“放飞”及后果:
>>> p = cPoint(1.0, 2.0)
>>> p.polar()
(2.23606797749979, 1.1071487177940904)
>>> p.radius = 5.0
>>> p.polar()
(5.0, 1.1071487177940904)
>>> p.cartesian()
(1.0, 2.0)
对__str__的说明:
>>> p
>>> print (p)
[1.0,2.0]
如果把cPoint类中的__str__删除:
>>> class cPoint:
def __init__(self, x, y): # create instances
self.x = x
self.y = y
self.radius = math.sqrt(self.x * self.x + self.y * self.y)
self.angle = math.atan2(self.y, self.x)
def cartesian(self):
return (self.x, self.y)
def polar(self):
return (self.radius, self.angle)
def __cmp__(self,other):
return (self.x > other.x) and (self.y > other.y)
>>> p = cPoint(1.0, 2.0)
>>> print (p)
显然,如果你要打印实例时,python首先自动搜寻__str__方法,如果有,调用该方法。
最后注意如何调用__cmp__方法,首先是错误的:
>>> p.__cmp__(p,q)
Traceback (most recent call last):
File "", line 1, in
p.__cmp__(p,q)
TypeError: __cmp__() takes 2 positional arguments but 3 were given
正确的:
>>> p.__cmp__(q)
False
还有公开课可以但我的python 3不可以的方法:
>>> p>q
Traceback (most recent call last):
File "", line 1, in
p>q
TypeError: '>' not supported between instances of 'cPoint' and 'cPoint'
判定两个坐标是否重合,需要用__eq__方法。关于这些比较方法,在6.00笔记11中有说明。
线段:
>>> class Segment:
def __init__(self, start,end):
self.start = start
self.end = end
def length(self):
return math.sqrt( ((self.start.x - self.end.x) *
(self.start.x - self.end.x)) +
((self.start.y - self.end.y) *
(self.start.y - self.end.y)) )
要注意的是,start, end are actually points。也就是说,在这个Segment类中,its elements are themselves instances of cPoint.
>>> p1 = cPoint(3.0, 4.0)
>>> p2 = cPoint(5.0, 7.0)
>>> s = Segment(p1, p2)
>>> print (s.length())
3.605551275463989
但是这个代码存在一个问题,就是直接读取数据,而不是通过method获取数据。这种data exposing的问题在于,当你的代码数量庞大的时候,有的时候修改会很麻烦。而通过method访问数据的话,那么需要调整的代码量就会少很多,而且明确。关于这点,老师一直在强调“模块化”这个概念。
另外,就这节课内容而言,还有一个封装的概念。我们首先把“点”对应的笛卡尔坐标和极坐标还有相应的方法封装在cPoint里面。在这个基础上,我们可以建立”线段“。之后我们还可以继续延伸,比如建造多边形等。