面向过程和面向对象
面向过程的程序设计的核心是过程,即解决问题的步骤。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计的核心是对象(上帝式思维),何为对象,世间存在的万物皆为对象,不存在也可以创建。
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法像面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
类:具有相同特征的一类事物(人、狗、老虎)
对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
实例化:类——>对象的过程(这在生活中表现的不明显)
类
在python中,用变量表示特征,用函数表示技能,因而具有相同特征和技能的一类事物就是‘类’,对象则是这一类事物中具体的一个。
类的作用有两种:属性引用和实例化
属性引用(类名.属性)
class Person: #定义一个人类
role = 'person' #人的角色属性都是人
def walk(self): #人都可以走路,也就是有一个走路方法
print("person is walking...")
print(Person.role) #查看人的role属性
print(Person.walk) #引用人的走路方法,注意,这里不是在调用
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
role = 'person' #人的角色属性都是人 #静态属性,类的属性 def __init__(self,name): #内置 初始化方法 self.name = name # 每一个角色都有自己的昵称; def walk(self): #人都可以走路,也就是有一个走路方法 #动态属性,对象的方法
print("person is walking...")
print(Person.role) #查看人的role属性
print(Person.walk) #引用人的走路方法,注意,这里不是在调用
实例化的过程就是类——>对象的过程
语法:对象名 = 类名(参数)
eg = Person('egon') #类名()就等于在执行Person.__init__()
#执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
#你可以偷偷的理解:eg = {'name':'egon','walk':walk}
查看属性&调用方法
print(eg.name) #查看属性直接 对象名.属性名
print(eg.walk()) #调用方法 对象名.方法名()
关于self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做。
因为你瞎改别人就不认识
类属性的补充
一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
二:特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类(在讲继承时会讲)
类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
对象/实例只有一种作用:属性引用
引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也管它叫动态属性。引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号
计算圆的周长和面积
from math import pi class Circle: ''' 定义了一个圆形类; 提供计算面积(area)和周长(perimeter)的方法 ''' def __init__(self,radius): self.radius = radius def area(self): return pi * self.radius * self.radius def perimeter(self): return 2 * pi *self.radius circle = Circle(10) #实例化一个圆 area1 = circle.area() #计算圆面积 per1 = circle.perimeter() #计算圆周长 print(area1,per1) #打印圆面积和周长
总结如下:
class 类名:
def __init__(self,参数1,参数2):
self.对象的属性1 = 参数1
self.对象的属性2 = 参数2
def 方法名(self):pass
def 方法名2(self):pass
对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西
#类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
#括号里传参数,参数不需要传self,其他与init中的形参一一对应
#结果返回一个对象
对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
由于类和对象之间存在一个关联关系,所以对象能找到类的属性,但类不能找到对象的属性。
写一个类,能统计这个类被多少个对象实例化了
class Foo: num = 0 def __init__(self): Foo.num += 1 f1 = Foo() f2 = Foo() print(Foo.num)
组合
组合是指一个类的对象的属性是另一个类的对象。
比如圆环和圆 :圆环是由两个圆组成的,圆环中有圆
from math import pi class Circle: def __init__(self,r): self.r = r def area(self): return pi * (self.r ** 2) def perimeter(self): return 2 * pi * self.r # c = Circle(5) # c.area() class Ring: def __init__(self,outer,inner): self.outer = Circle(outer) #Circle(outer) = c 是上一个类的对象,作为这个类的对象的属性,就是组合的应用 # 这里的self.outer = 上一个类的对象,即c self.inner = Circle(inner) def area(self): #计算圆面积= pi*(self.outer ** 2) 其中self.outer = c 借助上一个类中计算圆面积的方法 c.area() return self.outer.area() - self.inner.area() def perimeter(self): #周长同理 return self.outer.perimeter() + self.inner.perimeter() r = Ring(5,2) print(r.area(),r.perimeter())
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
面向对象的三大特性
面向对象的三大特性:继承,封装,多态。
1.继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。
三大特性是所有面向对象共有的特性,继承在其他的语言中比如:Java,只能继承一个父类,而python可以继承多个父类,这是独属于python的特点。
类的继承又分单继承和多继承。
class A: pass class C: pass class B(A): #单继承 A是基类,父类 B是派生类,子类 pass class D(A,C): #多继承 pass #可以通过__base__和__bases__查看继承 print(B.__base__) #<class '__main__.A'> print(D.__base__) #<class '__main__.A'> D类继承两个父类 只打印了第一个,所以__base__用于查看从左往右继承的第一个父类。 print(D.__bases__) #(<class '__main__.A'>, <class '__main__.C'>) __bases__用于查看所有继承的父类 print(A.__bases__) #(<class 'object'>,) 在python3中,如果没有指定基类,python会默认继承object类 #所以object是所有python3中类的基类。
备注:在python3中object是所有类的基类
继承与抽象 (先抽象再继承)
在类中是怎样实现继承的呢,继承之前要先抽象。
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
继承的作用:1.减少代码的重用 2.提高代码可读性 3.规范编程模式
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
#定义一个猫和一只狗,都有自己的名字,种类,食物,都要吃喝拉撒睡 #狗会看门,猫会上树 按照之前的书写会造成代码大量的重复,所以可以采用继承来实现 class Pet: def __init__(self,name,kind,food): self.name = name self.kind = kind self.food = food def eat(self): print('%s吃%s'%(self.name,self.food)) def drink(self): print('%s喝水'%self.name) def sleep(self): print('%s睡觉'%self.name) class Dog(Pet): def watch(self): print('%s看门'%self.name) class Cat(Pet): def climb(self): print('%s上树'%self.name) dog = Dog('狗','ketty','狗粮') cat = Cat('Tom','暹罗猫','猫粮') dog.eat(),cat.eat(),cat.climb()
在开发程序的过程中,我们定义了一个类A,然后又新建立另外一个类B,如果类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。这样不仅可以重用自己的类,也能继承别人的,比如标准库,来定制新的数据类型,这样就大大缩短了软件开发周期,对大型软件开发来说,意义重大.
派生
我们在继承别的类,就遗传了其所有的属性,那我们要实现添加自己的新属性或者重新定义这些属性(不影响父类的属性)时,就要用到派生这个东东。需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己的为准了。
#人和狗有相同的属性,也有属于自己的属性 class Animal: def __init__(self,name,food): self.name = name self.food = food def eat(self): print('%s吃%s' % (self.name,self.food)) def drink(self): print('%s喝水'%self.name) def sleep(self): print('%s睡觉'%self.name) class Person(Animal): def __init__(self,name,food,sex): self.sex = sex #派生出了新属性 Animal.__init__(self,name,food) def music(self): #派生出的新技能 print('%s在听音乐'%self.name) class Dog(Animal): def __init__(self,name,food,kind): self.kind = kind Animal.__init__(self, name, food) def watch(self): print('%s在看门' % self.name) person = Person('阿杰','面条','male') dog = Dog('黑皇','狗粮','不详') print(person.sex,dog.kind) person.music()
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值.
在python3中,子类执行父类的方法也可以直接用super方法.
super的用法
class Animal: def __init__(self,name,food): self.name = name self.food = food def eat(self): print('%s吃%s' % (self.name,self.food)) def drink(self): print('%s喝水'%self.name) def sleep(self): print('%s睡觉'%self.name) class Person(Animal): def __init__(self,name,food,sex): self.sex = sex # Animal.__init__(self,name,food) super().__init__(name,food) #super(Person, self).__init__(name,food) #单继承中 super会寻找父类,且在使用super调用父类方法的时候不需要再传self参数 def music(self): print('%s在听音乐'%self.name) #在这里如果也想调用父类中的方法时,直接用super().方法名 super().eat() class Dog(Animal): def __init__(self,name,food,kind): self.kind = kind Animal.__init__(self, name, food) def watch(self): print('%s在看门' % self.name) person = Person('阿杰','面条','male') dog = Dog('黑皇','狗粮','不详') print(person.sex,dog.kind) person.music() super(Person,person).eat() #在最外面也可以用super直接调用父类的方法,super(子类名,对象) #但是这样使用没有意义,用的很少
在单继承中,不要发生循环继承
class A:pass class B(A):pass class C(B):pass class D(C):pass d = D() #这样继承是可以的 class A:pass class B(A):pass class C(A,B):pass c = C() print(C.__bases__) #就会报错
多个类的继承顺序
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): def test(self): print('from F') f1=F() f1.test() print(F.__mro__) #只有新式类才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
在python3中都是新式类,而在python2中才会区分新式类和经典类。
在新式类中我们可以通过 子类名.__mro__ 这个方法查看继承关系。
继承原理:
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__ [<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类
MRO列表是在2.3中添加的一个C3线性算法实现的,其内部的实现原理是:先获得该类按照广度优先继承顺序的父类表,再将该类继承的所有父类按照深度优先继承的顺序表,取第一个表的表头(表中的第一个类),和其他表中的内容(除去第一个类的其他类)作比较,如果其他表中没有该表头,则将该表头取出,放到类的mro列表中,然后,对所有的表的表头进行去重;如果其他表内容中有该表头,则继续取下一个表的表头。按照这个顺序依次取,直到取完为止。
class A(object):pass class B(object):pass class C(object):pass class D(A,B):pass class E(B,C):pass class F(D,E):pass print(F.mro()) #D --> DABO E --> EBCO # DABO EBCO DE # FD ABO EBCO E #FDA BO EBCO E # FDAE BO BCO # FDAEB O CO #FDAEBCO
接口类和抽象类
接口类
在python中没有接口的概念,接口在Java、C+中是一种特殊的数据类型(Interface)。
接口原本不是python中的东西,在了解接口之前,先来了解两个开发原则:接口隔离原则和依赖倒置原则。
#我们模拟支付功能 class Wechatpay: '''微信支付''' def pay(self,money): print('微信支付%s'%money) class Alipay: '''阿里支付''' def pay(self,money): print('支付宝支付%s'%money) class Applepay: '''苹果支付''' def pay(self,money): print('苹果支付%s'%money) #上述都是单独的支付,我们可以设置一个函数,来负责总体支付 #支付函数 对应两个参数:支付对象和支付金额 def pay(pay_obj,money): #这样我们就模拟了一个关于支付的接口 程序的归一化设计 pay_obj.pay(money) pay(Alipay(),120) #支付宝支付120 pay(Wechatpay(),100) #微信支付100
上述我们通过一个函数,实现了总体支付这个功能,这就是归一化。我们可以继承和abc模块来实现接口。
#我们可以通过使用raise关键字来抛出异常 class Payment: def pay(self,money): '''注释''' raise NotImplementedError #通过raise 抛出异常 来规范子类的支付方法 class Alipay(Payment): '''阿里支付''' def pay(self,money): print('支付宝支付%s'%money) a = Alipay() a.pay(500)
接口类就是一个规范。
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): #用来规范和约束支付的方法 @abstractmethod def pay(self,money): '''注释''' pass class Wechatpay(Payment): '''微信支付''' def pay(self,money): print('微信支付%s'%money) class Alipay(Payment): '''阿里支付''' def pay(self,money): print('支付宝支付%s'%money) class Applepay(Payment): '''苹果支付''' def pay(self,money): print('苹果支付%s'%money) def pay(pay_obj,money): pay_obj.pay(money) pay(Alipay(),120) #支付宝支付120 pay(Wechatpay(),100) #微信支付100
继承有两种用途:1.代码重用 2.接口继承。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
接口提取了一群类共同的函数,可以把接口当做一个函数的集合。然后让子类去实现接口中的函数,这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
接口隔离原则:使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
#在动物园中,每个动物都有相同的属性和不同的属性,这是我们不能直接一次性的规范,有的动物自身没有某个功能, # 但是,一次性的统一,会导致自身没有,却可以使用父类的属性,与我们的需求不符,这是就需要一个原则,接口隔离原则 from abc import ABCMeta,abstractmethod class FlyAnimal(metaclass=ABCMeta): @abstractmethod def fly(self): print('在飞') class SwimAnimal(metaclass=ABCMeta): @abstractmethod def swim(self): print('在游泳') class WalkAnimal(metaclass=ABCMeta): @abstractmethod def walk(self): print('在奔跑') class Swan(FlyAnimal,SwimAnimal,WalkAnimal): def fly(self): print('在飞') def swim(self): print('在游泳') def walk(self): print('在奔跑') class Bird(FlyAnimal,WalkAnimal): def fly(self): print('在飞') def walk(self): print('在奔跑') class Panguin(SwimAnimal,WalkAnimal): def swim(self): print('在游泳') def walk(self): print('在奔跑')
接口类不能被实例化
from abc import ABCMeta,abstractmethod class FlyAnimal(metaclass=ABCMeta): @abstractmethod def fly(self): print('在飞') fly = FlyAnimal() #接口类不能实例化 print(fly) #直接报错 TypeError: Can't instantiate abstract class FlyAnimal with abstract methods fly
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程
抽象类
与java一样,python有抽象类的概念,但是同样需要借助模块实现。抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
什么是抽象类?类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,简而言之,抽象类是规范一个类的类,内容包括数据属性和函数属性。抽象类是一个介于类和接口之间的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的。
在python中抽象类和接口类没有区别。无论抽象类还是接口类其实都是一种面向对象编程的开发规范,只是去约束继承它们的子类必须实现某些方法。
在Java中抽象类和接口类是有区别的。Java中的类是不支持多继承的,通过接口来实现多继承,但是规定里面的方法一定不能实现,简而言之,就是接口类中的代码无法被执行,一句也不能写。而Java中的抽象类一定是单继承的。所以,如果在Java中,发生多继承,那么它一定是接口类,且其中的方法不会被执行;如果在方法里有了实现,那么一定是单继承的抽象类。
在继承抽象类的过程中,我们应该尽量避免多继承;而在继承接口的时候,我们反而鼓励你来多继承接口。
在抽象类中,我们可以对一些抽象方法做出基础实现;而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现。
2.多态
多态指的是一类事物有多种形态。python是自带多态的。
对于某些强数据类型语言,在归一化的设计中(函数的参数问题上),需要传递参数的数据类型,为了几个类的对象都可以作为参数传递给函数,就给这几个类创建一个父类,让父类成为这个参数的数据类型,这样,子类的所有对象就都能够作为参数传递了。在python中,传递参数的时候是弱数据类型特征,不需要指定参数的数据类型。所以python是自带多态的。
在python中,多个有相似特征的数据类型之间,不通过继承建立关系,而是通过一种约定俗成的制度来约束。这是python所崇尚的。
3.封装
【封装】
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
【好处】
1. 将变化隔离;
2. 便于使用;
3. 提高复用性;
4. 提高安全性;
【封装原则】
1. 将不需要对外提供的内容都隐藏起来;
2. 把属性都隐藏,提供公共方法对其访问。
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
class A: a = 'A' __b = 'B' #变量名前加__双下划线,就变为一个私有的静态变量 def __init__(self,name,price): self.name = name self.__price = price #变成一个私有的属性 def open(self): print('in open') def __close(self): #私有的方法 print('in close') print(A.a) # print(A.__b) #直接报错,在外面无法调用了 这个静态变量变成了私有的静态变量 a = A(1,2) # print(a.__price) #同理 报错AttributeError: 'A' object has no attribute '__price' A.__c = 'C' print(A.__dict__) #不能在一个类的外部创建一个'私有的变量'
#其实这仅仅这是一种变形操作 #类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式: class A: __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N def __init__(self): self.__X=10 #变形为self._A__X def __foo(self): #变形为_A__foo print('from A') def bar(self): self.__foo() #只有在类内部才可以通过__foo的形式访问到. #A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
这种自动变形的特点:
1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
class A: def file(self): print('file') def __test(self): print('test') class B(A): def read(self): pass b = B() print(b.__test()) #报错 AttributeError: 'B' object has no attribute '__test' 这样就无法调用父类的方法
封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。
class Room: def __init__(self,owner,single_price,length,width): self.owner = owner self.__single_price = single_price self.__length = length self.__width = width def get_area(self): return self.__length * self.__width def get_price(self): return self.__single_price * self.get_area() room = Room('dog',10000,2,1) print(room.get_area(),room.get_price()) ##对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积,价格
面向对象中常用的三大内置方法
在面向对象中,有三个非常重要的、很有用的内置方法:property,classmethod,staticmethod
1.property
作用:将一个方法伪装成一个属性
class BMI: def __init__(self,name,height,weight): self.name = name self.__height = height self.__weight = weight @property def bmi(self): return self.__weight / (self.__height**2) b = BMI('1',1.70,56) print(b.bmi) #这样就实现了通过查看属性的形式调用了方法
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
ps:面向对象的封装有三种方式:
【public】
这种其实就是不封装,是对外公开的
【protected】
这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开
【private】
这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
property本质就是实现了get,set,delete三种方法
class Foo: def __init__(self,name): self.__name = name @property #get def name(self): return self.__name @name.setter #改 set def name(self,new_name): #这样就实现了对私有属性的修改,通过一个判断,使得要修改的值更安全 if type(new_name) == str: #使得修改更合理 self.__name = new_name @name.deleter # 删 delete def name(self): del self.__name f = Foo('lang') print(f.name) #这样就实现了对变量的保护,无法通过这个值修改了 f.name = 234 print(f.name) del f.name print(f.name) #就实现了删除操作 # 这样就可以把一个方法伪装的非常像
2.classmethod
作用:将一个普通的方法装饰为一个类的方法
class A: a = 'role' @classmethod def creat(cls,new): #默认一个参数为类名:cls cls.a = new print(A.a) A().creat('person') print(A.a) #类方法是被@classmethod装饰的特殊方法 #被装饰之后,方法默认接收一个 类 作为参数 # 之后所有的操作都只能和 类中的静态变量相关 而不应该和对象相关 # 类名 和 对象名 都可以直接调用类方法
3.staticmethod
作用:将一个方法(带self的函数)转化为一个普通函数,不用传self参数
class A: def shop(self): pass @staticmethod def login(): #不用传self pass
另外,在面向对象中,有两个用来判断的内置函数:isinstance(对象与类之间的关系)和issubclass(类与类之间的关系)。
isinstance(obj,cls)检查obj是否是类 cls 的对象(实例)。
class Foo(object):
pass
obj = Foo()
isinstance(obj, Foo)
备注:isinstance与type的区别:isinstance可以用来判断一个对象与创建它的类的父类的关系,如果是,返回True。而type 只能判断创建它的类,不能判断到父类。
issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo(object):
pass
class Bar(Foo):
pass
issubclass(Bar, Foo)
反射
在Python面向对象中反射的定义是:通过字符串的形式操作对象的相关属性。在Python中,一切皆对象(类本身也是个对象),都可以使用反射。
在Python中有四个可以实现自省的函数:hasattr、getattr、setattr、delattr。
应用场景:网络编程,从文件中读取信息反映到编程中。
class Student: name = 'student' def __init__(self,name,cls,course): self.name = name self.cls = cls self.course = course def show(self): for key in self.__dict__: print(key,self.__dict__[key]) def study(self): print('study in class') @classmethod #(类方法) def read(self): print('read book') @staticmethod #(类的静态方法) def write(): print('write task') stu = Student('somebody','six','python') #hasattr 用于判断是否含有该属性 返回True 或 False print(hasattr(Student,'name')) #True 存在为True print(hasattr(stu,'study')) #True 存在为True print(hasattr(stu,'listen')) #False 不存在False print(hasattr(Student,'read')) # 存在为True #getattr 获取属性 不存在则报错,常与hasattr 搭配使用 print(getattr(Student,'name')) #student print(getattr(Student,'study')) #<function Student.study at 0x000001BE9935D7B8> func = getattr(Student,'read') print(func) #<bound method Student.read of <class '__main__.Student'>> func() #read book print(getattr(Student,'write')) #<function Student.write at 0x0000020E4E54D8C8> getattr(stu,'study')() # study in class print(getattr(stu,'shop')) #不存在则报错 AttributeError: 'Student' object has no attribute 'shop' print(getattr(stu,'show')()) #setattr 创建属性和修改属性 setattr(object,name,value) print(stu.__dict__) #{'name': 'somebody', 'cls': 'six', 'course': 'python'} setattr(stu,'age',11) #增加属性 print(stu.__dict__) #{'name': 'somebody', 'cls': 'six', 'course': 'python', 'age': 11} setattr(stu,'cls','five') #修改属性 print(stu.__dict__) #{'name': 'somebody', 'cls': 'five', 'course': 'python'} #delattr 删除属性 delattr(stu,'course') print(stu.__dict__) #{'name': 'somebody', 'cls': 'six'}
四个方法中hasattr常和getattr搭配使用
if hasattr(object,‘字符串格式的变量名’):
getattr(object,‘字符串格式的变量名’) #两者括号中的内容一样
反射当前模块 (sys.modules[__name__])
import sys count = 10 def func(): return 'Hello World' obj = sys.modules[__name__] #模块本身也是一个对象 {'字符串数据类型的模块名':模块的内存地址} print(getattr(obj,'count')) #10 print(getattr(obj,'func')()) #Hello World
反射类、对象、当前模块、其他模块中的名字
反射类中的名字
getattr(类名,'静态属性')
getattr(类名,'类方法')() ---> @classmethod
getattr(类名,'静态方法')() --->@staticmethod
反射对象中的名字
getattr(对象名,'对象属性')
getattr(对象名,'方法名')()
反射模块中的名字
import 模块名
getattr(模块名,'模块中的变量')
getattr(模块名,'模块中的函数名')()
getattr(模块名,'模块中的类名')
反射当前模块中的名字
import sys
getattr(sys.modules[__name__],'变量')
getattr(sys.modules[__name__],'函数')
getattr(sys.modules[__name__],'类名')
类中的内置方法
类中的内置方法表现方式为:__方法名__。又叫魔术方法或者双下方法。是直接与python语法隐性相关的。
__doc__ 输出类的描述信息(注释)
class Foo: '''描述类的信息:''' def func(self): pass print(Foo.__doc__) #输出:描述类的信息:
__dict__ 类或对象的成员,类调用打印类的所有属性,不包括实例属性。实例调用打印所有实例属性
class Person: role = '人类' def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def eat(self): print('eat food there') def sleep(self): print('sleep in room') man_obj = Person('小明',18,'male') women_obj = Person('小红',16,'female') print(Person.__dict__) #{'__module__': '__main__', 'role': '人类', '__init__': <function Person.__init__ at 0x000001848715FAE8>, # 'eat': <function Person.eat at 0x000001848715FB70>, 'sleep': <function Person.sleep at 0x000001848715FBF8>, # '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, # '__doc__': None} print(man_obj.__dict__) #{'name': '小明', 'age': 18, 'sex': 'male'} print(women_obj.__dict__) #{'name': '小红', 'age': 16, 'sex': 'female'}
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
__len__ 计算个数
class Person: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __len__(self): #实现计算对象属性个数 return len(self.__dict__) a =Person('one',2,3) print(len(a)) # 3 class中没有__len__内置方法会直接报错,len() 会直接调用类中的__len__这个方法
__eq__ ==运算
class A: def __init__(self,name,age): self.name = name self.age = age a = A('sbd',23) b = A('sbd',23) print(a == b) #False 两者__dict__内容相同 但是内存地址不同 print(a.__dict__ == b.__dict__) #True class A: def __init__(self,name,age): self.name = name self.age = age def __eq__(self, other): if self.__dict__ == other.__dict__: return True a = A('sbd',23) b = A('sbd',23) print(a == b) #True == 等同于调用了__eq__方法 print(a.__dict__ == b.__dict__) #True
__format__ 自定制格式化字符串
class School: def __init__(self,name,addr,type): self.name=name self.addr=addr self.type=type def __format__(self, format_spec): return format_spec.format(self.name,self.addr,self.type) s1=School('oldboy','北京','私立') print(format(s1,'{} * {} * {}')) #oldboy * 北京 * 私立 format () 等同于调用__format__方法
__str__ 和__repr__ 改变对象的字符串显示
class Foo: def __init__(self,name,age,addr): self.name = name self.age = age self.addr = addr a = Foo('sone',21,'beijing') # 在这里两者是一样的 print(a) #<__main__.Foo object at 0x000001E8E27828D0> print(str(a)) #<__main__.Foo object at 0x000001E8E27828D0> print('%s'%a) #<__main__.Foo object at 0x000001E8E27828D0> print(repr(a)) #<__main__.Foo object at 0x000001E8E27828D0> print('%r'%a) #<__main__.Foo object at 0x000001E8E27828D0> class Foo: def __init__(self,name,age,addr): self.name = name self.age = age self.addr = addr def __str__(self): return str(self.__dict__) a = Foo('sone',21,'beijing') #class中只含有__str__方法时,只转化str自己的(str(),%s) 不转化repr的 print(a) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print(str(a)) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print('%s'%a) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print(repr(a)) #<__main__.Foo object at 0x0000022B59B1A550> print('%r'%a) #<__main__.Foo object at 0x0000022B59B1A550> class Foo: def __init__(self,name,age,addr): self.name = name self.age = age self.addr = addr def __repr__(self): return repr(self.__dict__) a = Foo('sone',21,'beijing') #class中只含有__repr__方法时,所有的str和repr 都转化 print(a) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print(str(a)) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print('%s'%a) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print(repr(a)) #{'name': 'sone', 'age': 21, 'addr': 'beijing'} print('%r'%a) #{'name': 'sone', 'age': 21, 'addr': 'beijing'}
str函数或者print函数--->obj.__str__()
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常
__del__ 析构方法:删除一个对象的时候调用的方法
析构函数的调用是由python解释器在进行垃圾回收时自动触发执行的。也可以人为的调用del 时触发。
class A: def __init__(self,name,age): self.name = name self.age = age def __del__(self): print('执行了此处') a = A(1,2) print(a.__dict__) #{'name': 1, 'age': 2} #执行了此处 此时我们没有执行删除操作,解释器会自动触发执行
__del__有一些非常重要的应用场景:比如:打开一个文件,或者一个数据库,或者在网络通信中,打开了一个链接。对其操作完之后,我们可以通过__del__方法做一些收尾的工作,比如一定要关闭文件,关闭数据库,关掉网络连接。
class A: def __init__(self): self.f = open('文件路径','a') def consume(self): '''比如消费者的消费记录''' pass def __del__(self): '''做收尾工作,关闭文件''' self.f.close() #这样在删除文件时就会自动的先执行此处的操作
__new__ 构造方法:创建一个对象的方法
元类:type 创建类的东西
在python中,所有的创建都不是凭空出现的,我们在创建类时,(class 类名(metaclass = type):默认不写)python会触发执行它的元类,由元类来构造,python中所有类的元类默认是type()。在抽象类中,对父类规范时,(class 类名(metaclass = ABCMeta))此时,构造它的元类就是ABCMeta,这时构造的类不能被实例化。同样的,在创建对象时(类的实例化),会自动触发执行object类中的__new__方法构造对象,将构造的对象返回传递给__init__方法(初始化方法),在通过传参给self,self返回给对象名。所以类是type的对象。
#__new__方法在object类中的形式 #被装饰成一个静态方法,默认传一个类名。 @staticmethod # known case of __new__ def __new__(cls, *more): # known special case of object.__new__ """ Create and return a new object. See help(type) for accurate signature. """ pass --->c语言写的源码(接口)
#我们创建一个对象,看它执行的路径 class Foo: def __init__(self): print('执行init方法了') def __new__(cls, *args, **kwargs): print('执行new方法了') return object.__new__(cls) a = Foo() #输出:执行new方法了 先执行new方法 #执行init方法了
设计模式:
在提到__new__方法时,我们首先会想到单例模式。
单例模式:一个类可以被多次实例化,但是同一时间在python的内存中,只能有一个实例。
class A: pass a1 = A() a2 = A() print(a1,a2) #<__main__.A object at 0x000002EFACE2B5C0> <__main__.A object at 0x000002EFACE2B2E8> # 此时内存中同时存在多个实例化对象 class A: _instance = None def __new__(cls, *args, **kwargs): if not A._instance: A._instance = object.__new__(cls) return A._instance a1 = A() a2 = A() print(a1,a2) #<__main__.A object at 0x000002263996B5C0> <__main__.A object at 0x000002263996B5C0> #此时在内存中只存在一个内存地址,就实现了单例模式 #另一种方式 class A: def __init__(self,name): self.name = name def __new__(cls, *args, **kwargs): if not hasattr(A,'_instance'): A._instance = object.__new__(cls) return A._instance a = A('rain') b = A('snow') print(a.name,b.name) #snow snow 此时,b=a
__call__ 对象后面加括号,触发执行
对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __call__(self, *args, **kwargs): print('执行了__call__') Foo()() #执行了__call__ a = Foo() a() #执行了__call__
__hash__ hash(object)函数,object对象对应的类必然内部实现了__hash__方法,返回__hash__方法的返回值。
class Foo: def __init__(self,name,age): self.name = name self.age = age def __hash__(self): return hash('%s+%s'%(self.name,self.age)) a = Foo('rain',24) print(hash(a)) #__hash__() 方法的返回值
item系列 __getitem__ __setitem__ __delitem__
class Foo: def __init__(self,name,age): self.name = name self.age = age def eat(self): print('eat in Foo') def __getitem__(self, item): return self.__dict__[item] def __setitem__(self, key, value): #增改操作 self.__dict__[key] = value def __delitem__(self, key): #删除操作 del self.__dict__[key] a = Foo('swan',25) print(a.name) #swan a.eat() print(a['name']) #swan 这是我们就实现了这种方式的调用 a['sex'] = 18 print(a.__dict__) del a['age']
集合set的去重在内部执行了__hash__和__eq__方法
#一个类 产生了100个对象每个对象包含name,age,sex 三种属性,要求对这一百个对象去重, # 如果ame和sex相同,即使age不同,也是相同的对象,请用最简单的方法 class Person: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __hash__(self): return hash('%s %s'%(self.name,self.sex)) def __eq__(self, other): if self.name == other.name and self.sex == other.sex: return True l1 = [] for i in range(100): person = Person('sun',i,'male') l1.append(person) print(l1) print(set(l1))
list
纸牌的实现
from collections import namedtuple Card = namedtuple('Card',['x','y']) class FranchDeck: ranks = [str(i) for i in range(2,11)] + list('JQKA') suits = ['红桃','方片','黑桃','梅花'] def __init__(self): self.card = [Card(suit,rank) for rank in FranchDeck.ranks for suit in FranchDeck.suits] def __getitem__(self, item): return self.card[item] def __len__(self): return len(self.card) def __setitem__(self, key, value): self.card[key] = value deck = FranchDeck() print(deck[:]) #有序的输出一整套纸牌 print(deck[0]) #按照索引找元素 from random import choice print(choice(deck)) #随机出一张牌 from random import shuffle shuffle(deck) #打乱顺序 洗牌 print(deck[:])