Python基础-初始面向对象
Posted 鸿飞漫天
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python基础-初始面向对象相关的知识,希望对你有一定的参考价值。
面向对象vs面向过程
面向过程
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。
优缺点
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
初始类和对象
python中一切皆为对象,类型的本质就是类
类: 具有相同属性和相同动作的一类事物,组成一个类
对象:具有的摸一个具有是技术性和具体动作的一个实体
类似抽象的,对象是具体的
类被创造出来,是为了描述对象的
函数版本的代码是面向过程的思想,而类的引入则是面向对象的思想了。你先规划好有哪些角色,角色有哪些相同的属性,有哪些不能的能力,有哪些不同的属性,先做好规划。然后去一一实现
类的相关知识
class Person: #定义一个人类 role = \'person\' #人的角色属性都是人 # 静态属性 def walk(self): #人都可以走路,也就是有一个走路方法,也叫动态属性 print("person is walking...")
只要是写在类名中的名字,不管是变量还是函数名,都不能在类的外部直接调用
只能通过类名来使用它
类有两种作用:属性引用和实例化
属性引用(类名.属性)
class Person: #定义一个人类 role = \'person\' #人的角色属性都是人 def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用
类的静态属性
class 类名: 静态属性 = 123 def 动态属性(self): # 在类中的方法的一个默认的参数,但也只是一个形式参数,约定必须叫self print(\'-->\',self) # 只要是写在类名中的名字 不管是变量还是函数名 都不能在类的外部直接调用 # 只能通过类名来使用它 # 类名的第一个功能是 —— 查看静态属性 print(类名.静态属性) # 查看 类名.静态属性 = 456 # 修改 print(类名.静态属性) 类名.静态属性2 = \'abc\'# 增加 print(类名.静态属性2) # del 类名.静态属性2 # print(类名.静态属性2) print(类名.__dict__) # 类中必要的默认值之外 还记录了程序员在类中定义的所有名字
实例化
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
实例化的过程理解
为什么会执行init中的内容?
self到底是什么?
实例化的过程
类名()就是实例化
在实例化的过程中 发生了很多事情是外部看不到的
1.创建了一个对象
2.自动调用__init__方法
这个被创造的对象会被当做实际参数传到__init__方法中,并且传给第一个参数self
3.执行init方法中的内容
4.自动的把self作为返回值 返回给实例化的地方
class Person: #定义一个人类 role = \'person\' #人的角色属性都是人 def __init__(self,name): self.name = name # 每一个角色都有自己的昵称; def walk(self): #人都可以走路,也就是有一个走路方法 print("person is walking...") print(Person.role) #查看人的role属性 print(Person.walk) #引用人的走路方法,注意,这里不是在调用 类名可以查看某个方法,但是不可以直接调用方法
实例化的过程就是 类——>对象 的过程
原本我们只有一个Person类,在这个过程中,产生了一个egg对象,有自己具体的名字、攻击力和生命值。
语法:对象名 = 类名(参数)
alex = Person(\'alex\') #类名()就等于在执行Person.__init__() #执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
查看属性&调用方法
print(alex.name) #查看属性直接 对象名.属性名 print(alex.walk()) #调用方法,对象名.方法名()
关于self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做。
因为你瞎改别人就不认识
类属性的补充
一:我们定义的类的属性到底存到哪里了?有两种方式查看 dir(类名):查出的是一个名字列表 类名.__dict__:查出的是一个字典,key为属性名,value为属性值 二:特殊的类属性 类名.__name__# 类的名字(字符串) 类名.__doc__# 类的文档字符串 类名.__base__# 类的第一个父类(在讲继承时会讲) 类名.__bases__# 类所有父类构成的元组(在讲继承时会讲) 类名.__dict__# 类的字典属性 类名.__module__# 类定义所在的模块 类名.__class__# 实例对应的类(仅新式类中)
对象的相关知识
回到人狗大战,现在需要对我们的类做一点改变。
人类除了可以走路之外吗,还应该具备一些攻击技能
class Person: # 定义一个人类 role = \'person\' # 人的角色属性都是人 def __init__(self,name,sex,hp,dps): self.name = name self.sex = sex self.hp = hp self.dps = dps def attack(self,dog): # 人可以攻击狗,这里的狗也是一个对象 # 人攻击狗,那么狗的生命值就壶根据人的攻击力而下降 dog.hp -= self.dps print(\'%s打了%s,%s掉了%s点血,剩余%s点血\' % (self.name, dog.name, dog.name, self.dps, dog.hp))
对象是关于类而实际存在的一个例子,即实例
对象/实例只有一种作用:属性引用
print(alex.name) print(ha2.name) print(ha2.dps)
你也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也管它叫动态属性。
引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号
Person.attack(alex,ha2) ## 调用类中的方法的方式 可以简写下一行为 alex.attack alex.attack(ha2) ha2.bite(alex)
练习:写一个计算器计算圆的面积和周长
class Circle(): # 定义一个类--圆 def __init__(self,r): self.pi = 3.14 self.r = r def area(self): print(\'area:\',self.pi * self.r ** 2) def perimeter(self): print(\'perimeter:\',self.pi * self.r * 2) r = Circle(5) # 实例化一个半径=5的圆 r.area() r.perimeter()
现象对象小结--定义及调用的固定模式
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 # 查看对象的属性,直接用对象名.属性名 即可 对象名.方法名() # 调用类中的方法。直接用 对象名.方法名() 即可
类和对象在内存中这些事儿 (命名空间)
创建一个类就会创建一个类的Name Space,用来存储类中定义的所有名字,这些名字称为类的属性
而类有两种属性:静态属性和动态属性
- 静态属性就是直接在类中定义的变量
- 动态属性就是定义在类中的方法
其中类的静态属性是共享给所有对象的
4341594072 >>>id(Person.role) 4341594072
而类的动态属性是绑定到所有对象的
>>>egg.attack <bound method Person.attack of <__main__.Person object at 0x101285860>> >>>Person.attack <function Person.attack at 0x10127abf8>
创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性
在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常
class Person: COUNTRY = \'中国人\' def __init__(self,name): self.name = name alex = Person(\'alex\') egon = Person(\'egon\')
实例化过程:
当实例化一个对象的时候,在内存中间的创建步骤:
1. 在内存中开辟一块地址,存放类的名称,同时开辟类的namespace存放类中的静态属性和动态属性
2. 实例化执行的时候,执行init,开辟一个新的内存空间,self指向这个空间,在里面存放 name=\'alex\'
3. self.name = name 赋值过程
4. alex = Person(\'alex\') # 将对象赋值给alex
class Person: COUNTRY = \'中国人\' # 静态属性 def __init__(self,name): self.name = name def eat(self): print(\'%s在吃泔水\'%self.name) alex = Person(\'alex\') egon = Person(\'egon\') print(alex.name) print(egon.name) print(alex.COUNTRY) alex.eat() # Person.eat(alex) alex ---> Person 当一个类在创建一个实例的时候 就产生了一个这个实例和类之间的联系 (类对象指针) 可以通过实例 对象 找到实例化它的类 但是 类不能找到它的实例化
class Person: COUNTRY = [\'中国人\'] # 静态属性 Country = \'中国人\' # 静态属性 def __init__(self,name): self.name = name def eat(self): print(\'%s在吃泔水\'%self.name) alex = Person(\'alex\') egon = Person(\'egon\') print(alex.Country) alex.Country = \'印度人\' print(alex.Country) print(egon.Country) print(Person.Country) alex.COUNTRY[0] = \'印度人\' print(alex.COUNTRY) print(egon.COUNTRY) print(Person.COUNTRY) alex.COUNTRY = [\'印度人\'] print(egon.COUNTRY) print(Person.COUNTRY)
# 在访问变量的时候,都先使用自己命名空间中的,如果自己的空间中没有,再到类的空间中去找
# 在使用对象修改静态变量的过程中,相当于在自己的空间中创建了一个新的变量
# 在类的静态变量的操作中 应该使用类名来直接进行操作 就不会出现乌龙问题
创建一个类,能够自动及素颜这个类创建了多少个实例
class Foo: count = 0 def __init__(self): ###每次实例化一个对象,都会自动调用一次__init__函数 Foo.count += 1 f1 = Foo() print(Foo.count) [Foo() for i in range(10)] print(Foo.count)
结论:
对象可以访问类的命名空间
类不能访问对象的命名空间
面向对象的组合用法
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
一切皆对象
数据类型 -- 都是类
那么任意一个字符串,都是str这个类的对象
人狗大战-- 升级版
class Person: def __init__(self,name,sex,hp,dps): self.name = name self.hp = hp self.dps = dps self.sex = sex self.bag = [] def attack(self,dog): dog.hp -= self.dps print(\'%s打了%s,%s掉了%s点血,剩余%s点血\' % (self.name, dog.name, dog.name, self.dps, dog.hp)) class Dog: def __init__(self,name,kind,hp,dps): self.name = name self.hp = hp self.dps = dps self.kind = kind def bite(self,person): person.hp -= self.dps print(\'%s打了%s,%s掉了%s点血,剩余%s点血\' % (self.name, person.name, person.name, self.dps, person.hp)) class Weapon: def __init__(self,name,price,dps): self.name = name self.price = price self.dps = dps def kill(self,dog): dog.hp -= self.dps alex = Person(\'alex\',\'N/A\',250,5) ha2 = Dog(\'哈士奇\',\'藏獒\',15000,200) roubaozi = Weapon(\'肉包子\',600000,10000) alex.money = 1000000 if alex.money >= roubaozi.price: alex.weapon = roubaozi #把weapon类的肉包子对象座位对象alex的属性。
alex.weapon.kill(ha2) # <==> roubaozi.kill
print(ha2.hp)
组合练习--计算圆环的面积和周长
pi = 3.14 class Circle: def __init__(self,r): self.r = r def area(self): return pi * self.r ** 2 def perimeter(self): return self.r *pi * 2 # r1 = Circle(2) # # print(r1.area()) class Ring(): def __init__(self,out_r,in_r): self.out_c = Circle(out_r) ## 把圆的实例化过程放在init中做初始化 在这里初始化了两个圆出来 self.in_c = Circle(in_r) def area(self): return self.out_c.area() - self.in_c.area() # 这两个圆调用area方法 相减! def perimeter(self): return self.out_c.perimeter() - self.in_c.perimeter() ring1 =Ring(5,2) print(ring1.area()) print(ring1.perimeter())
面向对象的三大特性(继承 多态 封装)
继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类。父类又可称为基类或者超类,新建的类称为派生类或子类
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类 pass class ParentClass2: #定义父类 pass class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass pass class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类 pass
查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 (<class \'__main__.ParentClass1\'>,) >>> SubClass2.__bases__ (<class \'__main__.ParentClass1\'>, <class \'__main__.ParentClass2\'>)
提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__ (<class \'object\'>,) >>> ParentClass2.__bases__ (<class \'object\'>,)
继承与抽象(先抽象再继承)
抽象即抽取类似或者说比较像的部分。
抽象分成两个层次:
1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
2.将人,猪,狗这三个类比较像的部分抽取成父类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中,一个动作或者说一种技巧。通过抽象可以得到类。
继承与重用性
使用代码可以解决代码重用的问题
==========================第一部分 例如 猫可以:喵喵叫、吃、喝、拉、撒 狗可以:汪汪叫、吃、喝、拉、撒 如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下: #猫和狗有大量相同的内容 class 猫: def 喵喵叫(self): print \'喵喵叫\' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something class 狗: def 汪汪叫(self): print \'汪汪叫\' def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something ==========================第二部分 上述代码不难看出,吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现: 动物:吃、喝、拉、撒 猫:喵喵叫(猫继承动物的功能) 狗:汪汪叫(狗继承动物的功能) 伪代码如下: class 动物: def 吃(self): # do something def 喝(self): # do something def 拉(self): # do something def 撒(self): # do something # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 猫(动物): def 喵喵叫(self): print \'喵喵叫\' # 在类后面括号中写入另外一个类名,表示当前类继承另外一个类 class 狗(动物): def 汪汪叫(self): print \'汪汪叫\' ==========================第三部分 #继承的代码实现 class Animal: def eat(self): print("%s 吃 " %self.name) def drink(self): print ("%s 喝 " %self.name) def shit(self): print ("%s 拉 " %self.name) def pee(self): print ("%s 撒 " %self.name) class Cat(Animal): def __init__(self, name): self.name = name self.breed = \'猫\' def cry(self): print(\'喵喵叫\') class Dog(Animal): def __init__(self, name): self.name = name self.breed=\'狗\' def cry(self): print(\'汪汪叫\') # ######### 执行 ######### c1 = Cat(\'小白家的小黑猫\') c1.eat() c2 = Cat(\'小黑的小白猫\') c2.drink() d1 = Dog(\'胖子家的小瘦狗\') d1.eat() 使用继承来重用代码比较好的例子 使用继承来解决代码重用的例子
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时
我们不可能从头开始写一个类B,这就用到了类的继承的概念。
通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用
回头看我们的人狗大战代码中,人和狗都有同时拥有的属性,大量代码重复。那么我们可以把相同的属性抽象出来写一个父类Animal。
然后人和狗分别继承animal的属性,再分别加入自己的属性。
如果子类没有init方法:
如果子类有自己的init方法呢?
如果有自己的init就不回去父类的命名空间去找init方法。
如果仍旧想引用父类的init,就需要用super() 的写法去引用父类的init方法。
# class Animal(): def __init__(self, name, hp, dps): self.name = name self.hp = hp self.dps = dps def eat(self): print(\'%s吃药回血了\' %self.name) class Person(Animal): def __init__(self, name, hp, dps, sex): Animal.__init__(self, name, hp, dps) # 当写成super()的时候,不需要再写self参数 self.sex = sex def attack(self, dog): dog.hp -= self.dps print(\'%s打了%s,%s掉了%s点血,剩余%s点血\' % (self.name, dog.name, dog.name, self.dps, dog.hp)) class Dog(Animal): def __init__(self, name, hp, dps, kind): super().__init__(name, hp, dps) # 当写成super()的时候,不需要再写self参数 self.kind = kind def bite(self, person): person.hp -= self.dps print(\'%s咬了%s,%s掉了%s点血,剩余%s点血\' % (self.name, person.name, person.name, self.dps, person.hp)) alex = Person(\'alex\',250, 5, \'不详\') ha2 = Dog(\'哈士奇\', 15000, 200, \'藏獒\') alex.eat() ha2.eat() ha2.bite(alex) alex.attack(ha2)
一道著名的面试题: 下面代码的输出是什么?
class Foo: def __init__(self): self.func() def func(self): print(\'in Foo\') class Son(Foo): def func(self): print(\'in Son\') Son()
答案是: in Son
在涉及到继承的时候,见到self,不要在本类中找它的调用。而要先看看,self到底是谁。
在这道题中。self是初始化Son的时候执行的,所以调用self.func的时候,执行的是Son中的func函数。
钻石继承
python两种类
经典类 py3已经灭绝了 在python2里还存在,在py2中只要程序员不主动继承object,这个类就是经典类 —— 深度优先
新式类 python3所有的类都是新式类,所有的新式类都继承自object —— 在多继承中遵循广度优先算法
钻石继承问题
python3
广度优先算法--遍历算法
钻石模型和小乌龟模型
class A: def f(self): print(\'in A\') class B(A): pass # def f(self): # print(\'in B\') class C(A): pass # def f(self): # print(\'in C\') class D(B,C): pass # def f(self): # print(\'in D\') class E(C): pass # def f(self): # print(\'in B\') class F(D,E): pass # def f(self): # print(\'in C\') d = D() d.f() print(F.mro()) Python 面向对象编程基础——初始化实例类属性方法