15. 第十五章 类和对象

2024-06-16 13:04
文章标签 15 对象 第十五章

本文主要是介绍15. 第十五章 类和对象,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

15. 类和对象

到现在你已经知道如何使用函数组织代码, 以及如何使用内置类型来组织数据.
下一步将学习'面向对象编程', 面向对象编程使用自定义的类型同时组织代码和数据.
面向对象编程是一个很大的话题, 需要好几章来讨论.
本章的代码示例可以从↓下载,
https://github.com/AllenDowney/ThinkPython2/blob/master/code/Point1.py 
练习的解答可以在↓下载.
https://github.com/AllenDowney/ThinkPython2/blob/master/code/Point1_soln.py
15.1 用户定义类型
我们已经使用了很多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会告诉你它所属的类型,
以及存在内存中的位置(前缀0x表示后面的数字是十六进制的).每个对象都是某个类的实例, 所以'对象''实例'这个两个词很多情况下都可以互换,
但是在本章中我们使用'实例'来表示一个自定义类型的对象.
15.2 属性
可以使用句点表示法给实例赋值:
>>> blank.x = 3.0
>>> blank.y = 4.0
这个语法和从模块中选择变量的语法类似, 如math.pi或者strings.whitespace.
但在种情况下, 我们是将值赋值给一个对象的有命名的元元素. 这些元素称为属性(attribute).
作为名词时, 'AT-trib-ute'发音的重音在第一个音节, 这与作为动词的'a-TRIB-ute'不同.下面的图标展示了这些赋值的结果.
展示一个对象和其属性的状态图称为'对象图'(object diagram), 参见图15-1.

2023-04-14_00002

变量blank引用一个Point对象, 它包含了两个属性. 每个属性引用一个浮点数.
可以使用相同的语法来读取一个属性的值.
>>> blank.y
4.0
>>> x = blank.x
>>> x
3.0
表达式blank.x表示, '找打blank引用的对象, 并取得它的x属性的值'.
在这个例子中, 我们将那个值 赋值给一个变量x. 变量x和属性x并不冲突.
可以在任意表达式中使用句点表示法. 例如:
# %g用于打印浮点型数据时,会去掉多余的零,至多保留六位有效数字.
>>> '(%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 = Noney = None# 实例两个对象
blank1 = Point()
blank1.x = 3.0
blank1.y = 4.0blank2 = Point()
blank2.x = 5.0
blank2.y = 6.0def distance_between_points(p1, p2):"""计算两个Point对象之间的距离,:param p1: 第一个Point对象.:param p2: 第二个Point对象.:return: 两个Point对象之间的距离."""px = p1.x - p2.xpy = p1.y - p2.y# return math.sqrt(px ** 2 + py ** 2)return ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5res = distance_between_points(blank1, blank2)
print(res)  # 2.8284271247461903
15.3 矩形
有时候对象因该有哪些属性非常明显, 但也有时候需要你来做决定,
例如, 假设你子啊设计一个表达矩形的类. 你会用什么属性来指定一个矩形的位置和尺寸呢?
可以忽视角度, 为了简单起见, 假定矩形不是垂直的就是水平的.
最少有以下两种可能.
* 可以指定一个矩形的一个角落(或者中心点), 宽度以及高度.
* 可以指定两个相对的角落.
现在还很难说哪一种方案更好, 所以作为示例, 我们仅限实现第一个.
class Rectangle:"""Represents a rectangle.attributes: width, height, corner."""
文档字符列出了属性: width和height是数字, 用来指定左下角的顶点.
要表达一个矩形, 需要实例化一个Rectangle对象, 并对其属性赋值:
box = Rectangle()
# 设置宽
box.width = 100.0
# 设置高
box.height = 200.0
# 坐标对象 (0, 0)
box.corner = Ponint()
box.corner.x = 0.0
box.corner.y = 0.0
表达式box.corner.x表示, '去往box引用的对象, 并选择属性corner; 接着去往过那个对象, 并选择属性x'.
15-2展示了这个对象的状态. 作为另一个对象的属性存在的对象是'内嵌'.

2023-04-15_00001

