多态:
多态指的是一类事物有多种形态;比如 动物有多种形态:人、狗、猪
如下代码:
import abc class Animal(metaclass=abc.ABCMeta): #同一类事物:动物 @abc.abstractmethod def talk(self): pass class People(Animal): #动物的形态之一:人 def talk(self): print(‘say hello‘) class Dog(Animal): #动物的形态之二:狗 def talk(self): print(‘say wangwang‘) class Pig(Animal): #动物的形态之三:猪 def talk(self): print(‘say aoao‘) """ 多态动态绑定(在继承的背景下使用时,也称为多态性): 多态性是指在不考虑实例(对象)类型的情况下使用实例 多态性分为静态多态性和动态多态性 静态多态性:如任何类型都可以用运算符“+”进行计算(站在“+”的角度,无需考虑加的是什么类型的实例) 动态多态性:如调用方法(功能、函数) """ # 多态性:不考虑实例类型的情况下使用实例,如下分析: # 不用考虑是人、是狗还是猪,只要是动物,都可以调用 talk()方法 people = People() pig = Pig() dog = Dog() people.talk() pig.talk() dog.talk() # 上面调用talk()功能还能进一步优化,如下: def func(obj): obj.talk() # 这种方式的好处:只需要写这一个接口就行,想让谁talk()传入谁就行,无需考虑是人、猪、狗(不用再考虑对象的类型) func(people)
多态性的好处:
1. 增加了程序的灵活性: 不变应万变,不论对象千变万化,使用者都是同一种形式去调用,如:func(obj);但需要注意的是,多态性是建立在多态的基础上
2. 增加了程序的可扩展性: 通过继承Animal类创建一个新的类,使用者无需更改自己的代码,还是用func(obj)去调用,如下代码:
class Cat(Animal): def talk(self): print("say miaomiao") def func(obj): obj.talk() # 这种方式的好处:只需要写这一个接口就行,想让谁talk()传入谁就行,无需考虑是人、猪、狗(不用再考虑对象的类型) cat = Cat() func(cat) # 运行结果: # say miaomiao """这样我们新增了一个形态Cat,由Cat类产生的实例cat,使用者可以在完全不需要修改自己代码的情况下。使用和人、狗、猪一样的方式调用cat1的talk方法,即func(cat1)"""
这种方式虽然也增加了程序的可扩展性,但却不是python崇尚的方式;Python崇尚的是鸭子类型,即“如果看起来像、叫声像而且走路像鸭子,那它就是鸭子”
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象(如上述的Cat类),也可以创建一个外观和行为像、但与它没有任何关系的全新对象;后者通常用于保存程序组合的松耦合度
鸭子类型:
""" class File: def read(self): pass def write(self): pass """ class Disk: # Disk(硬盘)不是File(文件),但却跟文件的方法很像;不用继承File,创建一个跟File相似的类 def read(self): print("disk read") def write(self): print("disk write") class Txt: def read(self): print("txt read") def write(self): print("txt write") disk = Disk() txt = Txt() disk.read() #把disk当文件对象去使用
disk.write()
txt.read()
txt.write()
# 序列类型:列表list、元祖tuple、字符串str, 它们都有一个统计长度 .__len__() 的方法,其实现原理也是鸭子类型(只要是序列类型,就不用在乎是列表、元祖还是字符串,而且没有继承同一个父类)
鸭子类型:不需要专门制作父类(或抽象类)来约束子类,只要做的像一点就能调用多态性
封装:
隐藏类的属性:
"""在属性前加上两个下划线,就把属性变成了隐藏属性;(属性前后都加两个下划线是python的内置函数)""" class A: __x = 1 # 通过 A.__dict__ 能够看出,隐藏属性发生了变形:由 __x 变成了 _A__x def __init__(self,name): self.__name = name # 通过 a.__dict__ 能看出,__name 变成了:_A__name def __foo(self): # 同理, _A__foo print("run foo") print(A.__dict__) # 运行结果: # {‘__module__‘: ‘__main__‘, ‘_A__x‘: 1, ‘__init__‘: <function A.__init__ at 0x0000006AD34BB9D8>, ‘_A__foo‘: <function A.__foo at 0x0000006AD34BBA60>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘A‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘A‘ objects>, ‘__doc__‘: None} a = A("neo") print(a.__dict__) # 运行结果: # {‘_A__name‘: ‘neo‘}
属性变形的特点:
1. 在类的外部无法直接调用类的隐藏属性(如 无法通过 A.__x 的方式调用__x)
2. 在类的内部可以直接调用类的隐藏属性(如 能够通过 .__foo(self)的方式调用 __foo(self)); 原理如下:
class A: """类在定义阶段就会运行""" __x = 1 # 运行到这一步的时候把 __x = 1 变成了 _A.__x = 1 def __init__(self,name): # 运行到这一步的时候,程序不会执行 __init__(self,name) 函数,但会检测 __init__()函数中的语法 self.__name = name # 所以,程序检测函数语法的时候,会把 self.__name = name 变成 self._A__name = name def __foo(self): # 同理, 经过语法检测, __foo(self) 会变成 _A__foo(self) print("run foo") def bar(self): self.__foo() # 同理,经过语法检测,self.__foo() 也会变成 self._A__foo(), 这个函数名和 __foo(self)变形后的函数名一样的,所以能够调用 print("from Bar") a = A("neo") a.bar() # 运行结果: # run foo # from Bar
3. 子类无法覆盖父类 __ 开头的属性(隐藏属性)
"""子类无法覆盖父类隐藏的属性的原因""" class Foo: def __func(self): # __func 已经变成了 _Foo__func print("from Foo") class Bar(Foo): def __func(self): # __func 变成了 _Bar__func ; 变性后跟Foo中的 __func 名字已经不一样了 print("from Bar")
这种变形需要注意的问题:
1. 这种隐藏机制并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性就可以拼出名字: _类名__属性,然后就可以直接访问了,如 a._A__x
2. 变形的过程只在类的定义阶段发生一次,在定以后的赋值操作,不会变形
class B: __x = 1 # 在类的定义阶段发生变形: _B__x = 1 def __init__(self,name): self.__name = name B.__y = 2 # 这已经是在类的定义之后,所以不会再发生变形
3. 在继承中,父类如果不想让子类覆盖自己的方法(函数等),可以将自己的方法定义为私有的(隐藏属性)
"""情况1:(前面讲过的知识点)""" class A: def foo(self): print("A.foo") def bar(self): print("A.bar") self.foo() class B(A): def foo(self): print("B.foo") b = B() b.bar() # 运行结果: # A.bar # B.foo """情况2""" class A: def __foo(self): # 类定义阶段变成了 _A__foo print("A.foo") def bar(self): print("A.bar") self.__foo() # 定义阶段变成了 _A__foo, 所以调用的是A的 __foo函数 class B(A): def __foo(self): # 定义阶段变成了 _B__foo print("B.foo") b = B() b.bar() # 运行结果: # A.bar # A.foo
封装的意义:
1. 封装数据属性:将数据隐藏起来不是目的。隐藏起来后对外提供该数据的接口,然后我们可以在接口上附加上对该数据操作的限制,以此完成对数据属性操作的严格控制
""" 能够明确区分类内类外,控制外部对隐藏属性的操作行为 """
class People: def __init__(self,name,age): self.__name = name # 类内能够直接调用,但类外不能 self.__age = age def tell_info(self): # 通过隐藏属性,能够自己建一个接口,外部想调用隐藏的属性,必须调用我的接口; print("Name<%s> Age<%s>"%(self.__name,self.__age)) def set_info(self,name,age): # 而且我还能对这个接口加上自己的逻辑 if not isinstance(name,str): print("名字必须是字符串格式") return if not isinstance(age,int): print("年龄必须是数字") return self.__name = name self.__age = age people = People("neo",18) people.tell_info() # 运行结果: # Name<neo> Age<18> people.set_info(123,"NEO") people.tell_info() # 运行结果: # 名字必须是字符串格式 # Name<neo> Age<18> people.set_info("NEO",28) people.tell_info() # 运行结果: # Name<NEO> Age<28>
2. 封装函数属性(方法):隔离复杂度
""" 取款时功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱 对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,这么做就隔离了复杂度,同时提升了安全性 """ class ATM: def __card(self): print("插卡") def __auth(self): print("用户认证") def __input(self): print("输入取款金额") def __print_bill(self): print("打印账单") def __take_money(self): print("取款") def withdraw(self): self.__card() self.__auth() self.__input() self.__print_bill() self.__take_money() a = ATM() a.withdraw() # 运行结果: # 插卡 # 用户认证 # 输入取款金额 # 打印账单 # 取款
附:封装(隐藏)起来的属性外部无法直接访问,你需要在内部为其建一个接口(函数)
封装的扩展性:
"""定义一个房间的类""" class Room: def __init__(self,name,owner,length,width): self.name = name self.owner = owner self.__length = length self.__width = width def tell_area(self): return self.__length * self.__width room = Room("501房间","neo",10,9) print(room.tell_area()) # 运行结果: # 90 """现在这个房间想知道它的体积,只需要在__init__中再添加一个参数 height, tell_area 中也添加 height,用户想知道房间的体积继续调用 tell_area就行(即用户不用改变使用功能,直接就能用上扩展后的新功能),这样就实现了封装的扩展""" class Room: def __init__(self,name,owner,length,width,height): self.name = name self.owner = owner self.__length = length self.__width = width self.__height = height def tell_area(self): return self.__length * self.__width * self.__height room = Room("501房间","neo",10,9,3) print(room.tell_area()) # 运行结果: # 270
property用法:
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值 (一种“伪装”方法,把函数属性伪装成数据属性,并能够用调用数据属性的方式去调用这个函数属性)
示例1:
"""
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
体质指数(BMI)=体重(kg)÷身高^2(m)
"""
"""常规做法""" class People: def __init__(self,name,height,weight): self.name = name self.height = height self.weight =weight def bmi(self): # 在类中把BMI定义一个BMI的功能 return self.weight / (self.height ** 2) people = People("egon",1.8,75) print(people.bmi()) # 运行结果: # 23.148148148148145 # 想要得到BMI就需要利用 bmi() 这个方法(函数);类中的数据属性的含义是 “什么是什么”,函数属性的含义是“什么去干什么”, # 但BMI听起来应该是一个数据属性,而不是一种方法,这样使用者也更容易理解。so 如下修改后的代码: class People: def __init__(self,name,height,weight): self.name = name self.height = height self.weight = weight @property # property能够让方法当数据属性一样去调用,给使用者的感觉是他在调用一种数据属性而非方法;被property装饰的函数需要有return值 def bmi(self): return self.weight / (self.height ** 2) people = People("egon",1.8,75) print(people.bmi) # 此时想要调用bmi函数 直接people.bmi就行,跟数据属性的调用方式一样 # 运行结果: # 23.148148148148145 """被property装饰的函数的函数名不能再被当做变量去添加到其他对象(如people)的独有属性里面""" people.bmi = 24 print(people.bmi) # 会报错,因为此时bmi对应的是一种方法 # 运行结果: # AttributeError: can‘t set attribute
示例2:
# 以下代码没有实际意义,仅作为分析使用 class People: def __init__(self,name): self.__name = name # 把self.name隐藏起来;此时外部无法调用name属性,所以需要再创建一个调用隐藏属性的接口 def get_name(self): return self.__name people = People("egon") print(people.get_name()) # .get_name()给人的感觉还是在调用一种方法 # 运行结果: # egon # 为上述代码添加property class People: def __init__(self,name): self.__name = name @property def name(self): return self.__name people = People("egon") print(people.name) # 加上property后直接利用 people.name 调用 # 运行结果: # egon """ people.name = "EGON" 也不能用这种方式去赋值; 如果想对people.name 进行重新赋值,可用 @name.setter 方法, 想删除people.name,可用 @name.deleter 方法(了解性知识) """
绑定方法与非绑定方法:
在类内部定义的函数,分为两大类:
一: 绑定方法:绑定给谁,就应该由谁来调用,谁来调用就会把调用者当做第一个参数(self)自动传入
1. 绑定到对象的方法: 在类内定义的没有被任何装饰器修饰的
2. 绑定到类的方法: 在类内定义的没有被任何装饰器修饰的
二:非绑定方法: 不与类或者对象绑定;所以就不能自动传入参数,它只是类中定义的一个普通工具而已,并且对象和类都可以使用
绑定方法示例解析:
class Foo: def __init__(self,name): self.name = name def tell(self): # 这是绑定方法中的绑定到对象的方法 print("名字是%s" %self.name) @classmethod # 加上 @classmethod 被装饰的函数就变成了绑定到类的方法 def func(cls): print(cls) f = Foo("neo") print(Foo.tell) # <function Foo.tell at 0x0000009F6354BA60> # Foo.tell是一个函数,既然是函数就需要按照函数的方式执行,你需要自己传参 Foo.tell(f) # 手动传入f print(f.tell) # <bound method Foo.tell of <__main__.Foo object at 0x0000009F6354AB38>> # 是一种绑定方法,绑定方法会自动帮你传参 f.tell() # 把调用者自动传入self中 print(Foo.func) # <bound method Foo.func of <class ‘__main__.Foo‘>> # 加了 @classmethod 后,Foo.func也变成了一种绑定方式 Foo.func() # 跟 print(Foo) 效果一样 # 运行结果: # <function Foo.tell at 0x0000009F6354BA60> # 名字是neo # <bound method Foo.tell of <__main__.Foo object at 0x0000009F6354AB38>> # 名字是neo # <bound method Foo.func of <class ‘__main__.Foo‘>> # <class ‘__main__.Foo‘>
非绑定方法示例解析:
还拿上面的示例代码分析:
class Foo: def __init__(self,name): self.name = name @staticmethod # 加上 @staticmethod 被装饰的函数就变成了 非绑定函数;只是类中定义的一个普通工具而已,类和对象都能使用 def func1(x,y): print(x+y) f = Foo("neo") print(Foo.func1) print(f.func1) # 运行结果: # <function Foo.func1 at 0x00000009A482BA60> # <function Foo.func1 at 0x00000009A482BA60> Foo.func1(1,2) # 跟普通函数的调用传参方式是一样的 f.func1(1,3) # 运行结果: # 3 # 4
绑定方法和非绑定方法的使用:
1. """绑定到对象的使用"""
class People: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def tell(self): # 先把函数定义为 tell(),因为这个时候我还不确定需要往 tell里面传入什么参数;我需要根据函数体的功能代码去决定传入什么参数 """我想实现的功能是 打印对象的name,age,gender 的信息""" print("Name:%s Age:%s Gender:%s" %(self.name,self.age,self.gender)) # %后面的内容是对象的name、age、gender,对象是有可能发生改变的,所以我需要把对象设置成参数传进来,所以tell内的参数应该是self, 即 tell(self) p = People("neo",22,"male") """绑定给对象,就应该由对象来调用,自动将对象本身当做第一个参数传入""" p.tell() # 运行结果: # Name:neo Age:22 Gender:male
2. """绑定给类的使用"""
# 当前路径下有一个配置文件 setting.py # 内容如下: # name = "苍老师" # age = 22 # gender = "female" import setting # 导入配置文件 class People: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender def tell(self): print("Name:%s Age:%s Gender:%s" %(self.name,self.age,self.gender)) @classmethod # 根据功能体的代码分析出应该使用 @classmethod def read_config(cls): # 先不往 read_config 函数里面定义参数,我需要根据函数体的功能目标来决定传入什么参数 """我想实现的功能是 从配置文件中读取个人信息,然后读取到的name、age、gender传入到People中实例化""" obj = cls( setting.name, setting.age, setting.gender ) # 现在我需要的是传入到People这个类中进行实例化,假如现在有很多类,有可能需要把setting中的信息传入其他的类中进行实例化;如果现在写上People,就相当于把类写死了;所以应该把类当做变量传进来;而绑定给类的方法能够把类当做第一个参数自动传入,所以应该用 @classmethod 这种方法 # 实例化这个功能写入了People这个类中成为了People的一种方法 return obj # 绑定给类的, 就应该由类来调用,自动将类本身当做第一个参数传入 people = People.read_config() # People这个类 直接调用 read_config(),然后把People自己当做第一个参数传入read_config() # 接下来的效果:People实例化了setting中的name、age、gender,并实例化的对象是obj,把obj这个对象返回并赋值给people, 即people成了实例化后的对象 people.tell() # 运行结果: # Name:苍老师 Age:22 Gender:female
3. """非绑定方法"""
import hashlib import time class People: def __init__(self,name,age,gender): self.name = name self.age = age self.gender = gender self.id = self.create_id() # 为__init__ 添加一个 id的属性, id属性的值来源于 create_id()函数的返回值 """功能目标:根据时间的不同利用hashlib,给对象自动生成一个id""" @staticmethod def create_id(): # 同理,create_id函数中先不写参数,后面功能体的代码再决定写入什么参数 m = hashlib.md5(str(time.time()).encode("utf-8")) # 再把m hexdigest就得到了想要的结果,可以发现这个函数不需要传参,也不需要对象或者类的自动传入,所以应该用 @staticmethod """ hashlib.md5(str(time.time()).encode("utf-8")) 分析: 这句代码的效果相当于: m = hashlib.md5() m.update(str(time.time()).encode("utf-8")) # m.update只能处理bytes格式,而time.time()是一个数字,先将它str,再利用 .encode("utf-8") 将其变成bytes格式 """ return m.hexdigest() people1 = People("neo",22,"male") time.sleep(0.1) people2 = People("苍老师",20,"female") time.sleep(0.1) people3 = People("美奈子",18,"female") print(people1.id) print(people2.id) print(people3.id) # 运行结果: # 972d042a8c4ab3f6647764d6acf685b6 # ad91c2e65d01f0d1059178a587ca0342 # 96bb3ba6d752fd04b4d09516a951cb39