Python 学习笔记-第10讲:面向对象-封装、继承与多态

面向对象的三大特征:封装、继承、多态

1. 封装

    封装是指将某些东西包装和隐藏起来,让外界无法直接使用,只能通过某些特定的方式才能访问。被封装的特性只能通过特定的行为去访问,以保证对象的数据完整性、隐藏性和安全性,并且有利于后期的维护。

2. 继承

    即一个派生类(derived class)继承基类(base class)的属性和方法。它使用已经存在的类定义创建新的类,然后可以在新类的定义中增加新的属性和方法,或者如果从父类继承的方法不能满足子类的需求,也可以重写从父类继承的方法。如果B类继承A类,则B类对象便具有了A类的一切属性和方法,我们称A类为基类、父类或超类,称B类为A类的派生类或之类。

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。


    1) 继承关系分为直接继承和间接继承。如果C继承B,B继承A,则B是C的直接父类,A是B的直接父类,而A是C的间接父类,C间接继承A。继承关系的优点是能够清晰地体现相关类之间的层次结构,减少数据和代码冗余,提高代码的可重用性。通过保持一致性,可减少模块间的接口,改善程序的可维护性。

    2) 继承还可以分为单继承和多继承。

        当一个类只有一个直接父类时称为单继承,当一个类具有多个直接父类时称为多继承。

class 子类(父类):
    '类文档字符串'
    def __init__(self,参数列表1):
        父类.__init__(self,参数列表2)     # 也可以使用super().__init__(参数列表2); python2: super(子类,self).__init__(参数列表2)
        ......
    子类新增的类成员

    参数列表1中包含父类和子类的属性,参数列表2中仅包含父类的属性。

    

在 Python中,类中的属性和方法的可继承性由属性或方法名称前的下划线来表示:

“单下划线” 开始的成员变量叫做保护变量,只有类对象和子类对象自己能访问到这些变量;

“双下划线” 开始的是私有成员,只有类对象自己能访问,连子类对象也不能访问到此数据。

_xxx 不能用’from module import *’导入
__xxx 类中的私有变量名
__xxx__ 系统定义名字

方法名称前的双下划线(__)对解释器来说具有特定的意义。这种用法是为了避免与子类定义的名称冲突。Python文档指出,“__spam”这种形式(至少两个前导下划线,最多一个后续下划线)的任何标识符将会被“_classname__spam”这种形式原文取代,在这里“classname”是去掉前导下划线的当前类名。

>>> class A(object): 
... def _internal_use(self): 
... pass 
... def __method_name(self): 
... pass 
... 
>>> dir(A()) 
['_A__method_name', ..., '_internal_use']
“_internal_use”并未改变,而“__method_name”却被变成了“_ClassName__method_name”。此时,如果你创建A的一个子类B,那么你将不能轻易地覆写A中的方法“__method_name”。
>>> class B(A): 
... def __method_name(self): 
... pass 
... 
>>> dir(B()) 
['_A__method_name', '_B__method_name', ..., '_internal_use']

            以上对__ 解释和示例引自 http://python.jobbole.com/81129/

    3) 子类继承父类构造函数

        如果在子类中需要父类的构造方法就需要显示的调用父类的构造方法,或者不重写父类的构造方法。

子类不重写 __init__,实例化子类时,会自动调用父类定义的 __init__

        如果重写了__init__ 时,要继承父类的构造方法,可以使用 super 关键字:
super(子类,self).__init__(参数1,参数2,....)    # Python 3 可以使用直接使用 super().xxx 代替 super(Class, self).xxx
        也可以采用以下形式:
父类名称.__init__(self,参数1,参数2,...)
        
>>> class Father:
	def __init__(self,name):
		self.name=name
		print('self:',self)
		print('name: {0}'.format(self.name))
	def getName(self):
		return "Father "+self.name
>>> class Son(Father):
	def __init__(self,name):
		print('son',self)         #语句1
		super().__init__(name)    #语句2
		print(self)               #语句3
		self.name=name
	def getName(self):
		print(self)
		return 'Son '+self.name