15.4 作为返回值得示例
函数可以返回实例. 
例如, find_center接收一个Rectangle对象作为参数, 并返回一个Point对象,
包含这个Rectangle的中心点的坐标:
# 计算矩形的中心点.
def find_center(rect):p = Point()p.x = rect.corner.x + rect.width / 2p.y = rect.corner.y + rect.height / 2return 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 / 2p.y = rect.corner.y + rect.height / 2# 实例作为返回值return pclass 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.0box.height = 200.0# 设置坐标box.corner = Point()box.corner.x = 0.0box.corner.y = 0.0center = find_center(box)print_point(center)  # (50, 100)
15.5 对象是可变的
可以通过一个对象的某个属性赋值来修改它的状态.
例如, 要修改一个矩形的尺寸而保持它的位置不变(左下角坐标为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 += dwightrect.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 / 2p.y = rect.corner.y + rect.height / 2# 实例作为返回值return p# 移动坐标
def move_rectangle(rect, dx, dy):rect.corner.x += dxrect.corner.y += dyclass 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.0box.height = 200.0# 设置坐标box.corner = Point()box.corner.x = 0.0box.corner.y = 0.0# 打印坐标print_point(box.corner)  # (0, 0)# 移动坐标move_rectangle(box, 100, 100)print_point(box.corner)  # (100, 100)
15.6 复制
别名的使用有时候会让程序更难阅读, 因为一个地方的修改可能会给其他地方带来意想不到的变化.
要跟踪掌握所有引用到一个给定对象的变量非常困难.
使用别名的常用替代方案是复制对象. 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
# corner引用内嵌对象
>>> box2.corner is box.corner
True
15-3展示了这个操作的对象图.
这个操作成为浅复制(shallow copy), 因为它复制对象及其包含的任何引用, 但不复制内嵌对象.
(复制了内嵌对象的引用, 两个Recrangle对象的corner属性共用一个内嵌对象的引用,
哪个Recrangle对象对内嵌对象做了改动都会影响另一个Recrangle对象.)

2023-04-15_00005

对大多数应用, 这并不是你所想要的.
在这个例子里, 对一个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 / 2p.y = rect.corner.y + rect.height / 2# 实例作为返回值return p# 移动坐标
def move_rectangle(rect, dx, dy):# 深复制一个Rectangle对象box2 = copy.deepcopy(rect)box2.corner.x += dxbox2.corner.y += dyreturn box2class 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.0box.height = 200.0# 设置坐标box.corner = Point()box.corner.x = 0.0box.corner.y = 0.0# 深复制一个矩形对象, 移动新矩形对象的坐标, 并返回新的矩形对象.box2 = move_rectangle(box, 100, 100)print_point(box2.corner)  # (100, 100)# 旧矩形对象不变print_point(box.corner)  # (0, 0)
15.7 调试
开始操作对象时, 可能会遇到一些新的异常.
如果试图访问一个并不存在的属性, 会得到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'># 2.7版本, 显示 instance实例
<type 'instance'>
如果不确定一个对象是否拥有某个特定的属性, 可以使用内置函数hasatter:
>>> hasattr(p, 'x')
True
>>> hasattr(p, 'z')
False
第一个情形可以是任何对象, 第二个形参是一个包含属性名称的字符串.
也可以使用try语句来尝试对象是否拥有你需要的属性:
try:x = p.x
except AttributeEeeor:X = 0
这种方法可以使编写适用于不同类型的函数更加容易. 
关于这一主题的更多内容参见17.9.
15.8 术语表
(class): 一个用户定义的类型. 类定义会新建一个类对象.类对象(class object): 一个包含用户定义类的信息的对象. 类对象可以用来创建改类型的实例.实例(instance): 属于某个类的一个对象.属性(sttribute): 一个对象中关联的有命名的值.内嵌对象(embedded object): 作为一个对象的属性存储的对象.浅复制(shallow copy): 复制对象的内容, 包括内嵌对象的引用; copy模块中的copy函数实现了这个功能.深复制(deep copy): 复制对象的内容, 也包括内嵌对象, 以及它们内嵌的对象, 依次类推;copy模块中的deepcopy函数实现了这个功能.对象图(object diagram): 一个展示对象, 对象的属性以及属性的值的图.
15.9 练习
1. 练习1
定义一个新的名为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 = Noney = Noneclass 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.5def point_in_circle(cir, p):# 计算两个坐标之间的距离.distance = distance_between_points(cir.center, p)# 距离是否在圆的半径内print('输入的坐标离圆的距离为:%d,' % distance, end='\t')if distance <= cir.radius:return Truedef cert_in_circle(cir, rect):# 计算矩形的四个角的坐标离圆心坐标的距离# 左下角就是矩形的坐标bottom_left = rect.corner# 左上角, y + heightupper_left = Point()upper_left.x = rect.widthupper_left.y = bottom_left.y + rect.height# 右下角 x + widthbottom_right = Point()bottom_right.y = rect.heightbottom_right.x = bottom_left.x + rect.width# 右上角top_right = Point()# x = 左上角加widthtop_right.x = upper_left.x + rect.widthtop_right.y = upper_left.y# 计算四个角的是否在圆内corner_list = [('左下角', bottom_left), ('左上角', upper_left),('右下角', bottom_right), ('右上角', top_right)]count = 0for 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 += 1print('矩形这个角在圆内!')else:print('矩形这个角不在圆内!')if count == 4:print('圆的任何部分都在圆内')def main(p2_obj):# 实例化对象得到一个圆round1 = Circle()# 设置圆的坐标150.0round1.center = Point()round1.center.x = 150round1.center.y = 100# 设置圆的半径75round1.radius = 75# 判断坐标是否在圆内.is_inside_circle = point_in_circle(round1, p2_obj)if is_inside_circle:print('坐标在圆内!')else:print('坐标不在圆内!')# 输入的矩形的任何一个角是否在圆内# 实例化一个矩形对象rect = Rectangle()rect.width = 100rect.height = 200rect.corner = p2cert_in_circle(round1, rect)if __name__ == '__main__':p2 = Pointp2.x = 300p2.y = 300main(p2)

2023-04-16_00002

2. 练习2
编写一个名为draw_rect的函数, 接收一个Turtle对象, 和一个Rectangle对象组我形参,
并使用Turtle来绘制这个Rectangle. 如何使用Turtle对象的示例参见第4.编写一个draw_cect的函数, 接收一个Turtle对象和一个Circle对象, 并绘制出Circle.
解答: https://github.com/AllenDowney/ThinkPython2/blob/master/code/polygon.py
import turtlefrom Point1 import Point, Rectangle
# polygon.py 文件以及写的练习文件.
import polygonclass 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 = 400bob.fd(length)bob.bk(length)bob.lt(90)bob.fd(length)bob.bk(length)box = Rectangle()box.width = 100.0box.height = 200.0box.corner = Point()box.corner.x = 50.0box.corner.y = 50.0draw_rect(bob, box)circle = Circlecircle.center = Point()circle.center.x = 150.0circle.center.y = 100.0circle.radius = 75.0draw_circle(bob, circle)turtle.mainloop()

这篇关于15. 第十五章 类和对象的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066530

相关文章

Ilya-AI分享的他在OpenAI学习到的15个提示工程技巧

Ilya(不是本人,claude AI)在社交媒体上分享了他在OpenAI学习到的15个Prompt撰写技巧。 以下是详细的内容: 提示精确化:在编写提示时,力求表达清晰准确。清楚地阐述任务需求和概念定义至关重要。例:不用"分析文本",而用"判断这段话的情感倾向:积极、消极还是中性"。 快速迭代:善于快速连续调整提示。熟练的提示工程师能够灵活地进行多轮优化。例:从"总结文章"到"用

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

Java第二阶段---09类和对象---第三节 构造方法

第三节 构造方法 1.概念 构造方法是一种特殊的方法,主要用于创建对象以及完成对象的属性初始化操作。构造方法不能被对象调用。 2.语法 //[]中内容可有可无 访问修饰符 类名([参数列表]){ } 3.示例 public class Car {     //车特征(属性)     public String name;//车名   可以直接拿来用 说明它有初始值     pu

Adblock Plus官方规则Easylist China说明与反馈贴(2015.12.15)

-------------------------------特别说明--------------------------------------- 视频广告问题:因Adblock Plus的局限,存在以下现象,优酷、搜狐、17173黑屏并倒数;乐视、爱奇艺播放广告。因为这些视频网站的Flash播放器被植入了检测代码,而Adblock Plus无法修改播放器。 如需同时使用ads

HTML5自定义属性对象Dataset

原文转自HTML5自定义属性对象Dataset简介 一、html5 自定义属性介绍 之前翻译的“你必须知道的28个HTML5特征、窍门和技术”一文中对于HTML5中自定义合法属性data-已经做过些介绍,就是在HTML5中我们可以使用data-前缀设置我们需要的自定义属性,来进行一些数据的存放,例如我们要在一个文字按钮上存放相对应的id: <a href="javascript:" d

PHP7扩展开发之对象方式使用lib库

前言 上一篇文章,我们使用的是函数方式调用lib库。这篇文章我们将使用对象的方式调用lib库。调用代码如下: <?php $hello = new hello(); $result = $hello->get(); var_dump($result); ?> 我们将在扩展中实现hello类。hello类中将依赖lib库。 代码 基础代码 这个扩展,我们将在say扩展上增加相关代码。sa

hibernate修改数据库已有的对象【简化操作】

陈科肇 直接上代码: /*** 更新新的数据并并未修改旧的数据* @param oldEntity 数据库存在的实体* @param newEntity 更改后的实体* @throws IllegalAccessException * @throws IllegalArgumentException */public void updateNew(T oldEntity,T newEntity

类和对象的定义和调用演示(C++)

我习惯把类的定义放在头文件中 Student.h #define _CRT_SECURE_NO_WARNINGS#include <string>using namespace std;class student{public:char m_name[25];int m_age;int m_score;char* get_name(){return m_name;}int set_name

15 组件的切换和对组件的data的使用

划重点 a 标签的使用事件修饰符组件的定义组件的切换:登录 / 注册 泡椒鱼头 :微辣 <!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-

react笔记 8-19 事件对象、获取dom元素、双向绑定

1、事件对象event 通过事件的event对象获取它的dom元素 run=(event)=>{event.target.style="background:yellowgreen" //event的父级为他本身event.target.getAttribute("aid") //这样便获取到了它的自定义属性aid}render() {return (<div><h2>{