本篇结束面向对象的进阶内容:两个特性(概念)内容:多态、鸭子类型、实质性内容:封装、property方法伪造成属性、绑定与非绑定方法
继承至同一个类事物,但每个类形态都不同称之为:多态
比如:老师具备下课铃响了()功能,学生具备下课铃响了功能(),老师执行的是下班操作,而学生执行的是放学操作,二者功能名虽然一致,但执行效果不同。
同属于人的特性:多态,具备相同功能名:多态性
class Animal(object): # 动物类
def speak(self):
print('叫')
class Dog(Animal): # 动物形态之一:狗
def speak(self):
print('汪汪叫!')
class Cat(Animal): # 动物形态之一:猫
def speak(self):
print('喵喵叫!')
# 属于同一类:Animal(动物)
d = Dog()
c = Cat()
# 同一事物都在调用run方法,实现的效果不同:多态性
>>> d.run()
>>> c.run()
> '汪汪叫!'
> '喵喵叫!'
多态的前提是:属于同一个事物,而多态性则是:方法名相同但实现的效果则是不同的
多态依赖于继承,继承了以后,我们可以减少重复的代码量。作为同一事物下的类,我们无法避免使用父类的属性与方法。
一个接口,统一调用
# 提供一种接口,负责调用多态类,就是说不管传递进来的是什么类,只调用speak方法(前提是它们都具备这个方法)
def speak_api(obj):
obj.speak()
>>> speak_api(d)
> '汪汪叫!'
使用多态性的好处:
1.增加了程序的灵活性:
以不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如func(类名)
2.增加了程序的可扩展性:
方法里面的内容可以更改,使用者无需更改自己的代码,还是用func(类名)去调用
多态只是一种特性,本质上就是继承,但是需要将方法名一至(不需要全部方法名统一),以便于写接口时,将具备多态的类传递进来,执行都存在的方法名。
如果细心的话可以发现,我们平时所使用len方法,可以理解成多态性
s = 'str'
lis = [1,2,3,4]
tup = (1,2,3)
st = {
1,2,3,4,5}
print(s.__len__())
print(lis.__len__())
print(tup.__len__())
print(st.__len__())
不考虑对象类型的情况下,使用统一的调用方法。不管我的方法内如何变化,调用方式始终不变。
来自维基百科:鸭子类型
鸭子类型(英语:duck typing)在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试“鸭子测试”可以这样表述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注点在于对象的行为,能做什么;而不是关注对象所属的类型。例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于"不"测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
关注点:
当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子
在鸭子类型中,关注点在于对象的行为,能做什么;而不是关注对象所属的类型
鸭子类型可以在不使用继承的情况下使用多态
怎么理解呢;如:两个类,其中一个类包含了另一个类的所有属性与方法,这种可以称之为:鸭子类型
'''
class Ducks: 抽象出的类,如果其它类具备这个抽象类的属性与方法,那就可以称为这个抽象类的子类,但通常称呼是:鸭子类型
def __init__(self,name):
self.name = name
def quack(self):
pass
def feathers(self):
pass
'''
class Duck:
def __init__(self,name):
self.name = name
def quack(self):
print('鸭子在呱呱叫!')
def feathers(self):
print('鸭子拥有羽毛')
# 隐性表示:该Person类属于Duck类(鸭子类型)在不继承情况下,使用多态
class Person:
def __init__(self,name):
self.name = name
def quack(self):
print('这个人在模仿鸭子叫!')
def feathers(self):
print('这个人捡起了一只羽毛')
def run(self):
print('跑步')
以上的Person
可以称为鸭子类型,因为它具备了抽象类的所有属性与方法
鸭子类型是抽象出一个类,然后其它类如果具备这个抽象类的属性与方法,那么其它类就可以隐性表示为抽象类的子类(也就是鸭子类型)
如果包含另一个类的所有属性与方法名,接口调用时会报错,因为方法名未统一
# 调用相同方法名的的类接口
def imitate(duck):
duck.quack() # 如果传递进来的对象不存在此方法,则报错
duck.feathers()
imitate(d)
imitate(p)
执行结果:
'''
鸭子在呱呱叫!
鸭子拥有羽毛
这个人在模仿鸭子叫!
这个人捡起了一只羽毛
'''
总言之:不需要检查这个类是否属于鸭子,而是检查它是不是正在模仿鸭子,如果是那么它就是鸭子类型
封装(Encapsulation),在设计类时,刻意将属性进行了隐藏,目的是为了对象不能随意的查看及修改自身的属性,而后提供一种公共的接口,对象调用此接口,和调用属性无差异,且接口可以做一些限制。
先来了解一下:Python中类的公有属性与私有属性
公有属性:
正常定义属性或方法名,即在类的内部与外部都可以根据属性名进行访问
可以理解成就是日常我们定义的方法或初始化的属性
class People(object):
obj_name = 'People'
def __init__(self,name,age):
self.name = name
self.age = age
jack = People('jack',18)
print(jack.name) # 可以通过属性名正常访问
私有属性:
在类的内部可以正常访问,但是在类的外部不能正常访问,需要提供接口才能够访问的到,定义私有属性,只需要在其名称前面加上两个下划线即可
__
指的是在类里面可以正常访问,外部则不行
class People(object):
__descr = 'this is People Class'
def __init__(self,name,age):
self.__name = name
self.__age = age
print(People.__descr) # 报错:People没有找到这个属性
jack = People('jack',18)
print(jack.__name) # 报错:jack对象没有__name这个属性
出现这样的问题是因为,我们在定义时属性就进行了隐藏,导致外部不能够正常访问类里面的属性,但是还是有访问方法的!
class People(object):
# 增加双下滑线后进行了变形:_People__descr
__descr = 'this is People Class'
def __init__(self,name,age):
self.__name = name # 对象属性也是:self._People__name
self.__age = age
访问过程:
print(People._People__descr)
jack = People('jack',18)
print(jack._People__name) # python比较矛盾,既然隐藏了,但是还是可以访问
打印结果:
this is People Class
jack
可以正常访问,但是过程没有任何提示,也就是没有看到类里面代码的情况下,用户也无法确定,这个属性它是否存在类里面,这正好起到了一个隐藏的效果。但定义隐藏属性终归不是目的,最终还是要进行使用的,理解这个以后再来看一下属性的封装!
封装指的就是把数据与功能都整合到一起
将类的属性隐藏起来,然后提供公共的接口,对象可以通过接口访问或修改属性的值,且接口可以设置判断等限制语句,这样可以严格限制对象能够做什么事情。对象能够直接访问的都是公有属性,也可以说对象能够直接访问的,都是类想让你访问的
补充:在任何位置变量或函数,开头为__
都表示隐藏,只能在当前的作用域访问
使用普通的提供接口的方法:
class People:
def __init__(self,name):
self.__name = name # 隐藏了name属性
def get_name(self): # 提供给对象访问name属性的方法
print(self.__name)
def change_name(self,value): # 提供给对象修改name属性的方法
if len(value) >= 3:
self.__name = value # 在这里,我们可以对传入的值进行控制
else:
print('请传入大于3位数的名称')
stu1 = People('jack')
# 虽说可以直接修改隐藏属性,但是我们通常不会直接修改隐藏属性内容
stu1.get_name()
stu1.change_name('tom')
stu1.get_name()
打印结果
'''
jack
tom
'''
但是这种方式使用起来,不会很方便,还要调用方法才能拿到属性,修改也是相同,与之前获取属性方式相比,这个显得就很不舒服,那么我们来了解一种正常获取属性和更改属性的方式,且是经过封装以后的。
Python提供的一个装饰器,用于将类里面的方法伪装成属性,对象在访问该属性时,会触发方法里面的代码,然后可以将返回值作为本次访问的结果
使用方法:
class People:
def __init__(self,name):
self.__name = name
@property # 将name这个方法转变成属性
def name(self):
return self.__name # 返回了我们隐藏的名称属性__name
stu1 = People('jack')
print(stu1.name) # name此时相当于接口
打印结果:
'jack'
这个装饰器以后,我们还可以定义属性更改或删除时,执行的方法
class People:
def __init__(self,name):
self.__name = name
@property # 当对象名.name时 执行的方法
def name(self):
return self.__name
@name.setter # 当name属性(也就是修饰后的name方法)发生更改时,执行的方法
def name(self,value):
if len(value) >= 3:
self.__name = value
else:
print('修改的长度不能小于3!')
@name.deleter # 当删除(del) name属性时,执行的方法
def name(self):
print('警告!!!该属性不能被删除')
stu1 = People('jack')
stu1 = People('jack')
print(stu1.name)
# 看似正常修改属性,实则不然,已经是经过我们封装后的
stu1.name = 'tm' # 执行了name.setter装饰器对应的函数name(stu1,'tm'),判断不合格,没有修改值
print(stu1.name)
del stu1.name # 执行了name.deleter装饰器对应的函数(stu1),打印不能删除该属性
执行结果:
'''
jack
修改的长度不能小于3!
jack
警告!!!该属性不能被删除
'''
property有效地保证了属性访问的一致性
封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要使用提供的接口既可。
class People(object):
def __init__(self,height,weight):
self.height = height
self.weight = weight
@property
def bmi(self):
return round(self.weight / (self.height ** 2),2)
jack = People(1.80,56)
print(jack.bmi)
类的函数分为两大类:绑定方法与非绑定方法
而绑定方法又分为两种:对象绑定与类绑定
在类中正常定义的函数默认是绑定到对象的,而为某个函数加上装饰器@classmethod后,该函数就绑定到了类。
非绑定方法则是为函数加上装饰器@staticmethod,此后我们访问方法与普通函数无意,该传递几个参数就要传递几个参数,且不会自动装对象传递进去,此方法基本不用
对象绑定就不需做多介绍;
class People:
def __init__(self,name):
self.__name = name
@classmethod
def test(cls): # 我们在使用@classmethod装饰器创建方法时,默认就在括号内加上了cls,这个代表了我们这个People类
print(cls)
stu1 = People('jack')
stu1.test() # 通过对象来调用类绑定
执行结果:
<class '__main__.People'>
此绑定方法是专门给类进行使用的,虽说使用对象来调用也是可以,但传入的第一个参数仍然是类,所以这样调用没有意义,且容易引起混淆。
# 我们可以在调用类绑定时帮我们实例化对象,因为cls代表类,我们直接加()就可以实例化了
class People:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
@classmethod
def regis(cls,value):
# 分隔接收到的值,通过3个变量接收
name,age,sex = [i for i in value.split(':')]
# 将分隔后的值传入类(People)实例化后得到对象返回
return cls(name,age,sex)
# 如果用户输入的是这种的,那么我们可以将它分解且实例化为对象
obj = People.regis('jack:18:male') # 通过调用这个类方法,直接拿到对象且是经过处理的
print(obj.sex)
> 'male'
类绑定方法,很少场景下能够使用,在Python类中,最常用的还是对象绑定
合法的在类里面定义普通函数,因为类里面默认定义的函数都是绑定对象的,所以对比未使用装饰器定义的普通函数,定义时不会携带任何产生,这种甚至都不会飘红
我们可以使用对象来调用,调用时也不会将对象自动传入,真正的是一个普通函数
class People(object):
def __init__(self,name,sex,age):
self.name = name
self.sex = sex
self.age = age
@staticmethod
def test():
print('这是一个普通函数')
p = People('jack','男',18)
p.test()
也可以将我们对象传递进去,但是这样毫无意义!所以只是介绍此方法,但至于什么时候使用,全凭开发者决定!
在类里面定义的函数,绝大部分都是对象绑定,使用类绑定本身就很少,而静态方法更少之又少了!
技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请
点赞 收藏+关注
子夜欢迎您的关注,谢谢支持!