到现在你已经知道如何使用函数组织代码 ,  以及如何使用内置类型来组织数据 . 
下一步将学习 '面向对象编程' ,  面向对象编程使用自定义的类型同时组织代码和数据 . 
面向对象编程是一个很大的话题 ,  需要好几章来讨论 . 
本章的代码示例可以从↓下载 , 
https : / / github . com / AllenDowney / ThinkPython2 / blob / master / code / Point1 . py  
练习的解答可以在↓下载 . 
https : / / github . com / AllenDowney / ThinkPython2 / blob / master / code / Point1_soln . py 
我们已经使用了很多Python的内置类型 ;  现在我们要定义一个新类型 . 
作为示例 ,  我们将会新建一个类型Point ,  用来表示二维空间中的一个点 . 
在数学的表示法中 ,  点通常使用括号中逗号分割两个坐标表示 . 
例如 ,  ( 0 ,  0 ) 表示原点 ,  而 ( x ,  y ) 表示一个在圆点右侧x单位 ,  上方y单位的点 . 
在Python中 ,  有好几种方法可以表达点 . 
*  我们可以将两个坐标分别保存到变量x和y中 . 
*  我们可以将坐标作为列表或元组的元素存储 . 
*  我们可以新建一个类型用对象表达点 . 
新建一个类型比其他方法更复杂一些 ,  但它的优点很快就显现出来 . 
用户定义的类型也称为 '类' ( class ) .  类的定义如下 : 
class  Point : 
	""" Represents a point in 2-D space. """ 
定义头表示新的类名为Point .  定义体是一个文档字符串 ,  解释这个类的用途 . 
可以在类定义中定义变量和函数 ,  我们会后面回到这个话题 . 
定义一个叫作Point的类会创建一个 '对象类' ( object  class ) . 
>> >  Point
< class  '__main__.Point' > 
因为Point是在程序顶层定义的 ,  它的 '全名' 是__main__ . Point . 
类对象项一个创建对象的工厂 .  要新建一个Point对象 ,  可以把Point当作函数类调研 : 
>> >  blank =  Point( ) 
>> >  blank
< __main__. Point object  at 0x00000230EBFBB490 > 
返回值是一个Point对象的引用 ,  它们将它赋值给变量blank . 
新建一个对象的过程称为 '实例化' ( instantiation ) ,  而对象是这个类的一个实例 . 
在打印一个实例时 ,  Python会告诉你它所属的类型 , 
以及存在内存中的位置 ( 前缀 0 x表示后面的数字是十六进制的 ) . 
每个对象都是某个类的实例 ,  所以 '对象' 和 '实例' 这个两个词很多情况下都可以互换 , 
但是在本章中我们使用 '实例' 来表示一个自定义类型的对象 . 
可以使用句点表示法给实例赋值 : 
>> >  blank. x =  3.0 
>> >  blank. y =  4.0 
这个语法和从模块中选择变量的语法类似 ,  如math . pi或者strings . whitespace . 
但在种情况下 ,  我们是将值赋值给一个对象的有命名的元元素 .  这些元素称为属性 ( attribute ) . 
作为名词时 ,  'AT-trib-ute' 发音的重音在第一个音节 ,  这与作为动词的 'a-TRIB-ute' 不同 . 
下面的图标展示了这些赋值的结果 . 
展示一个对象和其属性的状态图称为 '对象图' ( object  diagram ) ,  参见图 15 - 1. 
 
变量blank引用一个Point对象 ,  它包含了两个属性 .  每个属性引用一个浮点数 . 
可以使用相同的语法来读取一个属性的值 . 
>> >  blank. y
4.0 
>> >  x =  blank. x
>> >  x
3.0 
表达式blank . x表示 ,  '找打blank引用的对象, 并取得它的x属性的值' . 
在这个例子中 ,  我们将那个值  赋值给一个变量x .  变量x和属性x并不冲突 . 
可以在任意表达式中使用句点表示法 .  例如 : 
>> >  '(%g, %g)'  %  ( blank. x,  blank. y) 
'(3, 4)' 
>> >  import  math
>> >  distance =  math. sqrt( blank. x **  2  +  blank. y **  2 ) 
>> >  distance
5.0 
可以将一个实例作为实参按通常的方式传递 .  例如 : 
def  print_point ( p) : 
	print ( '(%g, %g)'  %  ( p. x,  p. y) ) 
    
