5-2-类代码编写基础

1. 类产生多个实例对象

1.1 类对象提供默认行为

Python 类主要特性的要点:

  • class 语句创建类对象并将其赋值给变量名。
  • class 语句内的赋值语句会创建类的属性。
  • 类属性提供对象的状态和行为。

1.2 实例对象是具体的元素

类的实例内含的重点概要:

  • 像函数那样调用类对象会创建新的实例对象。
  • 每个实例对象继承类的属性并获得了自己的命名空间。
  • **在方法内对 self 属性做赋值运算会产生每个实例自己的属性。**在类方法函数内,第一个参数(self)会引用正处理的实例对象。

1.3 第一个例子

1
2
3
4
5
class FirstClass:                      
def setdata(self, value): # 定义类方法
self.data = value # self 是实例
def display(self):
print(self.data)

位于类中的函数通常称为方法。方法是普通 def。在方法函数中,调用时,第一个参数自动接收隐含的实例对象:调用的主体。

1
2
x = FirstClass()                         # 创建两个实例
y = FirstClass() # 每个实例都有新的命名空间

如果对实例以及类对象内的属性名称进行点号运算,Python 会通过继承搜索从类取得变量名:

1
2
x.setdata('King Arthur')                 # 调用方法:self 是 x
y.setdata(3.14159) # 运行:FirstClass.setdata(y, 3.14159)

x 或 y 本身都没有 setdata 属性,Python 会顺着实例到类的连接搜索。继承是在属性点号运算时发生的,而且只与查找连接对象内的变量名有关。

1
2
x.display()                               # 每个实例中的 self.data 都不同
y.display()
King Arthur
3.14159

在类内时,通过方法内对 self 进行赋值运算;而在类外时,则可以通过对实例对象进行赋值运算:

1
2
x.data = 'New value'
x.display()
New value

可以在实例命名空间内产生全新的属性:

1
x.anothername = 'spam'

2. 类通过继承进行定制

类可以引入新组件(子类)来进行修改,而不对现有组件进行原地的修改。由类产生的实例对象会继承该类的属性。

属性继承机制的核心观点:

  • **超类列在了类开头的括号中。**含有继承的类称为子类,而子类所继承的类就是其超类。
  • 类从超类中继承属性。
  • 实例会继承所有可读取类的属性。
  • 每个 object.attribute 都会开启新的独立搜索。
  • **逻辑的修改是通过创建子类,而不是修改超类。**在树中层次较低的子类中重新定义超类的变量名,子类就可取代并制定所继承的行为。

2.1 第二个例子

定义一个新的类 SecondClass,继承 FirstClass 所有变量名,并提供其自己的一个变量名:

1
2
3
class SecondClass(FirstClass):                      
def display(self): # 更改 display
print('Current value = "%s"' % self.data)
1
2
3
z = SecondClass()
z.setdata(42) # 在 FirstClass 中寻找 setdata
z.display() # 在 SecondClass 中寻找重载的方法
Current value = "42"

SecondClass 引入的专有化完全是在 FirstClass 外部完成的,不会影响当前存在的或未来存在的 FirstClass 对象:

1
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__
1
2
3
4
5
6
7
8
9
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
1
2
a = ThirdClass('abc')
a.display()
Current value = "abc"
1
print(a)
[ThirdClass:abc]
1
2
b = a + 'xyz'                     # __add__:创建一个新实例
b.display() # b 有所有 ThirdClass 的方法
Current value = "abcxyz"
1
print(b)
[ThirdClass:abcxyz]
1
2
a.mul(3)
print(a)
[ThirdClass:abcabcabc]

4. 世界上最简单的 Python 类

1
2
3
4
5
6
class rec: pass             # 空命名空间对象

rec.name = 'Bob' # 在 class 语句外,通过赋值变量名给类增加属性
rec.age = 40

print(rec.name)
Bob

类只是独立完备的命名空间,只要有类的引用值,就可以在任何时刻设定或修改其属性。

1
2
3
x = rec()                   # name 仅在类上存储,x 和 y 继承并获取附加在类上的属性
y = rec()
x.name, y.name
('Bob', 'Bob')
1
2
x.name = 'Sue'              # 把一个属性赋值给一个实例,就会在该对象内创建(或修改)该属性 
rec.name, x.name, y.name
('Bob', 'Sue', 'Bob')

命名空间对象的属性通常是以字典的形式实现的。__dict__ 属性是针对大多数基于类的对象的命名空间字典:

1
list(rec.__dict__.keys())
['__module__', '__dict__', '__weakref__', '__doc__', 'name', 'age']
1
list(x.__dict__.keys())
['name']
1
list(y.__dict__.keys())
[]

x 有自己的 name,y 依然是空的。

方法也可以完全独立地在任意类对象的外部创建:

1
2
3
4
def uppername(obj):
return obj.name.upper() # 只要传入一个带有 name 属性的对象

uppername(x)
'SUE'

当把这个简单函数赋值成类的属性,就会变成方法,可以由任何实例调用:

1
2
rec.method = uppername
x.method()
'SUE'
1
y.method()
'BOB'

4.1 类与字典的关系

利用类来记录属性,可以用不同的实例记录不同的数据,模拟字典的方法:

1
2
3
4
class rec: pass
rec.name = 'mel'
rec.age = 45
rec.job = 'trainer/writer'