类的继承和多态
继承和多态与函数有共同的目的,都是为了减少代码的冗余,提高复用的效率;
根据“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)
在继承这个问题上,需要注意类成员变量的层级(实例变量是互相独立的不会出现奇怪现象),改变子类的类成员变量不影响父类成员变量,改变父类的类成员变量会同步修改子类的类成员变量:



回到之前的那一段:

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

从这可以看出一点,子类的继承就是从父类复制了代码;
对于多态,其实更好理解,比如:
minicinema.sale_tickets(200)
由于minicinema是子类的实例,调用同名方法会自动判断出应调用子类的方法,这就是多态
扩展:多继承
所谓多继承是指子类继承自多个父类,比如定义一个娱乐的类,它继承了电影院,餐厅,温泉等类:
class funny(Cinema,rest,spa):
pass
关于继承,object是一切类的基类;
对于一个子类,有以下对象:
__base__:返回类的父类,用元组保存,
__mro__:返回一个元组,元素从左到右为类的继承顺序:

魔法方法__*__
魔法方法是定义在类中,有双下划线的特殊方法,如果在对应魔法方法名下进行修改,就可以进行特殊功能的定制
注意:魔法方法不能自己创建,只能修改,它是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])


描述符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)

可见,通过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))

类工厂函数是函数调用返回类,元类则是类的类,即所有类都是元类的实例,这也反映了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安装了
240

被折叠的 条评论
为什么被折叠?