print_point接收一个点作为形参 ,  并按照属性表达式展示它 . 
可以传入blank作为实参来调用它 : 
>> >  print_point( blank) 
( 3 ,  4 ) 
在函数中 ,  p是blank的一个别名 ,  所以如果函数修改了p ,  则blank也会被修改 . 
作为练习 ,  编写一个叫作distance_between_points的函数 ,  
接收两个Point对象作为形参 ,  并返回它们之间的距离 . 
第一个坐标 ( x1 = 3 ,  y1 = 4 ) 
第二个坐标 ( x2 = 5 ,  y2 = 6 ) 
计算公式 : ( ✓根号 ) 
| AB |  =  ✓ ( x1  -  x2 )  * *  2  +  ( y1  -  y2 )  * *  2 
或 : 
| AB |  =  ( x1  -  x2 )  * *  2  +  ( y1  -  y2 )  * *  2  * *  0.5 
import  math
class  Point : 
    """自定义对象""" 
    x =  None 
    y =  None 
blank1 =  Point( ) 
blank1. x =  3.0 
blank1. y =  4.0 
blank2 =  Point( ) 
blank2. x =  5.0 
blank2. y =  6.0 
def  distance_between_points ( p1,  p2) : 
    """
    计算两个Point对象之间的距离,
    :param p1: 第一个Point对象.
    :param p2: 第二个Point对象.
    :return: 两个Point对象之间的距离.
    """ 
    px =  p1. x -  p2. x
    py =  p1. y -  p2. y
    
    return  ( ( p1. x -  p2. x)  **  2  +  ( p1. y -  p2. y)  **  2 )  **  0.5 
res =  distance_between_points( blank1,  blank2) 
print ( res)   
有时候对象因该有哪些属性非常明显 ,  但也有时候需要你来做决定 , 
例如 ,  假设你子啊设计一个表达矩形的类 .  你会用什么属性来指定一个矩形的位置和尺寸呢? 
可以忽视角度 ,  为了简单起见 ,  假定矩形不是垂直的就是水平的 . 
最少有以下两种可能 . 
*  可以指定一个矩形的一个角落 ( 或者中心点 ) ,  宽度以及高度 . 
*  可以指定两个相对的角落 . 
现在还很难说哪一种方案更好 ,  所以作为示例 ,  我们仅限实现第一个 . 
class  Rectangle : 
    """Represents a rectangle.
    attributes: width, height, corner.
    """ 
    
文档字符列出了属性 :  width和height是数字 ,  用来指定左下角的顶点 . 
要表达一个矩形 ,  需要实例化一个Rectangle对象 ,  并对其属性赋值 : 
box =  Rectangle( ) 
box. width =  100.0 
box. height =  200.0 
box. corner =  Ponint( ) 
box. corner. x =  0.0 
box. corner. y =  0.0 
表达式box . corner . x表示 ,  '去往box引用的对象, 并选择属性corner; 接着去往过那个对象, 并选择属性x' . 
图 15 - 2 展示了这个对象的状态 .  作为另一个对象的属性存在的对象是 '内嵌' 的 . 
 
函数可以返回实例 .  
例如 ,  find_center接收一个Rectangle对象作为参数 ,  并返回一个Point对象 , 
包含这个Rectangle的中心点的坐标 : 
def  find_center ( rect) : 
	p =  Point( ) 
	p. x =  rect. corner. x +  rect. width /  2 
	p. y =  rect. corner. y +  rect. height /  2 
	return  p
	
下面是一个示例 ,  传入box作为实参 ,  并将结果的point对象赋值给center : 
>> >  center =  find_center( box) 
>> >  print_point( center) 
( 50 ,  100 ) 
def  print_point ( p) : 
    print ( '(%g, %g)'  %  ( p. x,  p. y) ) 
