第八课.Python面向对象(二)

类的继承和多态

继承和多态与函数有共同的目的,都是为了减少代码的冗余,提高复用的效率;
根据“Python面向对象(一)”的笔记,我现在定义一个Cinema类:

#父类
class Cinema(object):
    sale_total=0 #所有影院的售票总数
    def __init__(self,name,address,sale):
        self.name=name
        self.address=address
        self.sale=sale #实例影院的售票数
        Cinema.sale_total=Cinema.sale_total+self.sale
    
    #Cinema实例的售票方式
    def sale_tickets(self,sale):
        self.sale=self.sale+sale
        Cinema.sale_total=Cinema.sale_total+sale
    
    @classmethod
    def get_sales(cls):
        return cls.sale_total

这是一个电影院售票的场景,假设售票情况如下:

cinema1=Cinema("影院1","万达广场",100)
cinema2=Cinema("影院2","万达广场",50)

cinema1.sale_tickets(200)
cinema2.sale_tickets(100)

print(Cinema.get_sales(),cinema1.sale,cinema2.sale)
# 450 300 150

假设Cinema在郊区开辟了一些小影院,虽然它们的售票情况没有Cinema中心地区的乐观(为了保持营业,sale_tickets方法会进行打折),但其他内容都不变,那我现在真的需要拷贝一下Cinema类的代码然后稍作修改吗?
为了提高复用效率,我可以选择类的继承:

#子类
class MiniCinema(Cinema):
    def sale_tickets(self,sale):
        #小影院为了保持营业,需要打折售票吸引游客
        if sale>100:
            sale=sale*0.8
        #super().代表会调用父类的成员
        super().sale_tickets(sale)

在继承这个问题上,需要注意类成员变量的层级(实例变量是互相独立的不会出现奇怪现象),改变子类的类成员变量不影响父类成员变量,改变父类的类成员变量会同步修改子类的类成员变量:
fig1
fig2
fig3

回到之前的那一段:
fig4

我个人理解,当通过super()调用父类成员,是直接性的复制了父类的代码,父类中的sale_tickets()中,类成员修改就是用Cinema访问的,因此修改了父类的类成员变量,子类的类成员sale_total也同步被修改;
创建实例时,在MiniCinema中没有__init__但也调用了,原因是继承了父类的__init__,在创建实例时自动执行,回忆一下,在初始化魔法方法中,我写过一句:Cinema.sale_total=Cinema.sale_total+self.sale
当我不调用sale_tickets(),创建子类实例时也修改了父类成员(在之前,sale_total是450):
fig5
从这可以看出一点,子类的继承就是从父类复制了代码;
对于多态,其实更好理解,比如:

minicinema.sale_tickets(200)

由于minicinema是子类的实例,调用同名方法会自动判断出应调用子类的方法,这就是多态
扩展:多继承
所谓多继承是指子类继承自多个父类,比如定义一个娱乐的类,它继承了电影院,餐厅,温泉等类:

class funny(Cinema,rest,spa):
    pass

关于继承,object是一切类的基类;
对于一个子类,有以下对象:
__base__:返回类的父类,用元组保存,
__mro__:返回一个元组,元素从左到右为类的继承顺序:
fig6

魔法方法__*__

魔法方法是定义在类中,有双下划线的特殊方法,如果在对应魔法方法名下进行修改,就可以进行特殊功能的定制
注意:魔法方法不能自己创建,只能修改,它是python类定义中特有的一种机制

#回顾以前说迭代器的next方法
def gen(n):
    for i in range(n):
        yield i**2
        
#生成器一定是迭代器,必有next魔法方法
gener=gen(6)
#调用方式1
gener.__next__()
#调用方式2
next(gener)

在之前类设计中,常见__init__就是一个魔法方法,用于初始化,同样用于初始化的还有__new__方法(一般还是用init更多),实例生命周期结束时有__del__方法,现在我去其中添加我的语句,在执行时就会按照我的想法进行定制:

class myclass():
    def __init__(self):
        print("doing init")
    
    def __del__(self):
        print("doing del")

mycls=myclass()
del mycls
"""
>doing init
>doing del
"""

想想以前说过的切片,为什么li[start: end: step]就能切片,而且step>0就是左闭右开规则,这与列表的__getitem__方法有关,而len能返回列表的元素个数与__len__方法有关,现在在我随意重写一个列表类,定制其len(返回2倍元素个数)和切片规则(左闭右闭):

#定义一个新列表类,修改魔法方法对常见方法进行定制
class FunctionList():
    def __init__(self,values):
        self.values=values
    
    def __len__(self):
        return len(self.values)*2
    
    def __getitem__(self,key):
        #如果传入的key是slice类,则这是一个切片访问
        if type(key)==slice:
            start=key.start
            stop=key.stop+1 #不再左闭右开,而是左闭右闭
            return self.values[start:stop]
            
li=FunctionList([0,1,2,3,4,5,6,7,8])
len(li)
#相当于
li.__len__()

#对于以往的列表,step>0时切片是左闭右开的
li_or=[0,1,2,3,4,5,6,7,8]
print(li_or[0:3])

#修改了__getitem__后,FunctionList类的实例列表切片为左闭右闭
print(li[0:3])

fig7
fig8

描述符descriptor

描述符是一种对类中属性的控制,有一点修饰的意思;
在类中,还有一种特别的装饰器:property,该装饰器(也是一个数据描述符)将类中的读写方法变成了一种属性读写的控制,property多用于私有成员的读和写控制;

class Student():
    def __init__(self,name):
        self.__name=name
    
    """注意描述符格式"""
    #getter,方法名不能与实例变量重名
    @property
    def name(self):
        return self.__name
    
    #setter
    @name.setter
    def name(self,new_name):
        self.__name=new_name
        
student=Student('hack')
print(student.name)
student.name='baijingyi'
print(student.name)    

fig9
可见,通过property修饰,方法调用被转为属性一样的读写,换句话说,属性读写得到了控制

类工厂函数和元类

类工厂函数可以通过函数调用返回一个类:

from collections import namedtuple  

#返回一个类
User=namedtuple('User',['name','gender','age'])

#用这个类去创建实例
user=User('baijingyi','male',20)
print(user)
print(type(user))

#或者用列表去创建实例
userli=User._make(['baijingyi','male',20])
print(userli)
print(type(user))

fig10
类工厂函数是函数调用返回类,元类则是类的类,即所有类都是元类的实例,这也反映了python一切皆对象这个原则,这个metaclass出现了多次,就是type:


type()函数依次传入3个参数:
1.class的名称
2.class继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
3.class的方法名称与函数绑定


看起来有些麻烦,不过可以轻松一点,因为实际创建类一般不会用type

模块和包

模块就是一个文件,包是各个文件的组合
对于包,要注意:
1.文件夹下必须有__init__.py文件
2.路径必须可以被python搜索到
3.第三方包安装后位于/Lib/site-packages
关于python搜索的路径,可以用sys查看:

import sys
print(sys.path)

当某个包不在路径下,可以强行添加进path:

sys.path.append("my-package-dir")

补充:网站pypi:python package index,可以将自己的包上传到网站,审核通过后就能pip安装了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值