标签:oca [] ndt 除了 单元 软件维护 哈哈 c99 .class
一、什么是面向对象的程序设计
1、何为数据结构?
数据结构是指相互之间存在一种或多种特定关系的数据元素的集合,如列表、字典。
2、何为编程?
编程是指程序员用特定的语法+数据结构+算法,组成的代码,告诉计算机如何执行任务的过程。
3、何为编程范式?
实现一个任务的方式有很多种,对这些不同的编程方式的特点进行归纳总结得出的编程方式类别即为编程范式。
大多数语言只支持一种编程方式,也有些支持多种的。
两种最重要的编程范式:面向过程编程和面向对象编程。
4、面向过程编程(Procedural programming)
面向过程编程的核心是“过程”,过程即指解决问题的步骤。
优点:将复杂的问题流程化,简单化。
缺点:可扩展性差,后期如果要更改某一程序功能是,可能存在“牵一发而动全身”,软件的维护难度会越来越高。
应用场景:适用于那些功能一旦实现就不会轻易更改的情况,如Linux内核,git,Apache HTTP Server等。
5、面向对象编程(Object Oriented Programming)
面向对象,核心就是“对象”二字,对象就是“特征”与“技能”的结合体。
优点:可扩展性强,易更改,易维护。
缺点:编程复杂度高
应用场景:应用于用户需求经常变化的场景,如互联网应用、企业内部应用、游戏等。
面向对象编程:就是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,主要用于解决软件开发中可扩展性的问题。
6、面向对象三大特性:继承、多态、封装
二、类与对象
1、python中一切皆对象。
2、对象:指特征与技能的结合体。
3、类:指一系列对象相似的特征与技能的结合体,类就相当于一个模型。
4、站在不同的角度,得到的分类是不一样的。
5、类与对象的产生
在现实世界中:先有对象,后有类
在程序中:先定义类,后调用类来产生对象
与函数的使用是类似的:先定义函数,后调用函数,类也是一样的:在程序中需要先定义类,后调用类。不一样的是:调用函数会执行函数体代码,返回的是函数体执行的结果,而调用类会产生对象,返回的是对象。
6、如何定义类
在程序中,用class关键字定义一个类,类的特征用变量标识,即数据属性,类的技能用函数标识,即函数属性。
类在定义阶段就会被执行,会产生新的名称空间,用来存放类的变量名和函数名,可以通过“类名.__dict__”查看类的名称空间,返回的是一个字典。对于经典类来说,我们可以通过该字典操作类名称空间的名字,但新式类有限制。
7、如何使用类
class Person(object): """人类""" def __init__(self, name, age): self.name = name self.age = age def talk(self): print("hello, my name is %s, I‘m %s years old" % (p.name, p.age)) p = Person("jack", 22) print(p.name, p.age) # 查看对象属性 p.talk() # 注意这里调用并未传递参数 # 新增类属性 Person.country = "China" # 给类新增一个属性 # Person.__dict__[‘country‘] = "China" # 不支持这样新增 # 查看类属性 print(Person.__dict__["country"]) # 查看属性 print(Person.__dict__) # 查看所有属性 # 删除类属性 del Person.country # 删除属性 # Person.__dict__.pop("country") # 不支持这样删除 print(Person.__dict__) # 查看属性,发现country属性已不存在 """ 输出: jack 22 hello, my name is jack, I‘m 22 years old China {‘__module__‘: ‘__main__‘, ‘__doc__‘: ‘人类‘, ‘__init__‘: <function Person.__init__ at 0x0000000001E48950>, ‘talk‘: <function Person.talk at 0x0000000001E489D8>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘Person‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Person‘ objects>, ‘country‘: ‘China‘} {‘__module__‘: ‘__main__‘, ‘__doc__‘: ‘人类‘, ‘__init__‘: <function Person.__init__ at 0x0000000001E48950>, ‘talk‘: <function Person.talk at 0x0000000001E489D8>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘Person‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Person‘ objects>}
类的两大用途:对属性的操作、实例化对象。
8、使用__init__方法定制对象独有的特征
__init__方法被称为构造方法或初始化方法。在对象实例化的时候就会自动调用该方法,实现对象的初始化操作,在__init__方法中不能有return返回值。
class LuffyStudent: school = "luffycity" def __init__(self, name, sex, age): # 使用构造方法初始化对象 self.name = name self.sex = sex self.age = age def learning(self, x): print("%s is learning %s" % (self.name, x)) stu1 = LuffyStudent("alex", "男", 22) stu2 = LuffyStudent("rain", "男", 23) stu3 = LuffyStudent("laura", "女", 22) print(LuffyStudent.__dict__)
9、对象属性的操作
class LuffyStudent: school = "luffycity" def __init__(self, name, sex, age): # 使用构造方法初始化对象 self.name = name self.sex = sex self.age = age def learning(self, x): print("%s is learning %s" % (self.name, x)) stu1 = LuffyStudent("alex", "男", 22) stu2 = LuffyStudent("rain", "男", 23) stu3 = LuffyStudent("laura", "女", 22) # print(LuffyStudent.__dict__) # 修改 stu1.name = "Alex" # 等同于:stu1.__dict__["name"] = "Alex" print(stu1.__dict__) # 删除 del stu1.age # 等同于:stu1.__dict__.pop("age") print(stu1.__dict__) # 新增 stu1.course = "python" # 等同于:stu1.__dict__["course"] = "python" print(stu1.__dict__) 输出: {‘name‘: ‘Alex‘, ‘sex‘: ‘男‘, ‘age‘: 22} {‘name‘: ‘Alex‘, ‘sex‘: ‘男‘} {‘name‘: ‘Alex‘, ‘sex‘: ‘男‘, ‘course‘: ‘python‘}
10、类的属性查找与绑定方法
类有两种属性:数据属性和函数属性。
1、类的数据属性是所有对象共享的
2、类的函数属性在没有被任何装饰器修饰的情况下是绑定给对象用的,称为绑定到对象的方法,绑定到不同的对象就是不同的绑定方法,对象绑定方法时,会把对象本身当作第一个参数传入,即self==对象名。绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成的写为self。
三、继承
继承指类与类之间的一种什么“是”什么的关系。用于解决代码重用的问题。继承是一种新建类的方式,新建的类可以继承一个或多个父类,父类可以称为基类或超类,新建的类称为子类或派生类。
1、python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
2、查看继承:
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class ‘__main__.ParentClass1‘>,) >>> SubClass2.__bases__ (<class ‘__main__.ParentClass1‘>, <class ‘__main__.ParentClass2‘>)
3、继承中的属性查找:
# 属性查找 class Foo: def f1(self): print("from Foo.f1") def f2(self): print("from Foo.f2") self.f1() # 相当于b.fi() class Bar(Foo): def f1(self): print("form Bar.f1") b = Bar() print(b.__dict__) b.f2() 输出: {} from Foo.f2 form Bar.f1
调用f2方法,发现对象b中没有该方法,再去b的类Bar中找,也没有,接着去Bar类的父类Foo中找,找到了,就打印“from Foo.f2”,在Foo中还有语句“self.f1()”,这时,程序会又在对象b中找名为f1的方法,发现没有,再去b的类Bar中找,找到了,就打印“form Bar.f1”
4、python中经典类与新式类
在python3中如果不指定继承哪个类,默认就会继承Object类,而继承了Object类的类就叫做新式类。
python2中如果不指定继承哪个类也不会默认去继承Object类,而没有继承Object类的类就叫做经典类。而显示的指定继承Object类的类才是新式类。
经典类和新式类的不同就在于属性查找的顺序不同,经典类是深度优先(先一条路走到底),即先找自己类内,如果没有就找右边第一个父类,没找到继续从这个父类的父类中找依次类推直到找到最上一级的父类也没找到再找右边第二个父类,然后再重复之前的过程,直到所有父类找一遍没找到就报错;而新式类是广度优先,先按父类顺序逐个查找,如果没找到,最后才会去找object类,如果没有找到指定属性就报错。可以通过“类名.mro()”或“类名.__mro__”查看新式类继承中的属性查找顺序。
# python2中分有新式类和经典类,py3中只有新式类 #py2中,经典类:没有继承object的类以及它的子类 class Foo: pass class Bar(Foo): pass
# 在py2中,新式类:继承object的类,以及它的子类都称之为新式类 class Foo(object): pass class Bar(Foo): pass
# 在py3中,新式类:默认都继承object class Foo: # 等同于class Foo(object): pass class Bar(Foo): pass
下面是我在别处找到的比较经典的能够很好的展现深度优先和广度优先查找的图:
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‘) pass f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C
5、在自类中调用父类的方法
1)指名道姓,即父类名.父类方法(),这种方式不依赖于继承
2)使用super(),这种方式依赖于继承的,并且即使没有直接继承关系,super仍然会按照mro继续往后查找
class A: def f1(self): # 根据C.mro()列表,首先在A中找到f1 print("from A") super().f1() # 当程序运行到这一步时,super会沿着C.mro()列表往下找f1 class B: def f1(self): # 然后在B中找到f1 print("from B") class C(A,B): pass print(C.mro()) c = C() c.f1() # 运行程序,发现打印了A,也打印了B,因为这是基于C类引发的寻找,super只会根据C.mro()中的列表顺序一个接一个往下找 输出: [<class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘__main__.B‘>, <class ‘object‘>] from A from B
6、派生
子类可以给自己添加新的属性,或在自己这里重新定义父类的属性,而不会影响父类,一旦重新定义了自己的属性且与父类同名,那么调用该属性时,就会以自己重新定义的为准。
四、组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合是一种什么“有”什么的关系,如教师有课程,学生有课程。
class People: """人类""" school = "luffy" def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Teacher(People): """教师类""" def __init__(self, name, age, sex, level, salary): super(Teacher, self).__init__(name, age, sex) self.level = level self.salary = salary def teach(self): print("%s is teaching" % self.name) class Student(People): """学生类""" def __init__(self, name, age, sex, class_time): super().__init__(name, age, sex) self.class_time = class_time def learn(self): print("%s is learning" % self.name) class Course: """课程类""" def __init__(self, course_name, course_price, course_period): self.course_name = course_name self.course_price = course_price self.course_period = course_period def course_info(self): print("课程名:%s 价格:%s 课时:%s" % (self.course_name, self.course_price, self.course_period)) python = Course("python", 10000, "3month") linux = Course("linux", 8000, "2month") teach1 = Teacher("alex", 38, "male", 10, 3000) teach2 = Teacher("egon", 28, "male", 20, 4000) teach1.course = python # 给老师添加课程属性 teach2.course = python print(teach1.__dict__) print(teach2.__dict__) print(teach1.course.course_name) # 查看老师所教的课程名 teach2.course.course_info() # 查看课程信息 stu1 = Student("rain", 14, "male", "8:30") stu1.course1 = python stu1.course2 = linux stu1.course1.course_info() # 查看课程信息 stu1.course2.course_info() stu1.courses = [] stu1.courses.append(python) stu1.courses.append(linux) print(stu1.__dict__) 输出: {‘name‘: ‘alex‘, ‘age‘: 38, ‘sex‘: ‘male‘, ‘level‘: 10, ‘salary‘: 3000, ‘course‘: <__main__.Course object at 0x000000000251B518>} {‘name‘: ‘egon‘, ‘age‘: 28, ‘sex‘: ‘male‘, ‘level‘: 20, ‘salary‘: 4000, ‘course‘: <__main__.Course object at 0x000000000251B518>} python 课程名:python 价格:10000 课时:3month 课程名:python 价格:10000 课时:3month 课程名:linux 价格:8000 课时:2month {‘name‘: ‘rain‘, ‘age‘: 14, ‘sex‘: ‘male‘, ‘class_time‘: ‘8:30‘, ‘course1‘: <__main__.Course object at 0x000000000251B518>, ‘course2‘: <__main__.Course object at 0x000000000251B550>, ‘courses‘: [<__main__.Course object at 0x000000000251B518>, <__main__.Course object at 0x000000000251B550>]}
五、接口类与抽象类
接口类是用于规范子类的方法名定义用的,接口提取了一群类共同的函数,可以把接口当做一个函数的集合。继承接口类的子类可以不存在任何逻辑上的关系但是都需要实现某些共同的方法,为了让这些子类的方法名能够统一以便之后调用这些方法时不需要关注具体的对象就用接口类规范了这些方法的名字,子类一旦继承了接口类就必须实现接口类中定义的方法,否则在子类实例化的时候就会报错,而接口类本身则不需要实现去实现这些方法。
# 定义接口Interface类来模仿接口的概念 class Animal: # 接口类 all_type = "animal" # 接口名 def run(self): # 接口名,但不去实现它 pass def eat(self): # 接口名 pass class People(Animal): # 子类继承接口 def run(self): # 调用接口 print("people is walking") # 实现接口的功能 def eat(self): print("people is eating") class Pig(Animal): def run(self): print("pig is walking") def eat(self): print("pig is eating") class Dog(Animal): def run(self): print("dog is walking") def eat(self): print("dog is eating") poe1 = People() pig1 = Pig() dog1 = Dog() poe1.run() dog1.run() print(poe1.all_type)
上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,比如:在People类中,完全可以把run改为walk等方法名,也就是说,此时的父类对子类的调用方式,不具有约束力,
这就用到了抽象类:
# 如下代码,实现接口约束 import abc # 导入模块 class Animal(metaclass=abc.ABCMeta): # 这个Animal类只能被继承,不能被实例化 all_type = "animal" # 数据属性 @abc.abstractmethod # 装饰器 def run(self): # 定义抽象方法,无需实现功能 (函数属性) pass @abc.abstractmethod def eat(self): # 定义抽象方法,无需实现功能 pass class People(Animal): # 此时若在子类里面没有定义父类的run和eat方法,程序就会报错,父类相当于起到一个规范的作用 def run(self): # 子类必须定义run方法 pass # print("people is walking") def eat(self): # 子类必须定义eat方法 print("people is eating") class Pig(Animal): def run(self): print("pig is walking") def eat(self): print("pig is eating") class Dog(Animal): def run(self): print("dog is walking") def eat(self): print("dog is eating") poe1 = People() pig1 = Pig() dog1 = Dog() poe1.run() dog1.run() print(poe1.all_type) # 这样大家都是被归一化了,也就是一切皆文件的思想
抽象类的作用和接口类一样,只是继承它的子类一般存在一些逻辑上的关系。
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,
如果说类是从一堆对象中抽取相同的内容而来的,那么抽象类就是从一堆类中抽取相同的内容而来的,内容包括数据属性和函数属性。
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
抽象类与普通类的区别:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。
接口类与抽象类的区别:接口只强调函数属性的相似性。抽象类既包括函数属性又包括数据属性。
所以说,抽象类同时具备普通类和接口类的部分特性。
六、多态
1、多态指一类事物的多种形态,如动物类有:人、猫、狗。
2、多态性指在不考虑对象类型的情况下去使用对象,不同的对象可以调用相同的方法得到不同的结果。有点类似接口类的感觉。
3、多态性的好处:
1)增加程序灵活性
2)增加程序可扩展性
4、静态多态性:比如不管你是列表还是字符串还是数字都可以使用+和*。
5、动态多态性:调用方法
import abc # 导入模块 class Animal(metaclass=abc.ABCMeta): all_type = "animal" @abc.abstractmethod def run(self): pass @abc.abstractmethod def eat(self): pass class People(Animal): def run(self): print("people is walking") def eat(self): print("people is eating") class Pig(Animal): def run(self): print("pig is walking") def eat(self): print("pig is eating") class Dog(Animal): def run(self): print("dog is walking") def eat(self): print("dog is eating") # 多态性:在不考虑实例(对象)类型的情况下,直接使用实例(对象) # 动态多态性(调用方法): peop1 = People() # 造一个对象 dog = Dog() # peop1.eat() # 使用实例 # dog.run() def func(animal): # 设计一个函数接口 animal.run() func(peop1) # 传入对象,调用函数 func(dog)
七、封装
封装就是把类中的属性和方法定义为私有的。
就是在属性名或方法名前加双下划线,而一旦这样定义了属性或方法名后,python会自动将其转换为“_类名__属性名(方法名)”的格式。相当于一种变形操作。
在类的内部调用还是用双下划线加属性名或方法名,在类的外部调用就要用_类名__属性名(方法名)。
class A: __x = 1 # 打印A的属性字典发现,x这个属性已经变形为:_A__x‘ def __init__(self, name): self.__name = name def __foo(self): # 函数在字典中的属性已经变形为: _A__foo. 相当于:def _A__foo(self) print("run foo") def bar(self): self.__foo() # 在类内部调用__foo,看是否能运行 print("from bar") # print(A.__x) # 在类外部打印类的变量__x,发现报错,说没有这个属性 # print(A.__foo()) a = A("egon") # print(a.__name) a.bar() # 调用没有加“__”的函数,结果发现bar里面的__foo运行了,说明在类内部可以调用这个方法 print(A.__dict__) # 打印属性字典,观察里面的属性名称 a._A__foo() # 这样就能访问,使用python在正真意义上并没有完全禁止外部访问这个隐藏属性 """这种变形的特点: 1、在类外部无法直接使用obj.__AttrName进行类的属性访问 2、在类内部可以直接使用obj.__AttrName进行属性方法的访问 3、子类无法覆盖父类"__"开头的属性,因为你会发现它俩根本不是一个名字(看下面的例子) 这种隐藏只是一种变形操作,在类定义阶段就会发生变形""" 输出: run foo from bar {‘__module__‘: ‘__main__‘, ‘_A__x‘: 1, ‘__init__‘: <function A.__init__ at 0x00000000021C88C8>, ‘_A__foo‘: <function A.__foo at 0x00000000021C8950>, ‘bar‘: <function A.bar at 0x00000000021C89D8>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘A‘ objects>, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘A‘ objects>, ‘__doc__‘: None} run foo
这种变形操作只发生在类定义初期,即在类定义完成后是不起作用的。
class B: __x = 1 def __init__(self, name): self.__name = name print(B._B__x) B.__y = 2 # 在类定义完了之后又增加一个“__”开头的属性 print(B.__dict__) # 打印发现字典里面__y的格式是:‘__y‘: 2 , 没有发生变形 b = B("egon") print(b.__dict__) # 打印结果:{‘_B__name‘: ‘egon‘} b.__age = 22 # 给b对象新增一个属性 print(b.__dict__) # 打印结果:{‘_B__name‘: ‘egon‘, ‘__age‘: 22} # 所以变形只在类定义阶段发生,且只发生一次
父类的私有属性和方法,子类无法对其进行修改。
# 正常情况: class A: def foo(self): print("A.foo") def bar(self): print("A.bar") self.foo() # 实际相当于执行:b.foo() class B(A): def foo(self): print("B.foo") b = B() b.bar() 输出: A.bar B.foo
# 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的: class A: def __foo(self): # _A__foo print("A.foo") def bar(self): print("A.bar") self.__foo() # self._A__foo() class B(A): def __foo(self): # _B__foo print("B.foo") b = B() b.bar() 输出: A.bar A.foo
观察以上两个程序的运行结果,就很显然的发现加上“__”前后运行结果发生了变化。因为在类定义初期,类A里面的属性已经发生了变形。
封装的意义:1)封装数据:将数据属性封装起来,对外提供操作该数据的接口,在接口上附上一些操作数据的限制,以此来完成严格的操作数据属性的控制;
2)封装函数:将复杂的实现过程封装起来,隔离复杂度。
八、类的装饰器
1、property属性装饰器:property是一种特殊的属性,访问它时会执行一段功能(函数),然后返回一个值。
加上property属性装饰器后,将类内的方法的调用方式设置成和数据属性一样。
将类中的函数方法定义为property特性以后,对象在调用该方法时就可以和调用数据属性一样,使用“对象名.属性名”的格式,而不用加括号。
但此时的“对象名.属性名”是不能被赋值的,因为它只是长得像数据属性,其实质还是函数。
# 例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解) # 做一个计算BMI的程序,体质指数(BMI)=体重(kg)÷身高^2(m),计算一个对象是出于哪个级别(过轻,正常,...) class People: def __init__(self, weight, high): self.weight = weight self.high = high def bmi(self): return self.weight / (self.high * self.high) p = People(47, 1.58) print(p.bmi()) # 使用property之前 # 使用property class People: def __init__(self, weight, high): self.weight = weight self.high = high @property def bmi(self): # 这个方法必须要有个返回值 return self.weight / (self.high * self.high) p = People(47, 1.58) print(p.bmi) # 使用了property后,对于使用者来说,就像调用数据属性一样调用了函数 # 此时的p.bmi是不能被赋值的,它只是被做的像数据属性,其实本质还是一个函数的
class Circle: def __init__(self, r): self.r = r @property def perimeter(self): return self.r * 2 * 3.14 @property def area(self): return self.r ** 2 * 3.14 c = Circle(3) print(c.perimeter) # 可以向访问数据属性一样去访问perimeter,会触发一个函数的执行,动态计算出一个值 print(c.area) # 同上 # 注意:此时的特性area和perimeter不能像一般的属性那样可以被赋值 # c.area=3 #为特性area赋值 # 执行程序会抛出异常:AttributeError: can‘t set attribute
这个装饰器还有和其配套的setter、deleter:
class People: def __init__(self, name): self.__name = name @property def name(self): # 查询方法 # print("getter") # 测试用 return self.__name @name.setter def name(self, val): # 修改信息方法,使得p.name可以进行赋值操作 # print("setter") # 测试 if not isinstance(val, str): # 合法性判断 print("名字必须是字符串类型") return self.__name = val # 如果输入合法,就将新的值赋给self.__name @name.deleter def name(self): # 删除信息方法 # print("deleter") # 测试 print("不允许删除名字") p = People("egon") # 查询: print(p.name) # 触发查询方法(第一个name方法) # 修改: p.name = "alex" # 触发修改方法(第二个name方法) print(p.name) # 查看修改后的值 # 删除: del p.name # 触发删除方法(第三个name方法) 输出: egon alex 不允许删除名字
class Foo: def __init__(self, val): self.__NAME = val # 将所有的数据属性都隐藏起来,_Foo__NAME @property def name(self): return self.__NAME # obj.name访问的是self.__NAME(这也是真实值的存放位置) f._Foo__NAME @name.setter def name(self, value): if not isinstance(value, str): # 在设定值之前进行类型检查 raise TypeError(‘%s must be str‘ % value) self.__NAME = value # 通过类型检查后,将值value存放到真实的位置self.__NAME @name.deleter def name(self): raise TypeError(‘Can not delete‘) f = Foo(‘egon‘) print(f.name) # f.name = 10 # 抛出异常:TypeError: 10 must be str # del f.name # 抛出异常:TypeError: Can not delete
2、 staticmethod静态方法装饰器:将类内的方法变成普通的函数,即非绑定方法,类和对象都可以调用,不存在自动传值。
3、classmethod类方法装饰器,将方法绑定给类,类调用该方法时,将类名作为第一参数,自动传入。
九、绑定方法与非绑定方法
在类内部定义的函数,分为两大类:
(一)绑定方法:绑定给谁就应该由谁来调用,谁来调用就会自动把调用者当做第一个参数传递给函数
1、绑定到对象的方法:在类内定义的没有被任何装饰器修饰的函数
2、绑定到类的方法:在类内定义的被装饰器classmethod修饰的函数
(二)非绑定方法:没有自动传值这一说,就是类中定义的一个普通函数,类和对象都可以使用
在类内部用staticmethod装饰的函数,不与类或者对象绑定
class Foo: def __init__(self, name): self.name = name def tell(self): # 绑定给对象的方法 print("名字是%s" % self.name) @classmethod # 绑定给类的方法 def func(cls): print(cls) @staticmethod # 非绑定方法 def func1(x, y): return x + y f = Foo("egon") print(Foo.func) # <bound method Foo.func of <class ‘__main__.Foo‘>> 是一个bound method print(f.func1) # <function Foo.func1 at 0x0000000001E58A60> 是一个function print(Foo.func1) # <function Foo.func1 at 0x0000000001E58A60> print(f.func1(2, 3)) print(Foo.func1(2, 3))
十、isinstance和type的区别以及issubclass
isinstance和type都可以用于判断对象和指定类间的关系,但是isinstance的判断没有type准确,它无法正确判断子类的对象和其父类的关系
class A: pass class B(A): pass b=B() print(isinstance(b,B)) print(isinstance(b,A)) print(type(b) is B) print(type(b) is A) 输出: True True True False
issubclass用于判断给定的两个类,前者是否是后者的子类
十一、反射
反射是指通过字符串映射到对象(或类)的属性
hasattr(对象或类名,‘属性或方法名’) 判断指定的对象或类中是否存在指定的属性或方法,有返回True
getattr(对象或类名,‘属性或方法名‘,default=None) 获取对象或类的指定属性值或方法的内存地址,不存在就返回None
setattr(对象或类名,‘新属性名’,新属性值) 给对象或类添加新的属性或方法
delattr(对象或类名,‘新属性名’) 删除之前添加的属性
class People: country = "China" def __init__(self, name, age): self.Name = name self.age = age def talk(self): print("%s is talking" % self.Name) obj = People("egon", 23) # 判断一个属性是不是对象的属性,即该属性名是否存在于对象的属性字典里面 print(hasattr(obj, "Name")) # obj.name 是否存在于 obj.__dict__ print(hasattr(obj, "talk")) # 拿到一个对象的属性值 # print(getattr(obj, "Namexx")) # 如果没有这个属性,程序会报错:AttributeError: ‘People‘ object has no attribute ‘Namexx‘ print(getattr(obj, "Name", None)) # 这样的话,如果没有这个属性,就返回None,而不会报错 print(getattr(obj, "talk", None)) # 修改 setattr(obj, "sex", "male") # obj.sex = "male" 新增一个sex属性 print(obj.sex) # 删除 delattr(obj, "age") # del obj.age print(obj.__dict__) # 同样的方法对类也是可以使用的 print(getattr(People, "country")) # 拿到类中的country属性值
反射当前模块成员:
import sys def s1(): print( ‘s1‘) def s2(): print(‘s2‘) this_module = sys.modules[__name__] print(this_module) print(hasattr(this_module, ‘s1‘)) print(getattr(this_module, ‘s2‘))
输入命令,进行文件的下载与上传:
class Service: def run(self): # self = obj while True: inp = input(">>>").strip() # 输入属性方法:假如是要下载:cmd =get a.txt cmds = inp.split() # cmd = ["get","a.txt"] if hasattr(self, cmds[0]): # 对象中存在该属性 func = getattr(self, cmds[0]) # 获取该属性值,func = get func(cmds) # 调用属性方法 def get(self,cmds1): print("get...", cmds1) def put(self,cmds2): print("put...", cmds2) obj = Service() obj.run()
反射的好处:
1)实现可插拔机制。你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能
2)动态导入模块
十二、类的内置方法
1、__setattr__,__delattr__,__getattr__
class Foo: x=1 def __init__(self,y): self.y=y def __getattr__(self, item): print(‘----> from getattr:你找的属性不存在‘) def __setattr__(self, key, value): print(‘----> from setattr‘) # self.key=value #这就无限递归了,你好好想想 # self.__dict__[key]=value #应该使用它 def __delattr__(self, item): print(‘----> from delattr‘) # del self.item #无限递归了 self.__dict__.pop(item) #__setattr__添加/修改属性会触发它的执行 f1=Foo(10) print(f1.__dict__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值 f1.z=3 print(f1.__dict__) #__delattr__删除属性的时候会触发 f1.__dict__[‘a‘]=3#我们可以直接修改属性字典,来完成添加/修改属性的操作 del f1.a print(f1.__dict__) #__getattr__只有在使用点调用属性且属性不存在的时候才会触发 f1.xxxxxx
2、__getattribute__
class Foo: def __init__(self,x): self.x=x def __getattr__(self, item): print(‘run __getattr__‘) # return self.__dict__[item] def __getattribute__(self, item): print(‘不管是否存在,我都会执行‘) raise AttributeError(‘哈哈‘) f1=Foo(10) f1.x f1.xxxxxx #当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError
3、描述符(__get__,__set__,__delete__)
描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议。
__get__():调用一个属性时触发
__set__():为一个属性赋值时触发
__delete__():采用del删除属性时触发
描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)
class Foo: def __get__(self, instance, owner): print(‘触发get‘) def __set__(self, instance, value): print(‘触发set‘) def __delete__(self, instance): print(‘触发delete‘) #包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法 f1=Foo() f1.name=‘egon‘ f1.name del f1.name #疑问:何时,何地,会触发这三个方法的执行
严格来说,只有将描述符定义成一个类的类属性时,对这个类属性进行增删改查时便会触发执行__get__,__set__,__delete__
描述符分两种:
1)数据描述符:至少实现了__get__()和__set__()
class Foo: def __set__(self, instance, value): print(‘set‘) def __get__(self, instance, owner): print(‘get‘)
2)非数据描述符:没有实现__set__()
class Foo: def __get__(self, instance, owner): print(‘get‘)
注意事项:
描述符本身应该定义成新式类,被代理的类也应该是新式类 ;
必须把描述符定义成这个类的类属性,不能为定义到构造函数中 ;
要严格遵循该优先级,优先级由高到底分别是 1.类属性 2.数据描述符 3.实例属性 4.非数据描述符 5.找不到的属性触发__getattr__()
4、__setitem__,__getitem__,__delitem__
作用:将对象模仿成一个字典进行操作
class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): # 查看属性 self = obj ,item = "name" # print(item) return self.__dict__.get(item, None) # 返回属性值 def __setitem__(self, key, value): # 修改/新增属性 # print(key, value) self.__dict__[key] = value # 给字典增加键和值 # return self.__dict__[key] def __delitem__(self, key): self.__dict__.pop(key) # 或者: del self.__dict__[key] # print(key) obj = Foo("egon") # 一般使用 obj.属性名 的格式 去访问属性 print(obj["name"]) # 像字典一样去访问属性值。 直接触发getitem方法 # 修改/新增属性 # obj.age = 22 # 以前的方法 obj["age"] = 23 # 触发setitem方法, 现在的方法 # 查看设置后的属性 print(obj["age"]) # 方式一 print(obj.age) # 方式二 print(obj.__dict__) # 删除 del obj["name"] # 触发delitem方法 print(obj.__dict__)
5、__str__方法
打印对象时触发该方法,返回一个字符串类型的结果。
d = dict({"name": "egon"}) print(isinstance(d, dict)) print(d) # 输出:{‘name‘: ‘egon‘} class Foo: pass obj = Foo() print(obj) # 输出:<__main__.Foo object at 0x0000000001E67828> # 从上面两个打印结果看出,同为对象,打印出的结果却不一样,因为python给做了定制操作 # 下面自己来定制一个一样效果的操作: class People: def __init__(self, name, age): self.name = name self.age = age def __str__(self): # print(">>>") return self.name # 必须有个字符串格式的返回值 obj = People("egon", 22) print(obj) # 此时可以触发__str__方法:obj.__str__()
6、__del__方法
f = open("settings.py") # 给操作系统发送请求打开资源 f.read() f.close() # 提醒操作系统回收资源 print(f) class Open: def __init__(self, filename): print("open file...") self.filename = filename def __del__(self): # 所以在这个方法里面可以作回收操作系统资源的操作 print("回收操作系统资源") f = Open("settings.py") print("----main-----") # 程序运行到最后一步的时候会触发__del__方法
7、__doc__:输出类的描述信息,该属性无法被继承
8、 __module__ :表示当前操作的对象在那个模块
9、__class__ : 表示当前操作的对象的类是什么
10、__dict__ :查看类或对象中的所有成员,类调用时就打印类的所有属性,不包括实例属性。实例调用就打印所有实例的属性。
11、 __repr__ 格式化输出 %r输出该方法的值,并且%s在没有__str__方法时也是输出该方法的值
12、__new__ 用于创建没有属性的对象,调用object的__new__即可不需要自己实现。可以利用该方法实现单例模式
13、 __call__ :对象加括号执行该方法
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()
14、 __len__ :len()执行该方法
15、 __eq__ : ==运算输出该方法的值
16、__hash__ :hash执行该方法
十三、元类
1、何为元类
元类就是类的类,是类的模板
元类是用来控制如何创建类的,正如类是创建对象的模板一样,而元类的主要目的是为了控制类的创建行为。
type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。
2、exec函数
exec(参数一,参数二,参数三)
参数一:字符串形式的命令
参数二:全局作用域(字典形式),如果不指定,默认为globals()
参数三:局部作用域(字典形式),如果不指定,默认为locals()
g = { "x": 1, "y": 2 } l = {} exec(""" global x, z x = 100 z = 200 # 变量z函数exec执行期间产生的名字,但是它被声明为全局变量了,所以将被存放到全局名称空间中 m = 300 # 变量m函数exec执行期间产生的名字,将被存放到局部名称空间中 """, g, l) print(g) print(l)
3、python中一切皆对象,所以对象:
1)可以被引用 ,x = obj
2)可以当作函数的参数传入
3)可以当作函数的返回值
4)可以当作容器类的元素,l=[func,time, obj]
class Foo: pass obj = Foo() # 一切皆对象,所以类Foo也是对象,即类也是对象 # 而一个对象就是一个类实例化而来,所以有一个类实例化产生了Foo print(type(obj)) # <class ‘__main__.Foo‘> print(type(Foo)) # <class ‘type‘> 可以看出产生Foo的类是type # python中用class关键字定义的类,产生这个类的类称之为元类,这个元类就是type
4、定义类的两种方式
方式一:使用class关键字定义
# 定义类的两种方式:1、class关键字定义 class Chinese: country = "China" def __init__(self, name, age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) print(Chinese) obj = Chinese("egon", 18) # 可以实例化对象 print(obj.__dict__)
方式二:自定义,即用type 这个元类去产生,模拟class创建类的过程
# 造出一个类的要素:类名、类的基类们、类的名称空间 # 第一步:设置类名: class_name = "Chinese" # 第二步:设置类的基类: class_bases = (object,) # 第三步:设置类体,即用于产生类的名称空间的函数体: class_body = """ country = "China" def __init__(self, name, age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) """ class_dict = {} # 名称空间 exec(class_body, globals(), class_dict) # 使用exec函数产生类的名称空间 # print(class_dict) Chinese1 = type(class_name, class_bases, class_dict) # 使用type元类造出一个类 print(Chinese1) obj1 = Chinese1("alex", 19) # 可以实例化对象 print(obj1.__dict__)
5、自定义元类控制类的行为
class Mymeta(type): # 模拟一个元类,在这里面可以作一些类的规则的控制 def __init__(self, class_name, class_bases, class_dict): # print(self) # self = Chinese # print(class_name) # 类名 Chinese # print(class_bases) # 基类 (<class ‘object‘>,) # print(class_dict) # 名称空间 {‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘Chinese‘, ...} if not class_name.istitle(): # 判断类名首字母是否大写 raise TypeError("类名首字母必须大写") # 抛出异常 if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # 注释是否存在,或者是否为空 # 如果__doc__属性不在字典里面或者__doc__为空 raise TypeError("必须有注释,且注释不能为空") # 抛出异常 super(Mymeta, self).__init__(class_name, class_bases, class_dict) # 调用父类方法,继承父类属性 class Chinese(object, metaclass=Mymeta): # 继承元类Mymeta """ 在Chinese类里面 """ country = "China" def __init__(self, name, age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) c = Chinese("egon", 12) print(c.__dict__) # Chinese = Mymeta(class_name, class_bases, class_dict)
6、自定义元类控制类的实例化行为
# __call__方法,在对象调用时会触发 class Foo: def __call__(self, *args, **kwargs): print(self) print(args) print(kwargs) obj = Foo() obj(1, 2, 3, a=4, b=5) # 调用对象:obj.__call__(obj, (1, 2, 3), {a = 4, b = 5}) 输出: <__main__.Foo object at 0x00000000021D74E0> (1, 2, 3) {‘a‘: 4, ‘b‘: 5}
看上面的程序,Foo里面有个__call__方法,当调用对象obj时被触发,
那么,Foo的类是元类,元类内部也应该有一个__call__方法,会在调用Foo时触发执行:
Foo(1,2,x=1) # Foo.__call__(Foo,1,2,x=1)
所以我们可以模拟一个元类,在里面写上__call__方法,让它的子类在调用时触发__call__方法
class Mymeta(type): """模拟一个元类""" def __init__(self, class_name, class_bases, class_dict): print(class_name) # 类名 print(class_bases) # 基类 print(class_dict) # 名称空间 if not class_name.istitle(): # 判断类名首字母是否大写 raise TypeError("类名首字母必须大写") # 抛出异常 if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # 注释是否存在,或者是否为空 # 如果__doc__属性不在字典里面或者__doc__为空 raise TypeError("必须有注释,且注释不能为空") # 抛出异常 super(Mymeta, self).__init__(class_name, class_bases, class_dict) def __call__(self, *args, **kwargs): # __call__(Chinese, "egon", 12) print(self) # 打印参数, self = Chinese print(args) # args = (‘egon‘,) print(kwargs) # kwargs = {‘age‘: 12} # 第一件事:实例化一个类,造出一个空对象obj obj = object.__new__(self) # self = Chinese # 第二件事:初始化对象obj self.__init__(obj, *args, **kwargs) # 第三件事:返回初始化好了的对象obj return obj class Chinese(object, metaclass=Mymeta): # 继承元类Mymeta """在Chinese类里面""" country = "China" def __init__(self, name, age): self.name = name self.age = age def talk(self): print("%s is talking" % self.name) c = Chinese("egon", age = 12) # 实例化其实就是相当于调用类。相当于执行:Chinese.__call__(Chinese, "egon", 12) """调用Chinese会有三件事发生: 1、造出一个空对象 2、初始化对象 3、返回对象 """ print(c.__dict__) 输出: {‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘Chinese‘, ‘__doc__‘: ‘\n 在Chinese类里面\n ‘, ‘country‘: ‘China‘, ‘__init__‘: <function Chinese.__init__ at 0x00000000023D8A60>, ‘talk‘: <function Chinese.talk at 0x00000000023D8AE8>} <class ‘__main__.Chinese‘> (‘egon‘,) {‘age‘: 12} {‘name‘: ‘egon‘, ‘age‘: 12}
7、自定义元类控制类的实例化行为的应用
有如下程序:
class Mysql: def __init__(self): self.host = "127.0.0.1" self.port = 3306 obj1 = Mysql() obj2 = Mysql() print(obj1) print(obj2) 输出: <__main__.Mysql object at 0x0000000002177630> <__main__.Mysql object at 0x0000000002177748>
其实两个对象的内容是一样的,在实例化时根本不必造两个内存空间,这样定义就有点不合理了,这时就引入了单例模式
class Mysql: __instance = None def __init__(self): self.host = "127.0.0.1" self.port = 3306 @classmethod # 绑定给类的方法 def single(cls): # cls = Mysql if not cls.__instance: # 判断cls有没有值 obj = cls() # 没有值就实例化cls产生一个obj对象 cls.__instance = obj # 将obj对象赋值给__instance return cls.__instance obj1 = Mysql.single() # 实例化对象,自动传值:Mysql.single(Mysql) obj2 = Mysql.single() print(obj1) print(obj2) # 打印结果发现,此时两个对象的内存地址都一样了,因为通过这种单例模式使得它们共用了一个内存空间 print(obj1 is obj2) # 判断两个对象内存地址(id)是否相同 输出: <__main__.Mysql object at 0x00000000021E7780> <__main__.Mysql object at 0x00000000021E7780> True
将这种方式用于元类中:
class Mymeta(type): # 模拟一个元类 def __init__(self, class_name, class_bases, class_dict): # self = Mysql if not class_name.istitle(): # 判断类名首字母是否大写 raise TypeError("类名首字母必须大写") # 抛出异常 if "__doc__" not in class_dict or not class_dict["__doc__"].strip(): # 注释是否存在,或者是否为空 # 如果__doc__属性不在字典里面或者__doc__为空 raise TypeError("必须有注释,且注释不能为空") # 抛出异常 super(Mymeta, self).__init__(class_name, class_bases, class_dict) self.__instance = None def __call__(self, *args, **kwargs): # __call__(Chinese, "egon", 12) if not self.__instance: # 判断__instance是否为空 obj = object.__new__(self) # 若果为空就实例化一个空对象 self.__init__(obj) # 初始化空对象 self.__instance = obj # 将初始化后的对象赋值给__instance return self.__instance class Mysql(object, metaclass=Mymeta): """ 使用单例模式控制类的实例化 """ def __init__(self): self.host = "127.0.0.1" self.port = 3306 obj1 = Mysql() obj2 = Mysql() obj3 = Mysql() print(obj1 is obj2 is obj3) 输出: True
8、在元类中控制把自定义类的数据属性都变成大写
class Mymetaclass(type): def __new__(cls, name, bases, attrs): update_attrs = {} for k, v in attrs.items(): if not callable(v) and not k.startswith("__"): # 判断是否为数据属性 update_attrs[k.upper()] = v # 是数据属性就将属性名改为大写 else: update_attrs[k] = v # 否则不修改 return type.__new__(cls, name, bases, update_attrs) # 返回修改后的数据 class Chinese(metaclass=Mymetaclass): country = "China" # 数据属性 tag = "Legend of the Dragon" # 数据属性 def __init__(self, name): self.name = name def walk(self): # 函数属性 print("%s is walking" % self.name) print(Chinese.__dict__) # 打印结果发现,类中所有数据属性都变为大写
9、在元类中控制自定义的类无需init方法
""" 1.元类帮其完成创建对象,以及初始化操作; 2.要求实例化时传参必须为关键参数形式,否则抛出异常TypeError: must use keyword argument 3.key作为用户自定义类产生对象的属性,且所有属性变成大写 """ class Mymetaclass(type): def __call__(self, *args, **kwargs): # print(self) # self = Chinese # print(args) # args = () # print(kwargs) # kwargs = {‘name‘: ‘egon‘, ‘age‘: 18, ‘sex‘: ‘male‘} if args: # 如果args中有内容,说明实例化的时候传入了非关键参数 raise TypeError("must use keyword argument for key function") obj = object.__new__(self) # 创建对象,self= Chinese for k, v in kwargs.items(): obj.__dict__[k.upper()] = v # 将属性名改为大写 return obj class Chinese(metaclass=Mymetaclass): country = "China" tag = "Legend of the Dragon" def walk(self): print(‘%s is walking‘) p = Chinese(name=‘egon‘,age=18,sex=‘male‘) print(p.__dict__) 输出: {‘NAME‘: ‘egon‘, ‘AGE‘: 18, ‘SEX‘: ‘male‘}
十四、面向对象的软件开发流程
1.面向对象分析(object oriented analysis ,OOA)
软件工程中的系统分析阶段,要求分析员和用户结合在一起,对用户的需求做出精确的分析和明确的表述,从大的方面解析软件系统应该做什么,而不是怎么去做。面向对象的分析要按照面向对象的概念和方法,在对任务的分析中,从客观存在的事物和事物之间的关系,归纳出有关的对象(对象的‘特征’和‘技能’)以及对象之间的联系,并将具有相同属性和行为的对象用一个类class来标识。
建立一个能反映这是工作情况的需求模型,此时的模型是粗略的。
2 面向对象设计(object oriented design,OOD)
根据面向对象分析阶段形成的需求模型,对每一部分分别进行具体的设计。
首先是类的设计,类的设计可能包含多个层次(利用继承与派生机制)。然后以这些类为基础提出程序设计的思路和方法,包括对算法的设计。
在设计阶段并不牵涉任何一门具体的计算机语言,而是用一种更通用的描述工具(如伪代码或流程图)来描述
3 面向对象编程(object oriented programming,OOP)
根据面向对象设计的结果,选择一种计算机语言把它写成程序,可以是python
4 面向对象测试(object oriented test,OOT)
在写好程序后交给用户使用前,必须对程序进行严格的测试,测试的目的是发现程序中的错误并修正它。
面向对的测试是用面向对象的方法进行测试,以类作为测试的基本单元。
5 面向对象维护(object oriendted soft maintenance,OOSM)
正如对任何产品都需要进行售后服务和维护一样,软件在使用时也会出现一些问题,或者软件商想改进软件的性能,这就需要修改程序。
由于使用了面向对象的方法开发程序,使用程序的维护比较容易。
因为对象的封装性,修改一个对象对其他的对象影响很小,利用面向对象的方法维护程序,大大提高了软件维护的效率,可扩展性高。
在面向对象方法中,最早发展的肯定是面向对象编程(OOP),那时OOA和OOD都还没有发展起来,因此程序设计者为了写出面向对象的程序,还必须深入到分析和设计领域,尤其是设计领域,那时的OOP实际上包含了现在的OOD和OOP两个阶段,这对程序设计者要求比较高,许多人感到很难掌握。
现在设计一个大的软件,是严格按照面向对象软件工程的5个阶段进行的,这个5个阶段的工作不是由一个人从头到尾完成的,而是由不同的人分别完成,这样OOP阶段的任务就比较简单了。程序编写者只需要根据OOd提出的思路,用面向对象语言编写出程序既可。
十五、领域建模
领域模型,顾名思义,就是需求所涉及的领域的一个建模,更通俗的讲法是业务模型。
领域模型是完成从需求分析到面向对象设计的一座桥梁。
发掘重要的业务领域概念;建立业务领域概念之间的关系。
领域建模三字经:找名词、加属性、连关系。
标签:oca [] ndt 除了 单元 软件维护 哈哈 c99 .class
原文地址:https://www.cnblogs.com/yanlin-10/p/9124116.html