def  find_center ( rect) : 
    p =  Point( ) 
    p. x =  rect. corner. x +  rect. width /  2 
    p. y =  rect. corner. y +  rect. height /  2 
    
    return  p
class  Point : 
    """ Represents a point in 2-D space. """ 
class  Rectangle : 
    """Represents a rectangle.
    attributes: width, height, corner.
    """ 
if  __name__ ==  '__main__' : 
    box =  Rectangle( ) 
    box. width =  100.0 
    box. height =  200.0 
    
    box. corner =  Point( ) 
    box. corner. x =  0.0 
    box. corner. y =  0.0 
    
    center =  find_center( box) 
    print_point( center)   
可以通过一个对象的某个属性赋值来修改它的状态 . 
例如 ,  要修改一个矩形的尺寸而保持它的位置不变 ( 左下角坐标为 0 ,  0 不变 . ) , 
可以修改属性width和height的值 : 
box. width =  box. width +  50 
box. height =  box. width. height +  100 
也可以编写函数来修改对象 . 
例如 ,  grow_rectangle接收一个Rectangle对象和两个数 ,  dwidth ,  dheight , 
并把这些数加到矩形的宽度和高度上 : 
def  grow_revtangle ( rect,  dwidth,  dheight) : 
	rect. width +=  dwight
	rect. height +=  dheight
	
下面是展示这个函数效果的实例 : 
>> >  box. width,  box. height
( 150.0 ,  300.0 ) 
>> >  grow_rectangle( box,  50 ,  100 ) 
>> >  box. width,  box. height
( 200.0 ,  400.0 ) 
在函数中 ,  rect是box的别名 ,  所以如果当修改了revt时 ,  box也改变 . 
作为练习 ,  编写一个名为move_rectangle的函数 ,  接收一个Rectangle对象和两个分别名为dx和dy的数值 . 
它应当通过将dx添加到corner的x坐标和将dy添加到corner的y坐标来改变矩形的位置 . 
def  print_point ( p) : 
    print ( '(%g, %g)'  %  ( p. x,  p. y) ) 
def  find_center ( rect) : 
    p =  Point( ) 
    p. x =  rect. corner. x +  rect. width /  2 
    p. y =  rect. corner. y +  rect. height /  2 
    
    return  p
def  move_rectangle ( rect,  dx,  dy) : 
    rect. corner. x +=  dx
    rect. corner. y +=  dy
class  Point : 
    """ Represents a point in 2-D space. """ 
class  Rectangle : 
    """Represents a rectangle.
    attributes: width, height, corner.
    """ 
if  __name__ ==  '__main__' : 
    box =  Rectangle( ) 
    box. width =  100.0 
    box. height =  200.0 
    
    box. corner =  Point( ) 
    box. corner. x =  0.0 
    box. corner. y =  0.0 
    
    print_point( box. corner)   
    
    move_rectangle( box,  100 ,  100 ) 
    print_point( box. corner)   
