这篇教程我们一起了解构造方法和属性。
构造方法在之前的教程中我们已经接触过。
它就是__init()__这个方法。
特别说明:在Python中,这种两侧带有下划线的方法称为魔法方法或特殊方法,它们都有一些特殊的用途。
对象的创建就是通过构造方法来完成的,它的主要功能是完成对象的初始化。
当实例化一个类的对象时,会自动调用构造方法。
构造方法和其他方法一样也可以重写。
不过需要注意,重载构造方法,有的时候会出现问题。
例如,我们在超类中为构造方法定义了某个特性,而在子类中也为构造方法定义了某个特性,这个时候构造方法被重写。
当我们通过子类实例化的对象,调用超类中的特性时,会找不到这个特性,从而引发异常。
示例代码:(错误示例)
class SuperClass: def __init__(self): # 构造方法 self.name = '小楼' class SubClass(SuperClass): def __init__(self): # 重写构造方法 self.learn = 'Python' def say(self): print('我是%s,我在学习%s。' % (self.name, self.learn)) SubClass().say() # 抛出异常
运行上方代码,会抛出异常。
AttributeError: ‘SubClass’ object has no attribute ‘name’
特性错误:’SubClass’类的对象不包含特性 ‘name’
那么,如何解决这个问题?
我们可以使用super函数,通过super函数访问超类的特性。
示例代码:
class SubClass(SuperClass): def __init__(self): # 重写构造方法 super().__init__() self.learn = 'Python' def say(self): print('我是%s,我在学习%s。' % (self.name, self.learn)) SubClass().say() # 显示输出结果为:我是小楼,我在学习Python。
并且,不管有多少个超类,都只需要一个super()函数,就能够访问所有超类的特性。
但是,需要为每一个超类的构造方法使用super()函数才可以。
class SuperClass1: def __init__(self): # 构造方法 super().__init__() # 调用super函数 self.name = '小楼' class SuperClass2: def __init__(self): super().__init__() # 调用super函数 self.sex = '帅哥' class SubClass(SuperClass1, SuperClass2): def __init__(self): # 重写构造方法 super().__init__() # 调用super函数 self.learn = 'Python' def say(self): print('我是%s%s,我在学习%s。' % (self.sex, self.name, self.learn)) SubClass().say() # 显示输出结果为:我是帅哥小楼,我在学习Python。
接下来,我们了解什么是属性。
在之前的教程中,我们接触过访问器,就是类中定义的get_name()和set_name()这样的函数,用于控制特性的设置和读取。
这样定义的函数,我们是希望能够在设置和读取特性时,能够做一些其他的事情。
示例代码:
class Shape: # 定义形状类 def get_height(self): # 定义获取特性的函数 return self.height # 返回特性值 def set_height(self, height): # 定义修改特性的函数 if isinstance(height, (int, float)): # 验证参数类型 self.height = height # 设置特性值 else: raise TypeError # 引发类型异常
上方代码中,我们在修改特性时,加入了对参数类型验证的语句,如果输入的参数是整数或小数,能够修改特性的值,否则,就会引发异常。
特别说明:代码中除非必须尽量回避isinstance()函数,它不是多态的。
不过,并不是所有的特性都会通过私有的访问器进行访问的控制。
接下来,我们再来看一段代码。
对于立方体来说,有长、宽、高的特性,如果我们提供了体积的元组,这些长宽高的特性能够动态获取。
示例代码:
class Cube: # 定义立方体类 def __init__(self): self.length = 0 self.width = 0 self.height = 0 def set_cubage(self, cubage): # 定义修改特性的函数 if len([i for i in cubage if isinstance(i, (int, float))]) == 3: # 验证参数类型,验证通过的数值形成列表。 self.length, self.width, self.height = cubage # 设置特性值 else: raise TypeError # 引发类型异常 def get_cubage(self): # 定义获取特性的函数 return self.length, self.width, self.height # 返回特性值 cube = Cube() cube.set_cubage((10, 10, 10)) print(cube.get_cubage()) # 显示输出结果为:(10, 10, 10) cube.length = 20 print(cube.get_cubage()) # 显示输出结果为:(20, 10, 10)
在上方这段代码中,体积不是特性。
如果我们想把体积也变成特性,那么,需要将体积特性放在访问器。
示例代码:(错误示例)
def set_cubage(self, cubage): if len([i for i in cubage if isinstance(i, (int, float))]) == 3: self.cubage = cubage # 新增特性 self.length, self.width, self.height = cubage else: raise TypeError def get_cubage(self): return self.cubage # 修改返回值为新增特性 cube = Cube() cube.set_cubage((10, 10, 10)) print(cube.get_cubage()) # 显示输出结果为:(10, 10, 10) cube.length = 20 print(cube.get_cubage()) # 显示输出结果没有改变,仍然为:(10, 10, 10)
经过修改后的代码,虽然将新特性放入了访问器,但是,当修改长度特性时,结果没有变化。
这是因为修改length特性,并没有调用set_cubage()方法,所以get_cubage()方法的返回值不变。
那怎么解决这个问题呢?
如果,将每个特性都放入访问器,在设置特性的方法中重新绑定cubage的值,是能够正常实现的。
但是,这样的话,整个代码会变得很乱。
而且,导致使用了这个类的客户程序(使用代码的代码)也都需要修改。
例如,设置长度就不能再使用cube.length这样的特性进行设置,而需要使用cube.set_length()这样的方法。
其实,只加入一句代码来解决这个问题。
示例代码:
cubage = property(get_cubage,set_cubage)
上方代码中,cubage是新定义的特性,后方的property函数将两个访问器(注意顺序)绑定到这个特性,这样就可以正常使用新特性了。
我们测试一下。
示例代码:
cube = Cube() cube.set_cubage((10, 10, 10)) print(cube.get_cubage()) # 显示输出结果为:(10, 10, 10) cube.length = 20 print(cube.get_cubage()) # 显示输出结果为:(20, 10, 10) cube.cubage=(30,40,50) print(cube.get_cubage()) # 显示输出结果为:(30, 40, 50)
不管通过set_cubage()方法,还是直接设置特性,都能够正常返回结果。
property()实际上不是函数,它是一个类,所有的工作由这个类的特殊方法来完成。
property(fget=None,fset=None,fdel=None,fdoc=None)具有四个参数,这四个参数分别是特性的获取、设置、删除特性的方法以及说明文档字符串。
如果没有参数,属性不可读写;只写入一个参数,则属性为只读;写入两个参数,属性可以读写。
通过property()函数定义的特性(变量),称为属性。
而普通的的特性(变量),称为字段。
所以,属性就是包含了访问器的字段(变量)。
那么,到此为止我们了解到类的实例特性包含:字段、属性与方法。
最后,我们再来接触一些魔法方法。
__getattribute__(self,name):获取特性值时自动调用的方法,参数name填入特性名称。
__getattr__(self, item):通过调用来获取特性值的方法,参数填入特性名称。
__setattr__(self,key,value):设置特性值时自动调用的方法,参数key填入特性名称,参数value填入特性的值。
__delattr__(self,name):删除特性的方法,参数name填入特性名称。
__dict__:包含一个字典,存储实例的所有特性,包括新增的特性。
这些魔法方法,我们可以重写来满足我们的一些需求。
示例代码:(__getattr__,__setattr__,delattr__,__dict__)
class Class: def __init__(self, sex, age): self.sex = sex self.age = age def __getattr__(self, item): # 重写getattr方法 if self.sex == '女' and item == 'age': # 符合指定条件的情形 return '保密' # 返回指定的值 else: # 其他情形 return self.__dict__[item] # 从字典获取返回值 def __setattr__(self, key, value): # 重写setattr方法 if key == 'age' and value <= 0: # 符合指定条件的情形 raise ValueError('年龄数值错误!') # 抛出异常 else: self.__dict__[key] = value # 修改字典中的值 def __delattr__(self, item): # 重写delattr方法 del self.__dict__[item] # 删除字典中指定的键值 cls = Class('男', 18) print("我是%s孩,我的年龄%s。" % (cls.__getattr__('sex'), cls.__getattr__('age'))) # 显示输出结果为:我是男孩,我的年龄18。 cls.__setattr__('sex', '女') cls.__setattr__('age', 16) print("我是%s孩,我的年龄%s。" % (cls.__getattr__('sex'), cls.__getattr__('age'))) # 显示输出结果为:我是女孩,我的年龄保密。 cls.__delattr__('sex') print(cls.__dict__) # 显示输出结果为:{'age': 16}
示例代码:(__getattribute__)
class Class: def __init__(self, sex, age): self.sex = sex self.age = age def __getattribute__(self, item): # 重写getattr方法 attr = super().__getattribute__ if attr('sex') == '女' and item == 'age': # 符合指定条件的情形 return '保密' # 返回指定的值 else: # 其他情形 return attr(item) # 返回原始的值 cls = Class('男', 18) print("我是%s孩,我的年龄%s。" % (cls.sex, cls.age)) # 显示输出结果为:我是男孩,我的年龄18。 cls.sex = '女' cls.age = 16 print("我是%s孩,我的年龄%s。" % (cls.sex, cls.age)) # 显示输出结果为:我是女孩,我的年龄保密。
上方代码中,要格外注意:在重写方法时,读取特性不能通过“self.特性”的方式去获取,否则会引发异常。
因为,通过“self.特性”的方式获取时,会自动调用__getattribute__()方法自身,这就会导致不停的循环调用。
所以,一定要通过超类的方法进行获取,这里使用了super函数。
其实,还有另外一种方法,就是通过object这个超类去调用。
因为,在Python 3(注意版本)中定义的类,都会继承object类(即便没有显示继承)。
示例代码:(方法部分)
def __getattribute__(self, item): # 重写getattr方法 attr = object.__getattribute__ if attr(self,'sex') == '女' and item == 'age': # 符合指定条件的情形 return '保密' # 返回指定的值 else: # 其他情形 return attr(self,item) # 返回原始的值
注意:使用object类去调用方法的时候,需要提供两个参数,第一个是self,第二个是特性名称。
本节知识点:
1、构造方法
2、super函数的使用
3、访问器与属性
4、一些魔法方法
本节英文单词与中文释义:
1、learn:学习
2、sex:性别
3、shape:形状
4、cube:立方体
5、cubage:体积
6、property:属性
7、item:项
转载请注明:魔力Python » Python3萌新入门笔记(31)