python之面向对象
一.面向对象编程
面向对象:核心是对象二字,对象就是特征与技能的结合体。
优点:可扩展性强。
缺点:编程复杂度高。
应用场景:用户需求经常变化。比如互联网应用,游戏,企业内部应用。
类就是一系列对象相似的特征与技能的结合体。
强调:站在不同的角度,得到的分类是不一样的。站在生物的角度,我们和花都是一类,站在动物或植物的角度就不是了。
先有类还是先有对象
在现实世界中:一定先有对象,后有类。先有一个个的人,后归结为人类。
在程序:一定得先定义类,后调用类来产生对象。
'''
在现实世界当中:
对象1:王二丫
特征:
学校 = 'luyffycity'
名字 = '王二丫'
性别 = '女'
年龄 = 18
技能:
学习
吃饭
睡觉
对象2:李三炮
特征:
学校 = 'luyffycity'
名字 = '李三炮'
性别 = '男'
年龄 = 38
技能:
学习
吃饭
睡觉
对象3:张铁蛋
特征:
学校 = 'luyffycity'
名字 = '张铁蛋'
性别 = '男'
年龄 = 48
技能:
学习
吃饭
睡觉
总结现实中路飞学院的学生类
相似的特征
学校 = 'luyffycity'
相似的技能
学习
吃饭
睡觉
'''
# 先定义类
class LuffyStudent:
school = 'luffycity'
def learn(self):
print('is learning')
def eat(self):
print('is eating')
def sleep(self):
print('is sleeping')
# 后产生对象
stu1 = LuffyStudent() # 实例化
stu2 = LuffyStudent()
stu3 = LuffyStudent()
print(stu1) # <__main__.LuffyStudent object at 0x101fa6208>
print(stu2) # <__main__.LuffyStudent object at 0x101fa60f0>
print(stu3) # <__main__.LuffyStudent object at 0x101fa6400>
二.如何使用类
# 先定义类 class LuffyStudent: school = 'luffycity' # 类的数据属性 def learn(self): # 类的函数属性 print('is learning') def eat(self): # 类的函数属性 print('is eating') def sleep(self): # 类的函数属性 print('is sleeping') print('===run===') # 类在定义阶段,内部代码都会执行 # 查看类的名称空间 # print(LuffyStudent.__dict__) # print(LuffyStudent.__dict__['school']) # luffycity # print(LuffyStudent.__dict__['learn']) # <function LuffyStudent.learn at 0x101f35620> # 查 print(LuffyStudent.school) # 本质就是LuffyStudent.__dict__['school'] print(LuffyStudent.learn) # 本质就是LuffyStudent.__dict__['learn'] # 增 LuffyStudent.country = 'China' # 本质是在__dict__里面增加 print(LuffyStudent.country) # China # 删 del LuffyStudent.country # print(LuffyStudent.country) # 报错,没有country这个属性了 # 改 LuffyStudent.school = 'oldboy' print(LuffyStudent.school) # oldboy
三.__init__方法
__init__方法用来未对象定制对象自己独有的特征
class LuffyStudent: school = 'luffycity' # stu1 '王二丫', '女', 18 def __init__(self, name, sex, age): # 加双下划线的__xxx__会被自动调用。self就是对象,调用__init__方法的时候会先把对象传进去,当做第一个参数。 self.Name = name self.Sex = sex self.Age = age # stu1.Name = '王二丫' # 这就是在给对象定制属性,和给类定制属性一样。stu1也是名称空间,所以stu1也可以通过stu1__dict__来查看属性。 # stu1.Sex = '女' # 名称空间用来存放类的变量名与函数名,LuffyStudent.__dict__查看 # stu1.Age = 18 def learn(self): print('is learning') def eat(self): print('is eating') def sleep(self): print('is sleeping') # 后产生对象 stu1 = LuffyStudent('王二丫', '女', 18) # LuffyStudent.__init__(stu1,'王二丫', '女', 18) ''' 加上__init__方法后,实例化的步骤: 1.先产生一个空对象stu1 2.LuffyStudent.__init__(stu1,'王二丫', '女', 18) stu1就是上面的self ''' print(LuffyStudent.__init__) # <function LuffyStudent.__init__ at 0x101f35620> # 查 print(stu1.__dict__) # {'Name': '王二丫', 'Sex': '女', 'Age': 18} print(stu1.Name) # 王二丫 print(stu1.Sex) # 女 print(stu1.Age) # 18 # 改 stu1.Name = '李二丫' print(stu1.__dict__) # {'Name': '李二丫', 'Sex': '女', 'Age': 18} print(stu1.Name) # 李二丫 # 删除 del stu1.Name print(stu1.__dict__) # {'Sex': '女', 'Age': 18} # 增加 stu1.class_name = 'python开发' print(stu1.__dict__) # {'Sex': '女', 'Age': 18, 'class_name': 'python开发'} stu2 = LuffyStudent('李三炮', '男', 38) print(stu2.__dict__) # {'Name': '李三炮', 'Sex': '男', 'Age': 38} print(stu2.Name) # 李三炮 print(stu2.Sex) # 男 print(stu2.Age) # 38
四.属性查找与绑定方法
skill = 'global' class LuffyStudent: school = 'luffycity' # stu1 '王二丫', '女', 18 def __init__(self, name, sex, age): # 加双下划线的__xxx__会被自动调用。self就是对象,调用__init__方法的时候会先把对象传进去,当做第一个参数。 self.Name = name self.Sex = sex self.Age = age def learn(self, skill): print('%s is learning %s' % (self.Name, skill)) def eat(self): print('%s is eating' % self.Name) def sleep(self): print('%s is sleeping' % self.Name) # 后产生对象 stu1 = LuffyStudent('王二丫', '女', 18) stu2 = LuffyStudent('李三炮', '男', 38) stu3 = LuffyStudent('张铁蛋', '男', 42) print(stu1.__dict__) print(stu2.__dict__) print(stu3.__dict__) # 对象:特征与技能的结合体 # 类:类是一系列对象相似的特征与相似的技能结合体 # # 类中的数据属性:是所有对象共有的 print(LuffyStudent.school, id(LuffyStudent.school)) # luffycity 4327733936 print(stu1.school, id(stu1.school)) # luffycity 4327733936 print(stu2.school, id(stu1.school)) # luffycity 4327733936 print(stu3.school, id(stu1.school)) # luffycity 4327733936 ''' 类中的函数属性: 是绑定给对象使用的,绑定到不同的对象是不同的绑定方法,对象调用绑定方法时,会把对象本身当做第一个参数传入,传给self。 关于绑定到不同的对象是不同的绑定方法,举个生活中简单粗暴的例子:比如王二丫学习东西只能学习到他自己身上,李三炮学都东西也只能学习到他自己身上。 ''' # 下面四个的数据地址都不一样,相当于三个对象手里拿着一个同一个功能的一个指针,每一个用的内存地址都不一样,但绝对用的是同一段代码。 print(LuffyStudent.learn) # <function LuffyStudent.learn at 0x101f9f620> print(stu1.learn) # <bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x101fb3d68>> print(stu2.learn) # <bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x101fb3da0>> print(stu3.learn) # <bound method LuffyStudent.learn of <__main__.LuffyStudent object at 0x101fb3dd8>> LuffyStudent.learn() # 报错 TypeError: learn() missing 1 required positional argument: 'self' def learn(self) 因为没有传参数 LuffyStudent.learn(123) # is learning 改成这样就不能随便传参数了:print('%s is learning' % self.Name) LuffyStudent.learn(stu1) # 王二丫 is learning LuffyStudent.learn(stu2) # 李三炮 is learning LuffyStudent.learn(stu3) # 张铁蛋 is learning print(stu1.learn) stu1.learn() # learn(stu1) 王二丫 is learning print('%s is learning' % self.Name) stu1.learn(stu1) # TypeError: learn() takes 1 positional argument but 2 were given stu1.learn( 'English') # learn(stu1,'English') 王二丫 is learning English def learn(self, skill): print('%s is learning %s' % (self.Name, skill)) stu2.learn('programming') # 李三炮 is learning programming stu3.learn('speaking') # 张铁蛋 is learning speaking # 对象在访问一个属性的时候会先从自己的名称空间里找 ,没有的话去类里面找,类里面还没有就报错,不会去全局找。 LuffyStudent.skill = 'from Luffycity class' print(stu1.skill) # from Luffycity class stu1.skill = 'from stu1' print(stu1.__dict__) # {'Name': '王二丫', 'Sex': '女', 'Age': 18, 'skill': 'from stu1'} print(stu1.skill) # from stu1 # 如果类和对象里都没有,就会报错。 print(stu1.skill) # 报错,不会去全局找 AttributeError: 'LuffyStudent' object has no attribute 'skill'
补充说明 站的角度不同,定义出的类是截然不同的; 现实中的类并不完全等于程序中的类,比如现实中的公司类,在程序中有时需要拆分成部门类,业务类等; 有时为了编程需求,程序中也可能会定义现实中不存在的类,比如策略类,现实中并不存在,但是在程序中却是一个很常见的类。
五.一切皆对象
python一切皆对象,在python3里统一了类与类型的概念
class LuffyStudent: school = 'luffycity' # stu1 '王二丫', '女', 18 def __init__(self, name, sex, age): # 加双下划线的__xxx__会被自动调用。self就是对象,调用__init__方法的时候会先把对象传进去,当做第一个参数。 self.Name = name self.Sex = sex self.Age = age def learn(self, skill): print('%s is learning %s' % (self.Name, skill)) def eat(self): print('%s is eating' % self.Name) def sleep(self): print('%s is sleeping' % self.Name) print(type([1, 2])) # <class 'list'> print(type({'key': 'value'})) # <class 'dict'> print(type('string')) # <class 'str'> print(type(123)) # <class 'int'> print(type((1, 2))) # <class 'tuple'> print(type({1, 2})) # <class 'set'> print(type(True)) # <class 'bool'> print(LuffyStudent) # <class '__main__.LuffyStudent'> # python中所有的数据类型经过实例化后得到不同的数据对象,定义列表是列表对象,定义数字是数字对象。 # l = [1, 2, 3] # l = list([1,2,3]) l1 = list([1, 2, 3]) l2 = [] l1.append(4) # 原理就是绑定方法。list.append(l1,4),list是类 l2.append(1) # list.append(l2,1),list是类 print(l1) # [1, 2, 3, 4] list.append(l1, 5) # 这些功能其实都是给对象用的,所以我们都用l1.append(x)而不是list.append(l1, x) print(l1) # [1, 2, 3, 4, 5]
六.面向对象小结
从代码级别看面向对象
1、在没有学习类这个概念时,数据与功能是分离的
def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx #每次调用都需要重复传入一堆参数 exc1('127.0.0.1',3306,'db1','utf8','select * from tb1;') exc2('127.0.0.1',3306,'db1','utf8','存储过程的名字')
2、我们能想到的解决方法是,把这些变量都定义成全局变量
HOST=‘127.0.0.1’ PORT=3306 DB=‘db1’ CHARSET=‘utf8’ def exc1(host,port,db,charset): conn=connect(host,port,db,charset) conn.execute(sql) return xxx def exc2(host,port,db,charset,proc_name) conn=connect(host,port,db,charset) conn.call_proc(sql) return xxx exc1(HOST,PORT,DB,CHARSET,'select * from tb1;') exc2(HOST,PORT,DB,CHARSET,'存储过程的名字')
3、但是2的解决方法也是有问题的,按照2的思路,我们将会定义一大堆全局变量,这些全局变量并没有做任何区分,即能够被所有功能使用,然而事实上只有HOST,PORT,DB,CHARSET是给exc1和exc2这两个功能用的。言外之意:我们必须找出一种能够将数据与操作数据的方法组合到一起的解决方法,这就是我们说的类了
class MySQLHandler: def __init__(self,host,port,db,charset='utf8'): self.host=host self.port=port self.db=db self.charset=charset self.conn=connect(self.host,self.port,self.db,self.charset) def exc1(self,sql): return self.conn.execute(sql) def exc2(self,sql): return self.conn.call_proc(sql) obj=MySQLHandler('127.0.0.1',3306,'db1') obj.exc1('select * from tb1;') obj.exc2('存储过程的名字')
总结使用类可以:将数据与专门操作该数据的功能整合到一起。
如果我们新增一个类属性,将会立刻反映给所有对象,而对象却无需修改
七.继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
class ParentClass1: pass class ParentClass2: pass class SubClass1(ParentClass1): pass class SubClass2(ParentClass1, ParentClass2): pass print(SubClass1.__bases__) # (<class '__main__.ParentClass1'>,) print(SubClass2.__bases__) # (<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
属性查找顺序
class Foo: def f1(self): print('from Foo.f1') def f2(self): print('from Foo.f2') self.f1() # b.f1() class Bar(Foo): def f1(self): print('from Bar.f2') b = Bar() # print(b.__dict__) # {} b.f2() ''' from Foo.f2 from Bar.f2 因为会优先从自己类里找 ''' # 寻找顺序:对象自己这 -> 自己类里 -> 父类 -> 父父类......直到找不到
八.派生
子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类)。
需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
class Hero: def __init__(self, name, health, atk): self.name = name self.health = health self.atk = atk def attck(self, enemy): enemy.health -= self.atk class LiBai(Hero): camp = '隋唐' def attck(self, enemy): print('from LiBai Class') class BaiQi(Hero): camp = '秦汉' pass libai1 = LiBai('李白', 100, 30) baiqi1 = BaiQi('白起', 80, 50) libai1.attck(baiqi1) # from LiBai Class
九.继承的实现原理
经典类:
python3中统一都是新式类,pyhon2中才分新式类与经典类。
在python2中 --> 经典类:没有继承object的类,以及它的子类都称之为经典类
class Foo: pass class Bar(Foo): pass
在python2中 --> 新式类:继承了object的类,以及它的子类都称之为新式类
class Foo(object): pass class Bar(Foo): pass
在python3中 --> 默认都是新式类,没有object的类默认继承object
class Foo(): pass print(Foo.__bases__) # (<class 'object'>,)
类的继承顺序
经典类:先从第一个爹一路走到黑找到祖宗,在回来从第二个、第三......个爹开始找。
新式类:从第一个爹开始,找到还差一个就到祖宗的类后返回,直到遇到最后一个爹,再去找祖宗。
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
十.在子类中重用父类的属性
在子类派生出的新的方法中重用父类的方法,有两种实现方式:
方式一:指名道姓 不依赖继承 enemy.health -= self.atk Hero.__init__(self, name, health, atk)
方式二:super() 依赖继承 原理是在MRO列表中寻找
在python3中super(X, self)和super()效果一样
方式一
class Hero: def __init__(self, name, health, atk): self.name = name self.health = health self.atk = atk def attck(self, enemy): enemy.health -= self.atk class LiBai(Hero): camp = '隋唐' def __init__(self, name, health, atk, weapon): Hero.__init__(self, name, health, atk) # 不依赖于继承 self.weapon = weapon def attck(self, enemy): Hero.attck(self, enemy) print('from LiBai Class') class BaiQi(Hero): camp = '秦汉' pass libai1 = LiBai('李白', 100, 30, '金箍棒') print(libai1.__dict__) # {'name': '李白', 'health': 100, 'atk': 30, 'weapon': '金箍棒'}
方式二
class Hero: def __init__(self, name, health, atk): self.name = name self.health = health self.atk = atk def attck(self, enemy): enemy.health -= self.atk class LiBai(Hero): camp = '隋唐' def __init__(self, name, health, atk, weapon): Hero.__init__(self, name, health, atk) def attck(self, enemy): # super(LiBai, self) # 得到一个特殊对象,这个对象可以专门引用父类的属性,就相当于实例本身。 super(LiBai, self).attck(enemy) # 依赖于继承 相当于libai1.attck(baiqi1) print('from LiBai Class') class BaiQi(Hero): camp = '秦汉' pass libai1 = LiBai('李白', 100, 30, '金箍棒') baiqi1 = BaiQi('白起', 80, 50) print(baiqi1.health) # 80 libai1.attck(baiqi1) # from LiBai Class print(baiqi1.health) # 50
class Hero: def __init__(self, name, health, atk): self.name = name self.health = health self.atk = atk def attck(self, enemy): enemy.health -= self.atk class LiBai(Hero): camp = '隋唐' def __init__(self, name, health, atk, weapon): # super(LiBai, self).__init__(name, health, atk) # self.weapon = weapon super().__init__(name, health, atk) self.weapon = weapon def attck(self, enemy): Hero.attck(self, enemy) print('from LiBai Class') class BaiQi(Hero): camp = '秦汉' pass libai1 = LiBai('李白', 100, 30, '金箍棒') print(libai1.__dict__) # {'name': '李白', 'health': 100, 'atk': 30, 'weapon': '金箍棒'}
为什么说super()依赖继承
class A: def f1(self): print('from A') super().f1() class B: def f1(self): print('from B') class C(A, B): pass print(C.mro()) # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>] c = C() c.f1() ''' 打印: from A from B 解析: 先从C找,C里面没有f1函数,那就继续往下找到A,找到A后打印出"from A",然后运行super().f1(),重点来了!super是按照mro来找的,那按照mro,我现在的位置是在A, 那么接下来就该去B找了,所以就会打印出"from B",不是说非得B是A的父类才行,是按照mro来找的,这就是为什么说super()依赖继承。 '''
十一.组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
import collections # 组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合 class People: school = 'luffycity' def __init__(self, name, age, gender): self.name = name self.age = age self.gender = gender class Teacher(People): def __init__(self, name, age, gender, level, salary): super().__init__(name, age, gender) self.level = level self.salary = salary def teach(self): print('%s is teaching' % self.name) class Student(People): def __init__(self, name, age, gender, class_time): super().__init__(name, age, gender) 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 tell_info(self): print('课程名<%s> 课程价钱<%s> 课程周期<%s>' % (self.course_name, self.course_price, self.course_period)) class Date: def __init__(self, year, mon, day): self.year = year self.mon = mon self.day = day def tell_info(self): print('%s-%s-%s' % (self.year, self.mon, self.day)) teacher1 = Teacher('alex', 18, 'male', 10, 3000) teacher2 = Teacher('egon', 18, 'male', 10, 3000) python = Course('python', 3000, '3mons') linux = Course('linux', 200, '4mons') teacher1.course = python teacher2.course = python print(python) # <__main__.Course object at 0x1031bc1d0> print(teacher1.course) # <__main__.Course object at 0x101fbc1d0> print(teacher2.course) # <__main__.Course object at 0x101fbc1d0> print(teacher1.course.course_name) # python print(teacher1.course.course_price) # 3000 print(teacher1.course.course_period) # 3mons print(teacher2.course.course_name) # python teacher1.course.tell_info() # 给老师这个对象定制了课程这个属性,然后让这个属性指向了另外一个对象,把老师类和课程类组合在一起,这就叫组合。 student1 = Student('张三', 28, 'female', '08:30') student1.course1 = python student1.course2 = linux student1.courses = [] student1.courses.append(python) student1.courses.append(linux) d = Date(1988, 4, 20) student1 = Student('张三', 28, 'female', '08:30') python = Course('python', 3000, '3mons') student1.birth = d student1.birth.tell_info() student1.course = python
十二.抽象类与归一化
抽象类就是强制子类用一套命名规范,降低使用者的使用复杂度。
import abc # 用这个模块做抽象类 class Animal(metaclass=abc.ABCMeta): # 只能被继承,不能被实例化 all_type = 'animal' @abc.abstractmethod def run(self): pass @abc.abstractmethod def eat(self): pass # 没有run或eat就会报错 TypeError: Can't instantiate abstract class People with abstract methods eat, run class People(Animal): def run(self): print('people is walking') def eat(self): print('people is eating') class Pig(Animal): all_type = 'BigPig' 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') people1 = People() pig1 = Pig() dog1 = Dog() people1.eat() pig1.eat() dog1.eat() # # # 抽象类也是个类,查询顺序也按照类的来 print(people1.all_type) # animal print(pig1.all_type) # BigPig # 抽象类不能被实例化 animal = Animal() # TypeError: Can't instantiate abstract class Animal with abstract methods eat, run
十三.多态与多态性
多态:同一类事物的多种形态
文件有多种形态:文本文件,可执行文件
动物有多种形态:人,狗,猪
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') class Cat(Animal): def talk(self): print('say miaomiao ')
多态性:指的是可以在不考虑对象和类型的情况下而直接使用对象,态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
people1 = People() dog1 = Dog() pig1 = Pig() cat1 = Cat() people1.talk() dog1.talk() pig1.talk() def func(animal): animal.talk()
十四.鸭子类型
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
例1:二者都像鸭子,二者看起来都像文件,因而就可以当文件一样去用
class Disk: def read(self): print('disk read') def write(self): print('disk write') class Text: def read(self): print('text read') def write(self): print('text write') # f = open(...) # f.read() # f.write() disk = Disk() text = Text() disk.read() disk.write() text.read() text.write()
例2:序列类型:列表list,元组tuple,字符串str
l = list([1, 2, 3]) t = tuple(('a', 'b')) s = str('hello') # python把这三个独立的类,做的都像序列,都有__len__方法。我们可以在不考虑三者类型的前提下使用s,l,t print(l.__len__()) # 3 print(t.__len__()) # 2 print(s.__len__()) # 5 def len(obj): # python内置了这个方法 return obj.__len__() # 列表、元组、字符串都可以是用len(),是因为python用了多态性,鸭子类型, print(len(l)) # 3 print(len(t)) # 2 print(len(s)) # 5
批评:关于鸭子类型常常被引用的一个批评是它要求程序员在任何时候都必须很好地理解他/她正在编写的代码。在一个强静态类型的、使用了类型继承树和参数类型检查的语言中,给一个类提供未预测的对象类型更为困难。例如,在Python中,你可以创建一个称为Wine的类,并在其中需要实现press方法。然而,一个称为Trousers的类可能也实现press()方法。为了避免奇怪的、难以检测的错误,开发者在使用鸭子类型时需要意识到每一个“press”方法的可能使用,即使在语义上和他/她所正在编写工作的代码没有任何关系。
十五.封装之如何实现属性的隐藏
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
class A: __x = 1 def __init__(self, name): self.__name = name # 在类定义阶段就被翻译成:self._A__name = name def __foo(self): # def _A__foo(self): print('run foo') def bar(self): self.__foo() # self._A__foo() print('from bar') print(A.__dict__) # '_A__x': 1 print(A._A__x) # 1 print(A.__x) # AttributeError: type object 'A' has no attribute '__x' print(A.__foo) # AttributeError: type object 'A' has no attribute '__foo' a = A('edward') print(a.__x) # AttributeError: 'A' object has no attribute '__x' print(a.__dict__) # {'_A__name': 'edward'} print(a._A__name) # edward a.bar() # from bar
这种变形的特点:
-
外部无法直接obj.__AttrName
-
在类内部是可以直接使用:obj.__AttrName。因为在类定义阶段就已经给改成了正确的调用格式
-
子类无法覆盖父类__开头的属性,因为他们不是一个名字。
隐藏函数:
class Foo: def __func(self): # _Foo__func print('from foo') class Bar(Foo): def __func(self): # _Bar__func print('from bar') b = Bar() # b.func() # 'from bar' 两个函数名都是def func(self): b.func() # AttributeError: 'Bar' object has no attribute 'func' b._Bar__func() # from bar
总结这种变形需要注意的问题:
-
这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N。如果你想访问这个属性,就不要这么定义。
-
变形的过程只在类的定义时发生一次,在定义后的赋值操作,不会变形。
-
在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的。
验证问题一
class B: __x = 1 def __init__(self, name): self.__name = name print(B._B__x)
验证问题二
class B: __x = 1 def __init__(self, name): self.__name = name B.__y = 2 # 在定义后的赋值操作,不会变形 print(B.__dict__) # None, '__y': 2 b = B('edward') print(b.__dict__) # {'_B__name': 'edward'} b.__age = 18 # 在定义后的赋值操作,不会变形 print(b.__dict__) # {'_B__name': 'edward', '__age': 18} print(b.__age) # 18
验证问题三
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
十六.封装的意义
一、封装数据属性:明确的区分内外,控制外部对隐藏的属性的操作
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 p = People('egon', 18) p.tell_info() # Name:<egon> Age<18> p.set_info('EGON', 38) p.tell_info() # Name:<EGON> Age<38> p.set_info(123, 38) # 名字必须是字符串类型 p.tell_info() # Name:<EGON> Age<38> p.set_info('egon', '38') # 年龄必须是数字类型 p.tell_info() # Name:<EGON> Age<38>
二、封装方法:隔离复杂度
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, width, length, height): self.name = name self.owner = owner self.__weight = width self.__lengthh = length self.__height = height def tell_area(self): return self.__weight * self.__lengthh * self.__height r = Room('卫生间', 'alex', 10, 10, 10) print(r.tell_area()) # tell_area先是求面积,后变成求体积,对于使用者来说还是用tell_area,根本不用改变使用方式
十八.property的使用
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
例一:BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
class People: def __init__(self, name, weight, height): self.name = name self.weight = weight self.height = height @property def bmi(self): return self.weight / (self.height ** 2) p = People('egon', 75, 1.81) #版本一 p.bmi = p.weight / (p.height ** 2) # 没有定义def bmi(self)方法 print(p.bmi) # 22.89307408198773 #版本二 print(p.bmi()) # 没有加property属性 22.89307408198773 #版本三 property 把数据属性、应该是名词的东西的访问方式统一起来 print(p.bmi) # 22.89307408198773 p.height = 1.66 print(p.bmi) # 27.21730294672667 p.bmi = 333 # 报错,它本质还是个方法,只是做的像数据属性
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
面向对象的封装有三种方式:
【public】 这种其实就是不封装,是对外公开的
【
protected】 这种封装方式对外不公开,但对朋友(friend)或者子类公开
【private】 这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
#如何修改被property了的函数 class People: def __init__(self, name): self.__name = name @property def name(self): # print('getter') return self.__name @name.setter def name(self, val): # print('setter', val) if not isinstance(val, str): print('名字必须是字符串类型') return self.__name = val @name.deleter def name(self): print('deleter') print('不允许删除') p = People('edward') print(p.name) # edward # p.name # setter p.name = 'EDWARD' # setter EDWARD p.name = 123 # 名字必须是字符串类型 print(p.name) # EDWARD del p.name # deleter 不允许删除
十九.绑定方法与非绑定方法
在类内部定义的函数,分为两大类:
-
绑定方法:绑定给谁,就应该由谁来调用,谁来调用就会把调用者当做第一个参数自动传入
绑定到对象的方法:在类内定义的没有被任何装饰器修饰的绑定到类的方法: 在类内定义的被任何装饰器@classmethod修饰的方法
-
非绑定方法:没有自动传值这么一说,就是类定义的一个普通工具,对象和类都可以使用
不与类或者对象绑定
class Foo: def __init__(self, name): # 绑定到对象的方法,给对象用 self.name = name def tell(self): print('名字是%s' % self.name) @classmethod # 绑定到类,给类用 def func(cls): # cls = Foo print(cls) @staticmethod # 非绑定方法,谁都可以用 def func2(x, y): print(x + y) f = Foo('edward') # 绑定到对象 print(Foo.tell) # <function Foo.tell at 0x10309f620> print(f.tell) # <bound method Foo.tell of <__main__.Foo object at 0x101f37470>> f.tell() # 绑定到类 Foo.func() # <class '__main__.Foo'> print(Foo.func) # <bound method Foo.func of <class '__main__.Foo'>> # 非绑定方法 print(Foo.func2) # <function Foo.func2 at 0x10399f730> print(f.func2) # <function Foo.func2 at 0x10399f730> Foo.func2(1, 2) # 3 f.func2(1, 2) # 3
二十.绑定方法的应用
import settings # name = 'Ann' age = 27 gender = 'female' import hashlib import time class People: def __init__(self, name, age, sex): self.id = self.create_id() self.name = name self.age = age self.sex = sex def tell_info(self): # 绑定到对象的方法 函数体的逻辑需要一个对象,你不能新建个对象,函数里就变个对象的名字。 print('Name:%s Age:%s Sex:%s' % (self.name, self.age, self.sex)) @classmethod def from_conf(cls): obj = cls( settings.name, settings.age, settings.gender ) return obj @staticmethod def create_id(): m = hashlib.md5(str(time.time()).encode('utf-8')) return m.hexdigest() p = People('edward', 18, 'male') # 绑定给对象,就应该由对象调用,自动将对象本身当做第一个参数传入 p.tell_info() # Name:edward Age:18 Sex:male # 绑定给类,就应该由类来调用,自动将类本身当做第一个参数传入 p = People.from_conf() p.tell_info() # Name:Ann Age:27 Sex:female # 非绑定方法,不与类和对象绑定,谁都可以调用,没有自动传值一说 p1 = People('edward', 18, ' male') p2 = People('edward2', 28, 'male') p3 = People('edward3', 38, 'male') print(p1.id) # e502a37e4782b0b68ae45eb86649b708 print(p2.id) # e92f423d82dc1721bdbce781c6ec8d18 print(p3.id) # adef0c38cf94d53d3207261f457d077c
二十一.反射
1 什么是反射
反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。
2 python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
# 反射:通过字符串映射到对象的属性 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('edward', 18) print(hasattr(obj, 'name')) # True 判断object中有没有一个name字符串对应的方法或属性 obj.name print(getattr(obj, 'name', None)) # edward ''' Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y. When a default argument is given, it is returned when the attribute doesn't exist; without it, an exception is raised in that case. ''' print(getattr(obj, 'talk', None)) # <bound method People.talk of <__main__.People object at 0x101f426a0>> setattr(obj, 'sex', 'male') # obj.sex = 'mala' print(obj.sex) # male delattr(obj, 'age') # del obj.age print(obj.__dict__) # {'name': 'edward'} print(getattr(People, 'country')) # China
反射的应用
class Service: def run(self): while True: inp = input('>>>').strip() # cmd = 'get a.txt cmds = inp.split() # cmds = ['get','a.txt'] if hasattr(self, cmds[0]): func = getattr(self, cmds[0]) func(cmds) def get(self, cmds): print('get......%s' % cmds) def put(self, cmds): print('put......%s' % cmds) obj = Service() obj.run()
二十二.内置方法
items
class Foo: # Dict def __init__(self, name): self.name = name def __getitem__(self, item): # item = 'name' # print(item) # self.x return self.__dict__[item] def __setitem__(self, key, value): # print(key, value) # gender male self.__dict__[key] = value def __delitem__(self, key): # print(key) name self.__dict__.pop(key) obj = Foo('edward') # 查看属性 __getitem__ print(obj['name']) # 设置属性 __setitem__ obj['gender'] = 'male' print(obj.__dict__) # {'name': 'edward', 'gender': 'male'} # 删除属性 del obj['name'] print(obj.__dict__) # {'gender': 'male'}
__str__ str定义完后,会在打印对象的时候,触发对象下的str方法,把以字符串形式返回的结果打印出来。
d = dict(name='edward') # d 是 dict的实例 print(isinstance(d, dict)) # True print(d) class People: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return '<name:%s,age:%s>' % (self.name, self.age) obj = People('edward', 18) print(obj) # obj.__str__() <name:edward,age:18>
__del__
引子:
f = open('a.txt') # 做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件 del f # 只回收用户空间的f,操作系统的文件还处于打开状态 # 所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是 f = open('a.txt') # 读写... f.close() # 很多情况下大家都容易忽略f.close,这就用到了with上下文管理
class Open: def __init__(self, filename): print('open file......') self.filename = filename def __del__(self): # 回收跟对象相关联的其他操作,在对象被删除的时候会自动先触发这个方法 print('回收操作系统资源:self.close()') f = Open('settings.py') # del f # 等于手动执行f.__del__() # 回收操作系统资源:self.close() print('-----main-----') # 如果没有del f这行代码,到这里python代码执行完毕,自动执行__del__。
二十三.元类介绍
储备知识exec
参数1:字符串形式的命令
参数2:全局作用域(字典形式),如果不指定默认就使用globals()
参数3:局部作用域(字典形式),如果不指定默认就使用locals()
g = { 'x': 1, 'y': 2, } l = {} exec(''' global x,m x = 10 m = 100 z = 3 ''', g, l) print(g) # {'x': 10, 'y': 2, '__builtins__': print(l) # {'z': 3}
一切皆对象,对象可以怎么用?
- 都可以被引用,x = obj
- 都可以当做函数的参数传入
- 都可以当做函数的返回值
- 都可以当做容器(列表、元组、字典、集合)类型的元素 l = [func,time,obj,Foo(),1,'name',True]
符合这四点的都是对象
类也是对象,是由type实例化得来的。Foo = type(......)
class Foo: pass obj = Foo() print(type(obj)) # <class '__main__.Foo'> print(type(Foo)) # <class 'type'> class Bar: pass print(type(Bar)) # <class 'type'>
产生类的类称之为元类,默认所有用class定义的类,他们的元类是type
定义类的两种方式 :
方式一: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) # <class '__main__.Chinese'> obj = Chinese('edward', 18) print(obj, obj.name, obj.age) # <__main__.Chinese object at 0x102ba6390> edward 18
方式二:type
定义类的三要素:类名、类的基类们、类的名称空间
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_dic = {} exec(class_body, globals(), class_dic) # print(class_dic) # {'country': 'China', '__init__': <function __init__ at 0x101dbdea0>, 'talk': <function talk at 0x103335620>} Chinese2 = type(class_name, class_bases, class_dic) print(Chinese2) # <class '__main__.Chinese'> obj2 = Chinese2('edward', 18) print(obj2, obj2.name, obj.age) # <__main__.Chinese object at 0x1030b3cc0> edward 18
二十四.自定义元类来控制类的行为
import re class Mymeta(type): # 只是自己重写一小部分,大部分还是要用type,那就意味着自定义元类要继承type def __init__(self, class_name, class_bases, class_dic): print('--->', self) # <class '__main__.Chinese'> # print(class_name) # Chinese # print(class_bases) # (<class 'object'>,) # print(class_dic) # {......} partten = re.compile('^[A-Z].*?.*') if not partten.search(class_name): raise TypeError('类名的首字母必须大写') if '__doc__' not in class_dic or not class_dic['__doc__'].strip(): raise TypeError('必须有注释,且注释不能为空') super(Mymeta, self).__init__(class_name, class_bases, class_dic) class Chinese(object, metaclass=Mymeta): ''' 中国人的类 ''' country = 'China' def __init__(self, name, age): self.name = name self.age = age def talk(self): print('%s is talking' % self.name)
二十五.自定义元类控制类的实例化
储备知识: __call__方法
class Foo: def __call__(self, *args, **kwargs): print(self) # <__main__.Foo object at 0x103039400> print(args) # (1, 2, 3) print(kwargs) # {'a': 1, 'b': 2, 'c': 3} obj = Foo() obj(1, 2, 3, a=1, b=2, c=3) # obj.__call__(obj,1, 2, 3, a=1, b=2, c=3)
元类内部也应该有一个__call__方法,会在调用Foo时触发执行
Foo(1, 2, x=1) --> Foo.__call__(Foo, 1, 2, x=1)
import re class Mymeta(type): def __init__(self, class_name, class_bases, class_dic): partten = re.compile('^[A-Z].*?.*') if not partten.search(class_name): raise TypeError('类名的首字母必须大写') if '__doc__' not in class_dic or not class_dic['__doc__'].strip(): raise TypeError('必须有注释,且注释不能为空') super(Mymeta, self).__init__(class_name, class_bases, class_dic) def __call__(self, *args, **kwargs): # obj = Chinese('edward',age=18) # print(self) # <class '__main__.Chinese'> self = Chinese # print(args) # ('edward',) # print(kwargs) # age=18 # 第一件事:先造出一个空对象 obj = object.__new__(self) # 第二件事:初始化obj self.__init__(obj, *args, **kwargs) # 第三件事:返回obj return obj class Chinese(object, metaclass=Mymeta): ''' 中国人的类 ''' country = 'China' def __init__(self, name, age): self.name = name self.age = age def talk(self): print('%s is talking' % self.name) people1 = Chinese('edward', age=18) # Chinese.__call__(Chinese,'edward',18) # 先造出一个空对象obj # 初始化obj # 返回obj print(people1.__dict__) # {'name': 'edward', 'age': 18}
二十六.自定义元类控制类的实例化行为的应用
单例模式
实现方式一
class MySQL: __instance = None # obj1 @classmethod def singleton(cls): # 单例 if not cls.__instance: obj = cls() cls.__instance = obj return cls.__instance def __init__(self): self.host = '127.0.0.1' self.port = 3306 def conn(self): pass def execute(self): pass obj1 = MySQL() obj2 = MySQL() obj3 = MySQL() print(obj1) # 不同的地址 print(obj2) print(obj3) print(obj1 is obj2) # False obj1 = MySQL.singleton() obj2 = MySQL.singleton() obj3 = MySQL.singleton() print(obj1) # 一样的地址 print(obj2) print(obj3) print(obj1 is obj2) # True
实现方式二:元类的方式
import re class Mymeta(type): def __init__(self, class_name, class_bases, class_dic): partten = re.compile('^[A-Z].*?.*') if not partten.search(class_name): raise TypeError('类名的首字母必须大写') if '__doc__' not in class_dic or not class_dic['__doc__'].strip(): raise TypeError('必须有注释,且注释不能为空') super(Mymeta, self).__init__(class_name, class_bases, class_dic) # 父类可能还有一些东西需要用到 self.__instance = None def __call__(self, *args, **kwargs): if not self.__instance: obj = object.__new__(self) self.__init__(obj) self.__instance = obj return self.__instance class Mysql(object, metaclass=Mymeta): ''' mysql xxx ''' def __init__(self): self.host = '127.0.0.1' self.port = 3306 def conn(self): pass def execute(self): pass obj1 = Mysql() obj2 = Mysql() obj3 = Mysql() print(obj1 is obj2 is obj3) # True
二十七.异常处理
什么是异常:异常是错误发生的信号,一旦程序出错,并且程序没有处理这个错误,那么程序就会抛出异常,并且程序的运行随之终止
错误分为两种
语法错误:在程序执行前就要立刻改正过来,如:
print('xxx' if 1 > 2
逻辑错误
ValueError int('aaa') NameError name IndexError l = [1,2,3] l[1000] KeyError d = {} d['name'] AttributeError class Foo: pass Foo.xxx ZeroDivisionError 1 / 0 TypeError:int类型不可迭代 for i in 3: pass
异常处理
强调一:对于错误发生的条件如果是可以预知的,此时应该用if判断去预防异常
AGE = 10 age = input('>>>').strip() if age.isdigit(): age = int(age) if age < AGE: print('年龄太大了') else: print('输入数字')
强调二:对于错误发生的条件如果是不可预知的,此时应该用异常处理机制,try......except
try: with open('test.txt', 'r', encoding='utf-8') as f: print(next(f), end='') print(next(f), end='') print(next(f), end='') print(next(f), end='') print(next(f), end='') print(next(f), end='') print(next(f), end='') print(next(f), end='') except StopIteration: print('出错了') print('=====>1') print('=====>2') print('=====>3')
二十八.try......except的详细用法
多分支:被监测的代码块抛出的异常有多种可能性,并且我们需要针对每一种异常类型都定制专门的处理逻辑
try: print('===>1') # name print('===>2') l = [1, 2, 3] # l[100] print('===>3') d = {} d['name'] print('===>4') except NameError as e: print('--->', e) except IndexError as e: print('--->', e) except KeyError as e: print('--->', e) print('====> after code')
万能异常:Exception 被监测的代码块抛出的异常有多种可能性,并且我们针对所有的异常类型都只用一种处理逻辑
try: print('===>1') name print('===>2') l = [1, 2, 3] l[100] print('===>3') d = {} d['name'] print('===>4') except Exception as e: print('异常发生啦:', e) print('====> after code')
两种结合
try: print('===>1') # name print('===>2') l = [1, 2, 3] # l[100] print('===>3') d = {} d['name'] print('===>4') except NameError as e: print('--->', e) except IndexError as e: print('--->', e) except KeyError as e: print('--->', e) except Exception as e: print('统一的处理方法', e) print('====> after code')
其他结构
try: print('===>1') # name print('===>2') l = [1, 2, 3] # l[100] print('===>3') d = {} d['name'] print('===>4') except NameError as e: print('--->', e) except IndexError as e: print('--->', e) except KeyError as e: print('--->', e) except Exception as e: print('统一的处理方法', e) else: print('在被监测的代码块没有发生异常时执行') finally: # 常用,比如文件处理的时候,出不出错,都要把资源回收掉,那就需要在finally下面执行f.close() print('不管被监测的代码块有没有发生异常都会执行') print('====> after code')
主动出发异常:raise 异常类型(值)
class People: def __init__(self, name, age): if not isinstance(name, str): raise TypeError('名字必须传入str类型') if not isinstance(age, int): raise TypeError('年龄必须传入int类型') self.name = name self.age = age # p = People(333, 18) p = People('edward', '18')
自定义异常
class MyException(BaseException): def __init__(self, msg): super(MyException, self).__init__() self.msg = msg def __str__(self): return '<%s>' % self.msg raise MyException('我自己的异常') # print(obj)
断言assert
info = {} info['name'] = 'edward' # info['age'] = 18 # if 'name' not in info: # raise KeyError('必须有name这个key') # # if 'age' not in info: # raise KeyError('必须有age这个key') assert ('name' in info) and ('age' in info) if info['name'] == 'edward' and info['age'] > 10: print('welcome')