别名的使用有时候会让程序更难阅读 ,  因为一个地方的修改可能会给其他地方带来意想不到的变化 . 
要跟踪掌握所有引用到一个给定对象的变量非常困难 . 
使用别名的常用替代方案是复制对象 .  copy模块里有一个函数copy可以复制任何对象 : 
>> >  p1 =  Point( ) 
>> >  p1. x =  3.0 
>> >  p1. y =  4.0 
>> >  import  copy
>> >  p2 =  copy. copy( p1) 
p1和p2包含相同的数据 ,  但是它们不是同一个Point对象 . 
>> >  print_point( p1) 
( 3 ,  4 ) 
>> >  print_point( p2) 
( 3 ,  4 ) 
>> >  p1 is  p2
False 
>> >  p1 ==  p2
False 
正如我们预料 ,  is操作符告诉我们p1和p2不是同一个对象 . 
但你可能会预料 = = 能得到True值 ,  因为这两个点 ( 坐标点 ) 包含相同的数据 . 
如果那样 ,  你会失望地发现对于实例来说 ,  = = 操作符的默认行为和is操作符相同 , 
它会检查对象同一性 ,  而不是对象相等性 . 
这是因为对于用户自定义类型 ,  Python并不知道怎么才算相等 .  至少现在还不行 . 
对象同一性 ( object  identity ) :  当两个引用类型的变量存储的地址相同时 ,  它们引用的是同一个对象 . 
在Python中 ,  = = 操作符的默认行为是检查两个对象的值是否相等 . 
而is操作符则检查两个对象是否是同一个对象 , 即它们是否具有相同的内存地址。 
对于内置类型 ( 例如整数、浮点数、字符串等 ) Python已经定义了如何判断相等性 . 
但对于自定义类型 ,  Python不会自动判断相等性 ,  因为它不知道如何判断两个对象是否相等 . 
因此 ,  如果你定义了自己的类 ,  你需要自己定义__eq__ ( ) 方法来定义该类的相等性行为 . 
在这种情况下 ,  = = 操作符将使用您定义的__eq__ ( ) 方法进行比较 . 
需要注意的是 ,  即使您定义了__eq__ ( ) 方法 ,  使用is操作符也不会调用该方法 , 
因为is操作符只检查两个对象是否具有相同的内存地址 . 
如果使用copy . copy复制一个Rectangle ,  你会发现它复制了Rectangle对象但并不复制内嵌的Point对象 : 
>> >  box2 =  copy. copy( box) 
>> >  box2 is  box
False 
>> >  box2. corner is  box. corner
True 
图 15 - 3 展示了这个操作的对象图 . 
这个操作成为浅复制 ( shallow  copy ) ,  因为它复制对象及其包含的任何引用 ,  但不复制内嵌对象 . 
( 复制了内嵌对象的引用 ,  两个Recrangle对象的corner属性共用一个内嵌对象的引用 , 
哪个Recrangle对象对内嵌对象做了改动都会影响另一个Recrangle对象 . ) 
 
对大多数应用 ,  这并不是你所想要的 . 
在这个例子里 ,  对一个Recrangle对象调用grow_rectangle并不影响其他对象 , 
当对任何Recrangle对象调用move_rectangle都会影响全部两个对象 ! 
这种行为即混乱不清 ,  又容易导致错误 . 
幸好 ,  copy模块还提供了一个名为deepcopy的方法 ,  它不但赋值对象 ,  还会复制对象中引用的对象 , 
甚至它们引用的对象 ,  以此类推 . 
所以你并不会惊讶这个操作为何称为深复制 ( deep  copy ) . 
>> >  box3 =  copy. deepcopy( box) 
>> >  box3 is  box
False 
>> >  box3. corner is  box. corner
False 
box3个box是两个完全分开的对象 . 
作为练习 ,  编写move_rectangle的另一个版本 ,  它会新建并返回一个Rectangle对象 ,  而不是直接修改旧对象 . 
import  copy
def  print_point ( p) : 
    print ( '(%g, %g)'  %  ( p. x,  p. y) ) 
def  find_center ( rect) : 
    p =  Point( ) 
    p. x =  rect. corner. x +  rect. width /  2 
    p. y =  rect. corner. y +  rect. height /  2 
    
    return  p
def  move_rectangle ( rect,  dx,  dy) : 
    
    box2 =  copy. deepcopy( rect) 
    box2. corner. x +=  dx
    box2. corner. y +=  dy
    return  box2
class  Point : 
    """ Represents a point in 2-D space. """ 
class  Rectangle : 
    """Represents a rectangle.
    attributes: width, height, corner.
    """ 
if  __name__ ==  '__main__' : 
    box =  Rectangle( ) 
    box. width =  100.0 
    box. height =  200.0 
    
    box. corner =  Point( ) 
    box. corner. x =  0.0 
    box. corner. y =  0.0 
    
    box2 =  move_rectangle( box,  100 ,  100 ) 
    print_point( box2. corner)   
    
    print_point( box. corner)   
