带你了解Python面向对象 (3) 进阶:多态、鸭子类型、封装、property装饰器、绑定方法与非绑定方法


前言:

本篇结束面向对象的进阶内容:两个特性(概念)内容:多态、鸭子类型、实质性内容:封装、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('鸭子拥有羽毛')

class Person:
	def __init__(self,name):
		self.name = name
		
	def quack(self):
		print('这个人在模仿鸭子叫!')

	def feathers(self):
		print('这个人捡起了一只羽毛')

	def run(self):
		print('跑步')

# 以上两个类都具备,Ducks这个抽象类的所有属性与方法,那么就是鸭子类型

以上的PersonDuck可以称为鸭子类型,因为它具备了抽象类Ducks的所有属性与方法

鸭子类型是抽象出一个类,然后其它类如果具备这个抽象类的属性与方法,那么其它类就可以隐性表示为抽象类的子类(也就是鸭子类型)

如果包含另一个类的所有属性与方法名,接口调用时会报错,因为方法名未统一

# 调用对象相同方法名的的接口
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
'''

但是这种方式使用起来,不会很方便,还要调用方法才能拿到属性,修改也是相同,与之前获取属性方式相比,这个显得就很不舒服,那么我们来了解一种正常获取属性和更改属性的方式,且是经过封装以后的。


property装饰器

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有效地保证了属性访问的一致性

封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要使用提供的接口既可。


小结:计算bmi值,放入属性内

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()

也可以将我们对象传递进去,但是这样毫无意义!所以只是介绍此方法,但至于什么时候使用,全凭开发者决定!

在类里面定义的函数,绝大部分都是对象绑定,使用类绑定本身就很少,而静态方法更少之又少了!


技术小白记录学习过程,有错误或不解的地方请指出,如果这篇文章对你有所帮助请点赞 收藏+关注 子夜欢迎您的关注,谢谢支持!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值