5-2-类代码编写基础
1. 类产生多个实例对象
1.1 类对象提供默认行为
Python 类主要特性的要点:
- class 语句创建类对象并将其赋值给变量名。
- class 语句内的赋值语句会创建类的属性。
- 类属性提供对象的状态和行为。
1.2 实例对象是具体的元素
类的实例内含的重点概要:
- 像函数那样调用类对象会创建新的实例对象。
- 每个实例对象继承类的属性并获得了自己的命名空间。
- **在方法内对 self 属性做赋值运算会产生每个实例自己的属性。**在类方法函数内,第一个参数(self)会引用正处理的实例对象。
1.3 第一个例子
class FirstClass:
def setdata(self, value): # 定义类方法
self.data = value # self 是实例
def display(self):
print(self.data)
位于类中的函数通常称为方法。方法是普通 def。在方法函数中,调用时,第一个参数自动接收隐含的实例对象:调用的主体。
x = FirstClass() # 创建两个实例
y = FirstClass() # 每个实例都有新的命名空间
如果对实例以及类对象内的属性名称进行点号运算,Python 会通过继承搜索从类取得变量名:
x.setdata('King Arthur') # 调用方法:self 是 x
y.setdata(3.14159) # 运行:FirstClass.setdata(y, 3.14159)
x 或 y 本身都没有 setdata 属性,Python 会顺着实例到类的连接搜索。继承是在属性点号运算时发生的,而且只与查找连接对象内的变量名有关。
x.display() # 每个实例中的 self.data 都不同
y.display()
King Arthur
3.14159
在类内时,通过方法内对 self 进行赋值运算;而在类外时,则可以通过对实例对象进行赋值运算:
x.data = 'New value'
x.display()
New value
可以在实例命名空间内产生全新的属性:
x.anothername = 'spam'
2. 类通过继承进行定制
类可以引入新组件(子类)来进行修改,而不对现有组件进行原地的修改。由类产生的实例对象会继承该类的属性。
属性继承机制的核心观点:
- **超类列在了类开头的括号中。**含有继承的类称为子类,而子类所继承的类就是其超类。
- 类从超类中继承属性。
- 实例会继承所有可读取类的属性。
- 每个 object.attribute 都会开启新的独立搜索。
- **逻辑的修改是通过创建子类,而不是修改超类。**在树中层次较低的子类中重新定义超类的变量名,子类就可取代并制定所继承的行为。
2.1 第二个例子
定义一个新的类 SecondClass,继承 FirstClass 所有变量名,并提供其自己的一个变量名:
class SecondClass(FirstClass):
def display(self): # 更改 display
print('Current value = "%s"' % self.data)
z = SecondClass()
z.setdata(42) # 在 FirstClass 中寻找 setdata
z.display() # 在 SecondClass 中寻找重载的方法
Current value = "42"
SecondClass 引入的专有化完全是在 FirstClass 外部完成的,不会影响当前存在的或未来存在的 FirstClass 对象:
x.display() # x 仍然是 FirstClass 的一个实例
New value
2.2 类是模块内的属性
类名称总是存在于模块中,class 语句会在导入时执行已定义的变量名,而这些变量名会变成独立的模块属性。
3. 类可以截获 Python 运算符
运算符重载就是让用类写成的对象,可截获并响应用在内置类型上的运算:加法、切片、打印和点号运算等。
重载运算符主要概念的概要:
- **以双下划线命名的方法(
__x__
)是特殊的 hook。**Python 语言替每种运算和特殊命名的方法之间,定义了固定不变的映射关系。 - **当实例出现在内置运算时,这类方法会自动调用。**例如,如果实例对象继承了
__add__
方法,当对象出现在 + 表达式内时,该方法就会调用。 - 类可覆盖多数内置类型运算。
- 运算符覆盖方法没有默认值,而且也不需要。
- 运算符可让类与 Python 的对象模型相集成。
运算符重载是可选的功能,除非类需要模仿内置类型接口,不然应该使用更简单的命名方法。
3.1 第三个例子
定义 SecondClass 的子类,实现三个特殊名称的属性,让 Python 自动进行调用:
- 当新的实例构造时,会调用
__init__
(self 是新的 ThirdClass 对象)。 - 当 ThirdClass 实例出现在 + 表达式中时,则会调用
__add__
。 - 当打印一个对象的时候,运行
__str__
。
class ThirdClass(SecondClass):
def __init__(self, value):
self.data = value
def __add__(self, other):
return ThirdClass(self.data + other)
def __str__(self):
return '[ThirdClass:%s]' % self.data
def mul(self, other):
self.data *= other
a = ThirdClass('abc')
a.display()
Current value = "abc"
print(a)
[ThirdClass:abc]
b = a + 'xyz' # __add__:创建一个新实例
b.display() # b 有所有 ThirdClass 的方法
Current value = "abcxyz"
print(b)
[ThirdClass:abcxyz]
a.mul(3)
print(a)
[ThirdClass:abcabcabc]
4. 世界上最简单的 Python 类
class rec: pass # 空命名空间对象
rec.name = 'Bob' # 在 class 语句外,通过赋值变量名给类增加属性
rec.age = 40
print(rec.name)
Bob
类只是独立完备的命名空间,只要有类的引用值,就可以在任何时刻设定或修改其属性。
x = rec() # name 仅在类上存储,x 和 y 继承并获取附加在类上的属性
y = rec()
x.name, y.name
('Bob', 'Bob')
x.name = 'Sue' # 把一个属性赋值给一个实例,就会在该对象内创建(或修改)该属性
rec.name, x.name, y.name
('Bob', 'Sue', 'Bob')
命名空间对象的属性通常是以字典的形式实现的。__dict__
属性是针对大多数基于类的对象的命名空间字典:
list(rec.__dict__.keys())
['__module__', '__dict__', '__weakref__', '__doc__', 'name', 'age']
list(x.__dict__.keys())
['name']
list(y.__dict__.keys())
[]
x 有自己的 name,y 依然是空的。
方法也可以完全独立地在任意类对象的外部创建:
def uppername(obj):
return obj.name.upper() # 只要传入一个带有 name 属性的对象
uppername(x)
'SUE'
当把这个简单函数赋值成类的属性,就会变成方法,可以由任何实例调用:
rec.method = uppername
x.method()
'SUE'
y.method()
'BOB'
4.1 类与字典的关系
利用类来记录属性,可以用不同的实例记录不同的数据,模拟字典的方法:
class rec: pass
rec.name = 'mel'
rec.age = 45
rec.job = 'trainer/writer'