开始操作对象时 ,  可能会遇到一些新的异常 . 
如果试图访问一个并不存在的属性 ,  会得到AttrbuteErroe : 
>> >  p =  Point( ) 
>> >  p. x =  3 
>> >  p. y =  4 
>> >  p. z
AttributeError:  'Point'  object  has no attribute 'z' 
属性错误:  'Point' 对象没有属性'z' 
AttributeError:  Point instance has no attribute 'z' 
属性错误:  'Point' 实例没有属性“z”
如果不清楚一个对象是什么类型 ,  可以问 : 
>> >  type ( p) 
< class  '__main__.Point' > 
< type  'instance' > 
如果不确定一个对象是否拥有某个特定的属性 ,  可以使用内置函数hasatter : 
>> >  hasattr ( p,  'x' ) 
True 
>> >  hasattr ( p,  'z' ) 
False 
第一个情形可以是任何对象 ,  第二个形参是一个包含属性名称的字符串 . 
也可以使用try语句来尝试对象是否拥有你需要的属性 : 
try : 
	x =  p. x
except  AttributeEeeor: 
	X =  0 
	
这种方法可以使编写适用于不同类型的函数更加容易 .  
关于这一主题的更多内容参见 17.9 节 . 
类 ( class ) :  一个用户定义的类型 .  类定义会新建一个类对象 . 
类对象 ( class  object ) :  一个包含用户定义类的信息的对象 .  类对象可以用来创建改类型的实例 . 
实例 ( instance ) :  属于某个类的一个对象 . 
属性 ( sttribute ) :  一个对象中关联的有命名的值 . 
内嵌对象 ( embedded  object ) :  作为一个对象的属性存储的对象 . 
浅复制 ( shallow  copy ) :  复制对象的内容 ,  包括内嵌对象的引用 ;  copy模块中的copy函数实现了这个功能 . 
深复制 ( deep  copy ) :  复制对象的内容 ,  也包括内嵌对象 ,  以及它们内嵌的对象 ,  依次类推 ; 
	copy模块中的deepcopy函数实现了这个功能 . 
对象图 ( object  diagram ) :  一个展示对象 ,  对象的属性以及属性的值的图 . 
定义一个新的名为Circle的类表示圆形 ,  它的属性有center和radius ,  
其中center ( 中心坐标 ) 是一个Point对象 ,  而radius ( 半径 ) 是一个数 . 
实例化一个Circle对象来代表一个圆心在 ( 150 ,  100 ) ,  半径为 75 的圆形 . 
编写一个函数point_in_circle ,  接收一个Circle对象和一个Point对象 ,  
并当Point处于Circle的边界或其内时返回True . 
编写一个函数cert_in_circle ,  接收一个Circle对象和一个Rectangle对象 ,  
并在Rectangle的任何一个角落在Circle之内是返回True . 
另外 ,  还有一个更难的版本 ,  需要在Rectangle的任何部分都落在圆圈之内时返回True . 
解答 :  https : / / github . com / AllenDowney / ThinkPython2 / blob / master / code / Circle . py 
如何判断一个坐标是否在圆内 : 
1.  圆心到这个点的距离小于圆的半径 ,  则这个点在圆内 . 
2.  圆心到这个点的距离等于圆的半径 ,  则这个点在圆周上 . 
3.  圆心到这个点的距离大于圆的半径 ,  则这个点在圆外 . 
计算两个坐标的距离 : 
| AB |  =  ( x1  -  x2 )  * *  2  +  ( y1  -  y2 )  * *  2  * *  0.5 
class  Point : 
    """坐标""" 
    x =  None 
    y =  None 
class  Circle : 
    """圆""" 
class  Rectangle : 
    """矩形""" 
def  distance_between_points ( p1,  p2) : 
    """
    计算两个Point对象之间的距离,
    :param p1: 第一个Point对象.
    :param p2: 第二个Point对象.
    :return: 两个Point对象之间的距离.
    """ 
    return  ( ( p1. x -  p2. x)  **  2  +  ( p1. y -  p2. y)  **  2 )  **  0.5 
