Python类和对象(二)

这一篇着重介绍面向对象编程的三大特征中的封装继承

封装

封装指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问,而只能通过该类所提供的方法来实现对内部信息的访问和操作;

封装的目的:

  • 隐藏类的实现细节;
  • 限制对类的不合理访问;
  • 有利于保证对象信息的完整性;
  • 便于修改,提高代码的可维护性;

那么具体怎么实现封装呢?

1.首先需要把属性和实现细节隐藏起来;

在类中,只要在属性的前面加两条下划线就表示该属性只能在内部使用,不能被外部访问和操作:

class Stu():
    def __init__(self,name,age):
        self.__name = name
        self.__age = age
    def info(self):
        print(self.__name.title() + "'s age is " + str(self.__age) + '.')

这里注意 __xxx__这种变量名比较特殊,它是外部也可以访问的,还有 _xxx 这种变量名,它只有一个下划线,它虽然也可以被外部访问,但它的潜在意思是 虽然可以被外访问,但不要轻易访问;

另外要提一点,就算是__name、__age 这种变量也不是外部绝对就无法访问的,不能访问是因为Python解释器把 __name 改成了 _Stu__name,因此仍然可以用_Stu__name来访问,甚至来进行赋值和修改;但不同版本的Python解释器可能改动也不一样,因此轻易还是不要这么干;

除了属性之外,你也可以在方法名的前面加两条下划线,那么Python默认这个方法就是被隐藏的,如果你在外部直接调用就会报错,但同样与属性一样,可以通过其它方式调用;

总结来说就是,Python并没有提供真正意义上的隐藏机制,究竟该怎么搞还是看你;

2.提供可以访问和操作内部信息的方法

a.property()函数

这里先举一个例子:

class Rectangle(): #定义一个长方形类
    def __init__(self,width,height):
        self.__width = width
        self.__height = height
    def setsize(self,size):
        self.__width,self.__height = size
    def getsize(self):
        return self.__width,self.__height
    def delsize(self):
        self.__width,self.__height = 0,0

如上,尽管我们不能直接访问类中的实例属性,但我们通过getwidth,setsize等方法依然能够获得我们想要的数据并对之进行操作;

但是你可能会觉得这种方法过于麻烦,那么你也可以使用Python提供的property()函数,property()函数的基本使用语法如下:

属性名 = property(fget=None, fset=None, fdel=None, doc=None)

四个参数分别对应读、写、删函数以及说明文档,在具体使用时Python会根据你传入的参数多少来决定property属性究竟具备多少功能;

看一个具体使用的例子:

class Rectangle(): #定义一个长方形类
    def __init__(self,width,height):
        self.__width = width
        self.__height = height
    def setsize(self,size):
        self.__width,self.__height = size
    def getsize(self):
        return self.__width,self.__height
    def delsize(self):
        self.__width,self.__height = 0,0
    size = property(getsize,setsize,delsize,'这是一个长方形')

这个类与上一个类唯一不同之处在于它多了一个size属性,我们往size属性里面传入了四个参数,这意味着该属性具备读写删以及提供说明文档的功能,接下来我们就可以借助该属性对类中的实例属性进行相关操作了;示例如下:

print(Rectangle.size.__doc__) #输出说明文档

rec = Rectangle(5,6) #读操作
print(rec.size)

rec.size = 7,9 #写操作
print(rec.size)

del rec.size #删除操作
print(rec.size)

输出:

这是一个长方形
(5, 6)
(7, 9)
(0, 0)

<?>这里有一个小疑问,如果setsize()函数中的参数设置为(self,w,h),意思是分开赋值,之后再使用property下的size属性进行写操作时就会出错,错误原因是:

TypeError: setsize() missing 1 required positional argument: 'h'

这个错误我百度了好久也没有找到结果,先埋在这里,留待以后再遇到类似情况再挖出来填上;

b.@property装饰器
Python不仅提供了property()函数,还提供了@property装饰器,通过@property装饰器,可以将类中的方法当作属性去调用,也就是说,在使用方法时不需要再加小括号;