>>> son=Son('Jimmy')
son <__main__.Son object at 0x000001927F2F68D0>    #输出自语句1
self: <__main__.Son object at 0x000001927F2F68D0>    #输出自语句2
name: Jimmy                                        #输出自语句2
<__main__.Son object at 0x000001927F2F68D0>        #输出自语句3

    4) 在单继承和多继承中使用 super() 调用父类中的方法

        请参阅“super 详解”(http://www.runoob.com/w3cnote/python-super-detail-intro.html)中的示例。


    总结:当子类继承父类时,子类的构造方法应该包含父类和子类共同的属性,在子类的初始化方法中,将父类的属性传递给父类,子类的属性赋值给子类

    方法重写:子类继承父类时,子类的方法签名和父类一样,此时子类重写了父类的方法,当生成子类对象时,调用的是子类重写的方法

    当生成子类对象时,先初始化父类对象,所以如果父类有__init__()方法,并且有属性时,要通过子类的构造赋值
一个类可以有多个子类
在子类中,调用父类的属性时,在__init__()方法中使用
父类.属性,或self.属性或父类.__init__(self,参数)或super(父类,self).__init__(参数)四种方法给父类传参

调用父类方法时:super().父类方法()

    三代继承:子类初始化方法需要祖父、父类及自己的属性,可以调用父类的初始化方法传参,可以重写父类的方法

构造的顺序依然先构造祖父类,再构造父类,最后构造自己

    类名.mro(),可以看到所有父类,即搜索顺序

3. 多态

    多态是同一个行为具有多个不同表现形式或形态的能力。

    多态存在的三个必要条件
        继承
        重写
        父类引用指向子类对象

class Person:
    def __init__(self,name=None,age=None,sex=None):
        self.name=name
        self.age=age
        self.sex=sex
    def say(self):
        print('{0}说...'.format(self.name))

class Student(Person):
    def __init__(self,name=None,age=None,sex=None,score=None):
        # super(Student,self).__init__(name,age,sex)
        super().__init__(name, age, sex)
        self.score=score
    def say(self):
        print('{0}在学习,考了 {1} 分。'.format(self.name,self.score))

class Doctor(Person):
    def __init__(self, name=None, age=None, sex=None, department=None):
        # super(Student,self).__init__(name,age,sex)
        super().__init__(name, age, sex)
        self.department = department

    def say(self):
        print('{0}在{1}工作'.format(self.name, self.department))
stu1=Student('张三',22,'男',88)
stu2 = Student('李四', 18, '男', 95)
doc=Doctor('李静',30,'女','内科')
list1=[stu1,stu2,doc]
def cure(person):
    print('---------')
    person.say()

for i in list1:
    cure(i)
输出:
---------
张三在学习,考了 88 分。
---------
李四在学习,考了 95 分。
---------
李静在内科工作

子类继承父类的属性和方法,在子类中重写方法,方法名相同,但方法体各异。通过传入不同子类的实例对象,即可实现利用同一方法实现不同的功能。

4. 在对象中定义__lt__(self,other) 方法按对象属性对对象进行排序

# 父类车类(品牌、颜色,方法:启动),子类小汽车(座位数)
class Vehicle:
    def __init__(self,brand,color):
        self.brand=brand
        self.color=color
    def start(self):
        print('{0}发动机已启动'.format(self.brand))

    def __str__(self):
        return str(self.__dict__)

class Car(Vehicle):
    def __init__(self,brand,color,seats):
        self.seats=seats
        Vehicle.__init__(self,brand,color)

        # super().__init__(brand,color)
        # Vehicle.__init__(self,brand,color)

    def showInfo(self):
        print(self.brand)
        print(self.color)
        print(self.seats)

    def __str__(self):
        return '品牌:{0};颜色:{1};{2} 座'.format(self.brand,self.color,self.seats)
    def __lt__(self, other):        在__lt__ 方法中实现属性的大小比较
        return self.seats<other.seats


# car=Car('奥迪','黑色',5)
# car.start()
# car.showInfo()
# print(car)
c1=Car('奥迪', '黑色', 5)
c2 = Car('迈腾', '白色', 5)
c3 = Car('法拉利', '红色', 2)
c4 = Car('宝马', '灰色',2)
l1=[c1,c2,c3,c4]
print(l1)
for car in l1:
    print(car)
# l1.sort(key=lambda x:x.seats) #也可以在列表的 sort()方法中通过 key 参数设置排序所依据的属性
l1.sort()
print('排序后:')
for car in l1:
    print(car)
输出:
[<__main__.Car object at 0x000001D4A681F5F8>, <__main__.Car object at 0x000001D4A681F5C0>, <__main__.Car object at 0x000001D4A681F588>, <__main__.Car object at 0x000001D4A681F630>]
品牌:奥迪;颜色:黑色;5 座
品牌:迈腾;颜色:白色;5 座
品牌:法拉利;颜色:红色;2 座
品牌:宝马;颜色:灰色;2 座
排序后:
品牌:法拉利;颜色:红色;2 座
品牌:宝马;颜色:灰色;2 座
品牌:奥迪;颜色:黑色;5 座
品牌:迈腾;颜色:白色;5 座

5. 简单工厂类

    在工厂设计模式中,客户端可以请求一个对象,而无需知道这个对象来自哪里;也就是,


使用哪个类来生成这个对象。工厂背后的思想是简化对象的创建。与客户端自己基于类实例化直 接创建对象相比,基于一个中心化函数来实现,更易于追踪创建了哪些对象。通过将创建对象的代码和使用对象的代码解耦,工厂能够降低应用维护的复杂度。

    意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
    主要解决:主要解决接口选择的问题。
    何时使用:我们明确地计划不同条件下创建不同实例时。
    如何解决:让其子类实现工厂接口,返回的也是一个抽象的产品。

    关键代码:创建过程在其子类执行

工厂模式属于创建模式,用于解决子类创建问题。

    
class A:
    def t(self):
        print("A...")


class B(A):
    def t(self):
        print("B...")


class C(A):
    def t(self):
        print("C...")


class D(A):
    def t(self):
        print("D...")

class Factory:        #定义一个工厂类,用于选择要实例化的子类
    @classmethod
    def get(cls,x):
        return eval(x)()

temp=input('A,B,C,D').strip().upper()
result=Factory.get(temp)
result.t()
输出:
A,B,C,D #用户输入A、B、C、D即可选择要调用的子类。
A...
B...
C...
D...





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值