def  point_in_circle ( cir,  p) : 
    
    distance =  distance_between_points( cir. center,  p) 
    
    print ( '输入的坐标离圆的距离为:%d,'  %  distance,  end= '\t' ) 
    if  distance <=  cir. radius: 
        return  True 
def  cert_in_circle ( cir,  rect) : 
    
    
    bottom_left =  rect. corner
    
    upper_left =  Point( ) 
    upper_left. x =  rect. width
    upper_left. y =  bottom_left. y +  rect. height
    
    bottom_right =  Point( ) 
    bottom_right. y =  rect. height
    bottom_right. x =  bottom_left. x +  rect. width
    
    top_right =  Point( ) 
    
    top_right. x =  upper_left. x +  rect. width
    top_right. y =  upper_left. y
    
    corner_list =  [ ( '左下角' ,  bottom_left) ,  ( '左上角' ,  upper_left) , 
                   ( '右下角' ,  bottom_right) ,  ( '右上角' ,  top_right) ] 
    count =  0 
    for  corner_name,  corner_point in  corner_list: 
        distance =  distance_between_points( cir. center,  corner_point) 
        print ( '矩形的%s离圆心的距离为: %d.'  %  ( corner_name,  distance) ,  end= ' ' ) 
        if  distance <=  cir. radius: 
            count +=  1 
            print ( '矩形这个角在圆内!' ) 
        else : 
            print ( '矩形这个角不在圆内!' ) 
    if  count ==  4 : 
        print ( '圆的任何部分都在圆内' ) 
def  main ( p2_obj) : 
    
    round1 =  Circle( ) 
    
    round1. center =  Point( ) 
    round1. center. x =  150 
    round1. center. y =  100 
    
    round1. radius =  75 
    
    is_inside_circle =  point_in_circle( round1,  p2_obj) 
    if  is_inside_circle: 
        print ( '坐标在圆内!' ) 
    else : 
        print ( '坐标不在圆内!' ) 
    
    
    rect =  Rectangle( ) 
    rect. width =  100 
    rect. height =  200 
    rect. corner =  p2
    cert_in_circle( round1,  rect) 
if  __name__ ==  '__main__' : 
    p2 =  Point
    p2. x =  300 
    p2. y =  300 
    main( p2) 
 
编写一个名为draw_rect的函数 ,  接收一个Turtle对象 ,  和一个Rectangle对象组我形参 , 
并使用Turtle来绘制这个Rectangle .  如何使用Turtle对象的示例参见第 4 章 . 
编写一个draw_cect的函数 ,  接收一个Turtle对象和一个Circle对象 ,  并绘制出Circle . 
解答 :  https : / / github . com / AllenDowney / ThinkPython2 / blob / master / code / polygon . py 
import  turtle
from  Point1 import  Point,  Rectangle
import  polygon
class  Circle : 
    """""" 
def  draw_circle ( t,  circle) : 
    t. pu( ) 
    t. goto( circle. center. x,  circle. center. y) 
    t. fd( circle. radius) 
    t. lt( 90 ) 
    t. pd( ) 
    polygon. circle( t,  circle. radius) 
def  draw_rect ( t,  rect) : 
    t. pu( ) 
    t. goto( rect. corner. x,  rect. corner. y) 
    t. setheading( 0 ) 
    t. pd( ) 
    for  length in  rect. width,  rect. height,  rect. width,  rect. height: 
        t. fd( length) 
        t. rt( 90 ) 
if  __name__ ==  '__main__' : 
    bob =  turtle. Turtle( ) 
    length =  400 
    bob. fd( length) 
    bob. bk( length) 
    bob. lt( 90 ) 
    bob. fd( length) 
    bob. bk( length) 
    box =  Rectangle( ) 
    box. width =  100.0 
    box. height =  200.0 
    box. corner =  Point( ) 
    box. corner. x =  50.0 
    box. corner. y =  50.0 
    draw_rect( bob,  box) 
    circle =  Circle
    circle. center =  Point( ) 
    circle. center. x =  150.0 
    circle. center. y =  100.0 
    circle. radius =  75.0 
    draw_circle( bob,  circle) 
    turtle. mainloop( )