仍以上面的Rectangle类说明:

class Rectangle(): 
    def __init__(self,width,height):
        self.__width = width
        self.__height = height
    @property
    def getsize(self):
        return self.__width,self.__height

我们用@property修饰getsize()方法,使getsize()方法变成了getsize属性的getter()方法。这里注意如果类中只包含这一个方法,那么该属性将是一个只读属性;
之后就可以直接利用该属性访问:

rec = Rectangle(5,6)
print(rec.getsize)

此外除了@property装饰器之外,还有setter以及deleter装饰器,它们分别提供修改属性以及删除属性的功能,用法如下:

@方法名.setter
@方法名.deleter

继承

编写类时,并非总要从空白开始,如果你要编写的类是另一个现有的类的特殊版本,可使用继承

一个类继承另一个类时,它将获得原有类的所有属性和方法,包括被隐藏起来的,原有的类称为父类,继承得来的类称为子类;子类不仅继承得到了父类的所有属性和方法,还可以定义自己的属性和方法;

1.子类的创建

使用super()函数:

class Animal():
    def __init__(self,name):
        self.name = name
    def run(self):
        print(self.name.title() + " is running.")
        
class Dog(Animal):
    def __init__(self,name):
        super().__init__(name)

my_dog = Dog('Tom')
my_dog.run() #Tom is running.

如上,我首先创建了一个父类Animal,然后让子类Dog继承Animal,继承时用到了一个特殊函数:super(),具体实现方法代码中已经详细地给了出来;
这里只需要注意子类Dog后的括号里面需要写入父类名称Animal;

super()函数会调用父类的__init__()方法,将父类与子类连接起来,在单继承时你可以不需要考虑它,即把它省略:

class Dog(Animal):
    pass
class Cat(Animal):
    pass

这对继承并不会造成影响,上例定义了两个类Dog和Cat,它们都继承了父类的所有属性和方法;

另外,我们在继承的子类中也可以定义新的属性和方法,这里不再举例;

2.多继承

大多数面向对象的编程语言(除C++)都只支持单继承,Python虽然在语法上明确支持多继承,但多继承增加了编码复杂度,容易出现错误,一般情况下还是不要使用;

使用多继承非常简单,在子类名后的括号内添加多少个父类,它就会继承多少个;

这里要注意的就是,如果一个方法在父类1里面有,在父类2里面也有,那么怎么办呢?子类到底该执行哪个?

这就需要格外留意子类圆括号内父类的顺序:

class C_child(F1,F2,F3)

如上子类C_child,如果F1,F2,F3里面都有名称重复的方法,那么在子类使用该方法时又未指定,那么排在前面的父类方法会屏蔽后面的重名方法,排列顺序就是 你指定的 F1,F2,F3这种顺序;

3.MixIn设计模式

MixIn设计模式本质上就是一种多继承,MixIn的目的就是给一个类增加多种功能,因此在设计时可以考虑多继承来组合出我们想要的类,而不是设计多层次的复杂的设计关系;

4.父类方法重写

当父类中某个方法并不是你想要的方法时,你可以对该方法进行重写,即在子类中定义一个和该方法重名的方法,然后设计出你想要的方法,设计完成后,子类中的方法会覆盖掉父类中的重名方法,这种操作叫做方法重写

但如果你想在子类中调用父类中被重写的方法时该怎么办呢?由于该方法已被重写,因此调用出的总是子类中已被重写的方法,这时可以用 父类名.方法 的方式调用父类中的该方法;

5.super()函数与多继承

如果子类继承了多个父类,那么它究竟该继承哪一个父类中的构造方法呢?如果不使用super(),那么默认继承排在第一个位置的父类继承,这当在父类排列的不严谨时或者其它情况下都很容易出现错误,因此这时就需要重写父类构造方法,并通过super()函数调用;

部分内容(不限于本文)参考自:

菜鸟编程
廖雪峰的官方网站
c语言中文网
以及《Python编程-从入门到实践》

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值