python10 类和对象
2020-04-13 本文已影响0人
巴巴11
定义一个叫做 Point 的类将创建了一个类对象(class object)
class Point:
"""Represents a point in 2-D space."""
由于 Point 是定义在顶层的,所以它的“全名”是__main__.Point。
类对象就像是一个用来创建对象的工厂。 要创建一个点,你可以像调用函数那样调用 Point 。
>>> blank = Point()
>>> blank
<__main__.Point object at 0xb7e9d3ac>
属性
可以使用点标记法向一个实例进行赋值操作:
>>> blank.x = 3.0
>>> blank.y = 4.0
使用相同的语法读取一个属性的值:
>>> blank.y
4.0
>>> x = blank.x
>>> x
3.0
可以将一个实例作为参数传递。 例如:
def print_point(p):
print('(%g, %g)' % (p.x, p.y))
print_point接受一个点作为参数,打印出其在数学中的表示方法。 调用它的时候,你可以将 blank 作为参数传递:
>>> print_point(blank)
(3.0, 4.0)
定义矩阵类
class Rectangle:
"""Represents a rectangle.
attributes: width, height, corner.
"""
实例化
box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0
实例作为返回值
def find_center(rect):
p = Point()
p.x = rect.corner.x + rect.width/2
p.y = rect.corner.y + rect.height/2
return p
>>> center = find_center(box)
>>> print_point(center)
(50, 100)
对象可变
box.width = box.width + 50
box.height = box.height + 100
复制
别名会降低程序的可读性,因为一个地方的变动可能对另一个地方造成预料之外的影响。 跟踪所有引用同一个对象的变量是非常困难的。
通常用复制对象的方法取代为对象起别名。 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 运算符相同; 它检查对象的标识(identity)是否相同,而非对象的值是否相同。
如果你使用 copy.copy 来复制一个 Rectangle , 你会发现它仅仅复制了 Rectangle 对象,但没有复制嵌套的 Point 对象。
>>> box2 = copy.copy(box)
>>> box2 is box
False
>>> box2.corner is box.corner
True
这个操作叫做浅复制(shallow copy),因为它仅复制了对象以及其包含的引用, 但未复制嵌套的对象。
copy 模块拥有一个叫做 deepcopy 的方法, 它不仅可以复制一个对象,还可以复制这个对象所引用的对象, 甚至可以复制这个对象所引用的对象所引用的对象,这个操作叫做深复制(deep copy)。
>>> box3 = copy.deepcopy(box)
>>> box3 is box
False
>>> box3.corner is box.corner
False
box3和 box 是完全互不相干的对象。
如果你访问一个不存在的属性,你会得到 Attributeerror 的错误提示:
>>> p = Point()
>>> p.x = 3
>>> p.y = 4
>>> p.z
AttributeError: Point instance has no attribute 'z'
如果你不确定一个对象的类型,你可以询问:
>>> type(p)
<class '__main__.Point'>
你也可以用 isinstance 来检查某个对象是不是某个类的实例。
>>> isinstance(p, Point)
True
如果你不确定一个对象是否拥有某个属性, 你可以使用内置函数 hasattr 检查:
>>> hasattr(p, 'x')
True
>>> hasattr(p, 'z')
False
第一个参数可以是任何对象; 第二个参数是一个字符串,代表了某个属性的名字。
你也可以使用 try 语句来检查某个对象是不是有你需要的属性:
try:
x = p.x
except AttributeError:
x = 0
Python 是一门面向对象的编程语言
类和函数:
class Time:
"""Represents the time of day."""
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
类和方法:
将 print_time 变成一个方法,我们只需要将函数定义移到类定义里面即可。注意缩进 的变化。
class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
现在有两种方法可以调用print_time。第一种(也是不常用的)是使用函数的语法:
>>> Time.print_time(start)
09:45:00
在这个点标记法的用法中,Time 是类的名字,print_time是方法的名字。start 是传递的参数。
第二种语法(也更简洁)是使用方法语法:
>>> start.print_time()
09:45:00
在这个点标记法的用法中,print_time是方法的名称,然后 start 是调用方法的对象 ,被称为主语(subject)。就像一个句子的主语是句子的核心,方法的主语也是方 法作用的主要对象。
在方法中,主语被赋值为第一个参数,所以在这里 start 被赋值给 time 上了。
根据约定,方法的第一个参数写作 self ,所以print_time写成这样更常见:
class Time:
def print_time(self):
print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
init方法:
是一个特殊的方法,当一个对象初始化的时候调 用。
# inside class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
通常__init__方法的参数和属性的名称一样。
self.hour = hour
str方法:
特殊方法,返回一个对象的字符串表现形式
# inside class Time:
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
当你打印一个对象,Python 调用 str 方法:
>>> time = Time(9, 45)
>>> print(time)
09:45:00
运算符重载:
通过定义其它的一些特殊方法,你可以在程序员自定义类型上指定运算符的行为。 例如,如果你为 Time 类定义了一个叫__add__的方法,你就可以在 Time 对象上使用 + 运算符。
可以大致像这样定义:
# inside class Time:
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
下面是使用方式:
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
当你在 Time 对象上应用 + 运算符,Python 会调用__add__。 当你打印结果时,Python 会调用__str__。
类型分发(type-based dispatch)
在上一节中,我们将两个 Time 对象相加,但是你还会想要将一个整数与 Time 对象相加。下面这个版本的 add 会检查 other 的类型,并相应地调用 add_time 或者 increment :
# inside class Time:
def __add__(self, other):
if isinstance(other, Time):
return self.add_time(other)
else:
return self.increment(other)
def add_time(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
内建函数 isinstance 接受一个值和一个类对象,如果值是这个类的实例则返回 True 。
如果 other 是一个 Time 对象,__add__调用add_time。 否则它假设参数是一个数字然后调用 increment 。 这个操作被称为类型分发(type-based dispatch),因为它根据参数的 类型将计算任务分发给不同的方法。
下面是一些在不同类型上使用 + 运算符的例子:
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
>>> print(start + 1337)
10:07:17
不幸的是,这个加法的实现没有交换性(commutative)。如果第一个运算数是一个整数,你会得到:
>>> print(1337 + start)
TypeError: unsupported operand type(s) for +: 'int' and 'instance'
问题在于,我们不是让一个 Time 对象去加一个整数,而是让一个整数去加一个 Time 对 象,但是Python不知道怎样去做。不过这个问题有一个优雅的解决方案:特殊方法 __radd__ ,表示“右手加法”。当一个 Time 对象在 + 运算符的右手边出现时,调用这个方法。下面是定义:
# inside class Time:
def __radd__(self, other):
return self.__add__(other)
接着是使用方法:
>>> print(1337 + start)
10:07:17
如果你不确定一个对象是否应该有某个属性,你可以使用内建函数 `hasattr` (参见[调试](https://codingpy.com/books/thinkpython2/15-classes-and-objects.html#hasattr)一节)。
另一种访问对象属性的方法是使用内建函数 `vars` ,它接受一个对象,并返回一个将属性名称(字符串形式)到对应值的字典:
>>> p = Point(3, 4)
>>> vars(p)
{'y': 4, 'x': 3}
</pre>
定义下面这段代码,可能对调试非常有用:
def print_attributes(obj):
for attr in vars(obj):
print(attr, getattr(obj, attr))
</pre>
`print_attributes`遍历一个对象的字典,然后打印每个属性的名称和对应的值。
内建函数 `getattr` 接受一个对象和一个属性名称(字符串)作为参数,然后返回该属性的值。