python之类的多态(鸭子类型 )、封装和内置函数property
一、多态
1、什么是多态:一个类表现出的多种状态--->通过继承来实现的
例如:
class Animal:pass
class Dog(Animal):pass
class Cat(Animal):pass
Animal类表现出了Dog,Cat两种形态
好了,多态就是这样,结束...
哈哈,有没看懂的吧,那么我们一步一步来解释:
首先我们来看:
s = 'abc'
print(type(s)) # <class 'str'>
s = 'abc' 我们都知道s就是一个str类型,在python中一切皆对象,其实你打开源码可以看到str是一个类,
它在源码中是这样定义的:class str(object): ...
而我们定义一个变量的时候s = 'abc' ,它在内部是进行了一个这样的转换:s = str('abc')
刚才说了str是一个类,那str()就是实例化 , str('abc')就是把参数'abc'传进去初始化一个对象,那么s就是一个str类的对象,s的type就是str,
也就是说其实我们平时定义的变量都是对象,例如:
a = 10 是一个int类的对象
b = [1,2,3] 是一个list类的对象等等
那么我们自己定义的类也是一样的:
class A:
pass
a = A()
print(type(a)) # 对象a的数据类型就是A
2、我们引用一下java中的例子再来了解一下(假设下面是java代码)
class ApplePay: def pay(self): pass 在java中,参数是要指定类型的,其他类型的数据是不能传进去的,这里指定了是ApplePay类型 def My_pay(ApplePay obj,int money): obj.pay() app = ApplePay() # app是ApplePay类的对象,所以app就是ApplePay类型的数据 My_pay(app,100) 这么一看,好像没什么问题,但是这么写的话,这个My_pay函数只能给ApplePay类使用了,其他类就不能使用了, 扩展性很差,那么这个时候就需要多态了,且看下面例子: class Payment: # 首先定义一个父类 pass class ApplePay(Payment): #每个子类都继承父类 def pay(self): pass class Alipay(Payment): #每个子类都继承父类 def pay(self): pass #这里obj类型写父类的类型Payment,因为子类继承父类,也继承了父类的类型 def My_pay(Payment obj,int money): obj.pay() app = ApplePay() # 这里app既是ApplePay类型,也是Payment类型 My_pay(app,100) alp = Alipay() # 这里alp既是Alipay类型,也是Payment类型 My_pay(alp,200) 这样利用多态就解决了参数类型的问题
3、总结(为什么在python中,我们写的函数都不用指定类型?)
1,
在java中:多态用来解决传参数的时候数据类型的规范问题。
在java中的表现 : 在一个函数中需要给参数指定数据类型,如果这个地方可以接收两个以上类型的参数,
那么这些类型应该有一个父类,这个父类是所有子类对象的类型
2,
在python中:函数的参数不需要指定数据类型,我们也不需要通过继承的形式来统一数据类型,
因为python中所有的对象都继承了object类,所以都是object类型,因此在python当中处处都是多态。
4、鸭子类型(拓展)
定义:
不是明确的通过继承实现的多态,
而是通过一个模糊的概念来判断这个函数能不能接收这个类型的参数
例如:
len() # str list tuple dict set range(3)--->这些对象就是鸭子类型
print() # print什么类型都能打印,所有的对象都是鸭子类型
二、封装
1、定义:
广义上的封装:把属性和函数都放到类里
狭义上的封装:定义私有成员
1,广义上的封装:
class 类名:
def 方法1(self):pass
是为了只有这个类的对象才能使用定义在类中的方法
2,狭义上的封装: __属性名 __方法名
把一个属性(方法)藏在类中
class Goods:
__discount = 0.7 # 私有的静态变量
print(Goods.__discount) # 报错
在类的外部不能引用私有的静态变量
class Goods:
__discount = 0.7 # 私有的静态变量
print(__discount) # 类内部打印输入
程序一执行就打印出0.7
因为:
类中的静态变量和方法名在程序加载的过程中就已经执行完了,不需要等待调用
在这个类加载完成之前,Goods这个名字还没有出现在内存空间中
私有的静态属性可以在类的内部使用,用来隐藏某个变量的值
3,变形
其实用双下划线定义好一个私有变量后,python会自动把这个私有变量进行一个变形,例如私有变量__discount,变形如下:
__discount ---> _Goods__discount
也就是:
__私有变量 ---> _类名__私有变量
class Goods:
__discount = 0.7 # 私有的静态变量
print(Goods.__dict__) # 查看Goods类的所有属性和方法可以看到里面有个变量_Goods__discount的值是0.7
print(Goods._Goods__discount) # 可以看到0.7 但从编程规范的角度上出发 我们不能在类的外部使用私有的变量
2、
类中的私有成员:
1,私有的静态属性
2,私有的对象属性
3,私有的方法
为什么要定义一个私有变量呢:
1,不想让你修改这个值
2,不想让你看到这个值
3,想让你在修改这个值得时候有一些限制
4,有些方法或者属性不希望被子类继承
话不多说,请看例子理解
例1:私有的静态属性、不想让你修改这个值
class Student: def __init__(self,name,age): self.__name = name # 设置了一个私有变量__name用来储存学生的名字 self.age = age def name(self): # 因为私有变量在外部不能引用,所有设置一个方法用来打印学生的名字 return self.__name xiaoming = Student('xiaoming',18) print(xiaoming.age) # 18 print(xiaoming.name()) # xiaoming xiaoming.age = 28 print(xiaoming.age) # 28 xiaoming.name = 'xiaogou' #这里只是为这个对象新增了一个属性name,并没有修改原本的__name print(xiaoming.name()) #报错 那么肯定会有人问,这样做确实不能修改__name,但是别人可以新增name啊,而且__name还要用name()这样才能显示,多麻烦。 好,这个问题等下在后面的内容会给你解决,现在你只要明确地知道你一开始传进去的学生名字__name是修改不了就行了。
例2:私有的静态属性、修改的时候有限制
class Goods: __discount = 0.7 # 私有的静态变量 def __init__(self,name,price): self.name = name self.__price = price def price(self): #折扣价 return self.__price * Goods.__discount def change_price(self,new_price): if type(new_price) is int: self.__price = new_price else: print('本次价格修改不成功') apple = Goods('苹果',5) print(apple.price()) apple.change_price(6) print(apple.price())
例3:私有的对象属性、不想让你看到这个值(通过算法把密码变成密文的形式)
class User: def __init__(self,username,password): self.username = username self.__pwd = password self.pwd = self.__getpwd() def __getpwd(self): # 把密码转换成哈希值 return hash(self.__pwd) obj = User('xiaoming','123456') # 用户名:xiaoming 密码: 123456 print(obj.username,obj.pwd) # 用户名:xiaoming 密码的密文:-1675187800842546722
3、私有变量能不能在外部被定义?(不能)
class A: __country = 'China' print(A.__dict__) A.__Language = 'Chinese' # 其实这里只是定义了一个名为__Language的属性,并不是私有变量 print(A.__dict__)
4、私有变量能不能被继承?(可以)
class A: __country = 'China' # __country 就是 _A__country def __init__(self,name): self.__name = name # __name 就是 _A__name 继承A的B类的对象初始化的时候 self.__name 就是 # self._A__name # 此时为self._A__name赋值,并没有形成self的私有变量 # 而本身的A类对象初识化的时候,为self.__name赋值,会形成self的私有变量 class B(A): def get_country(self): print(__country) # 报错:name '_B__country' is not defined print(B._A__country) # China def get_name(self): return self.__name # 报错:'B' object has no attribute '_B__name' b = B('xiaoming') b.get_country() print(b.get_name()) print(b.__dict__) # {'_A__name': 'xiaoming'}
总结:
子类可以继承父类的所有属性(包括私有属性)和方法,但是继承的私有属性是变形后的形式,在子类中不再是私有属性,
在子类中用 __私有属性名 是找不到继承的私有属性的 因为它会默认去找自己类中的私有属性,
要想调用父类的私有属性则应该用变形后的变量 _父类名__变量名
三、内置函数property
1、
装饰器的分类:
装饰函数:
装饰方法 : property
装饰类:
装饰器函数都怎么用:
在函数、方法、类的上面一行直接@装饰器的名字
2、property是一个装饰器函数(就是用来解决刚才私有属性的问题)
property:将一个方法伪装成一个属性
2-1学生信息中,姓名不想给别人修改,就设置为私有属性,在定义一个同名的方法伪装成属性,便于查找
class Student: def __init__(self,name,age): self.__name = name self.age = age @property def name(self): # 声明了@property使用此方法的时候就可以不写括号,就伪装成了属性 return self.__name xiaoming = Student('小明',18) print(xiaoming.name) # 小明 xiaoming.name = '小狗' # 改:报错 print(xiaoming.name)
2-2圆的半径可以修改,但是面积和周长应该是属性的形式比较正确,但是直接设置为属性,圆的半径改了后,
周长和面积并不会改变
class Circle: def __init__(self,r): self.r = r self.area = 3.14 * self.r ** 2 self.perimeter = 2 * 3.14 * self.r c = Circle(6) print(c.area) # 113.04 print(c.perimeter) # 37.68 c.r = 3 # 改变了半径 print(c.area) # 113.04 print(c.perimeter) # 37.68
2-3因此上面的圆可以写成这样:
class Circle: def __init__(self,r): self.r = r @property def area(self): return 3.14 * self.r ** 2 @property def perimeter(self): return 2 * 3.14 * self.r c = Circle(6) print(c.area) # 113.04 print(c.perimeter) # 37.68 c.r = 3 # 改变了半径 print(c.area) # 28.26 print(c.perimeter) # 18.84