最新消息:欢迎光临 魔力 • Python!大家可以点开导航菜单中的【学习目录】,这个目录类似图书目录,更加方便学习!

Python3萌新入门笔记(31)

Python教程 小楼一夜听春语 5387浏览 0评论

这篇教程我们一起了解构造方法和属性。

构造方法在之前的教程中我们已经接触过。

它就是__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)

头像
发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网站 (可选)

网友最新评论 (3)

  1. 头像
    发现后面的内容有点难懂了 可能是楼主前面有几节课过渡的有点太快了
    song5年前 (2019-01-24)回复
    • 小楼一夜听春语
      难懂也可能是因为前面掌握的还不够牢固,或者一些概念没有清楚。
      小楼一夜听春语5年前 (2019-01-24)回复
  2. 头像
    cubage = property(get_cubage,set_cubage) 这行代码添加后,运行会报递归错误,RecursionError: maximum recursion depth exceeded in __instancecheck__
    无量洞主2年前 (2021-12-17)回复