一、初识类和对象
python中一切皆为对象,类型的本质是类。
>>> d = dict(name='eva') #实例化一个对象
>>> d.pop('name') #向d发一条消息,执行d方法
'eva'
>>>
从上面的例子来看,字典就是一类数据结构,用{}表示,里面由K-V键值对的东西来表示,他还具有一些增删改查的方法。对于一个类来说,它具有相同的特征属性和方法。
在python中,用变量表示特征,用函数来表示技能,因而具有相同的特征和技能的一类事物就是“类”,对象则是这一类事物中具体的一个。
1、类的相关知识
(1)初识类:
声明:
#声明函数 def functionName(args): '函数文档字符串' 函数体 #声明类 ''' class 类名 '类的文档字符串' 类体 ''' # 创建一个类 class Data: pass
class Person: #定义一个人类
role = 'person' #人的角色属性都是人
def walk(self): #人都可以走路,也就是有一个走路的方法,也叫动态属性
print('person is walking...')
(2)类有两种作用:属性引用和实例化
属性引用(类名.属性)
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def walk(self): #走路的方法 print('person is walking.....') print(Person.role) #查看人的cole属性 print(Person.walk) #引用人的走路的方法,注意,这里不是在调用
实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用来为每个实例定制自己的特征
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def __init__(self,name): self.name = name #每一个角色都有自己的昵称 def walk(self): #人走路的方法 print('person is wakling...') print(Person.role) print(Person.walk) ---------------------------------------------------------------- 实例化的过程就是类------>对象的过程 例如我们只有一个Person类,在这个过程中,产生了一个egg对象,有自己具体的名字、攻击力和生命值。 语法: 对象名 = 类名(参数) egg = Person('egon') #类名()就等于在执行Person.__init__() #执行完__init__()就会返回一个对象。这个对象类似一个字典,存折属于这个人本身的一些属性和方法。
查看属性&调用方法
print(egg.name) #查看属性直接 对象名.属性名 print(egg.walk()) #调用方法,对象名.方法名()
关于self:
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,也可以给他起个别的名字,一般不建议这么做,改了就不认识了。
类属性的补充:
一、定义的类的属性存在了哪儿? 查看方式: 1、dir(类名):查出的是一个名字列表 2、类名.__dict__ :查出来的是一个字典,key为属性名,value为属性值。 二、特殊的类属性 类名.__name__ #类的名字(字符串) 类名.__doc__ #类的文档字符串 类名.__base__ #类的第一个父类 类名.__bases__ #类所有父类构成的元组 类名.__dict__ #类的字典属性 类名.__module__ #类定义所在的模块 类名.__class__ #实例对应的类(仅在新式类中)
2、对象的相关知识
对象是关于类而实际存在的一个例子,即实例
class Person: #定义一个人类 role = 'person' #人的角色属性都是人 def __init__(self,name,aggressivity,life_value) self.name = name #每一个角色都有自己的昵称 self.aggressivity = aggressivity #每一个角色都有自己的攻击力 self.life_value = life_value #每一个角色都有自己的生命值 def attack(self,dog): #人可以攻击狗,这里的狗也是一个对象 #人攻击狗,那么狗的生命值会根据人的攻击力而下降 dog.life_value -= self.aggressivity
对象/实例只是一种作用:属性引用
egg = Person('egon',10,1000) print(egg.name) print(egg.aggressivity) print(egg.life_value)
print(egg.attack)
当然了,你也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也称之为动态属性。
引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号。
理解面向对象
def Person(*args,**kwargs): self = {} def attack(self,dog): dog['life_value'] -= self['aggressivity'] def __init__(name,aggressivity,life_value): self.['name'] = name self.['aggressivity'] = aggressivity self.['life_value'] = life_value self.['attack'] = attack __init__(*args,**kwargs) return self egg = Person('egon',78,10)
print(egg['name'])
小结——定义及调用的固定模式
class 类名: def __init__(self,参数1,参数2): self.对象的属性1 = 参数1 self.对象的属性2 = 参数2 def 方法名1(self):pass def 方法名2(self):pass 对象名 = 类名(1,2) #对象就是实例,代表一个具体的东西 #类名() :类名+括号就是实例化一个类,相当于调用了__init__方法 #括号里传参数,参数不需要传self,其他与__inint__中的形参一一对应 #结果返回一个对象 对象名.对象的属性1 #查看对象的属性,直接用 对象名.属性名 即可 对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
二、函数编程和面向对象编程的对比:
函数编程:将某功能代码封装到函数中,入后无需重复编写,直接调用函数即可。
#函数编程:
例1: def email(em,text): """ 发邮件 :return: """ print(em,text) def msg(em,text): """ 发短息 :return: """ print(em,text) def wechat(em,text): """ 发微信 :return: """ print(em,text) if 1 == 1: msg('1378786868768','张无忌登录了系统') email('[email protected]''张无忌登录了系统') wechat('xxxx','张无忌登录了系统')
例2:完成以下功能:
老王/30岁/男/上山去砍柴
老王/30岁/男/开车去东北
老王/30岁/男/喜欢大保健
def kc(name,age,gender):
data = "%s,今年%s,性别%s,喜欢上山砍柴"%(name,age,gender)
print(data)
def bc(name,age,gender):
data = "%s,今年%s,性别%s,喜欢开车去东北" % (name, age, gender)
print(data)
def dbj(name,age,gender):
data = "%s,今年%s,性别%s,喜欢大保健" % (name, age, gender)
print(data)
kc('老王',30,'男')
bc('老王',30,'男')
dbj('老王',30,'男')
面向对象编程:对函数进行分类和封装,使开发”更快更好更强。。。“
#面向对象编程:
面向对象的格式:
定义:
class 类名:
def 函数名(self):
pass
调用:
x1 = 类名()
x1.函数名()
示例1:
class Account:
def login(self):
user = input("请输入用户名:")
pwd = input("请输入密码:")
if user == "zhangwuji" and pwd == "123":
print("登录成功!")
else:
print("登录失败!")
obj = Account()
obj.login()
例1: class Message: def email(em, text): """ 发邮件 :return: """ print(em, text) def msg(em, text): """ 发短息 :return: """ print(em, text) def wechat(em, text): """ 发微信 :return: """ print(em, text) if 1 == 1: obj = Message() obj.msg('1378786868768', '张无忌登录了系统') obj.email('[email protected]''张无忌登录了系统') obj.wechat('xxxx', '张无忌登录了系统')
例2:完成以下功能:
老王/30岁/男/上山去砍柴
老王/30岁/男/开车去东北
老王/30岁/男/喜欢大保健
class LaoWang:
def __init__(self,name,age,gender):
self.name = name
self.age = age
self.gender = gender
def kc(self):
data = "%s,今年%s,性别%s,喜欢上山砍柴" % (self.name, self.age, self.gender)
print(data)
def bc(self):
data = "%s,今年%s,性别%s,喜欢开车去东北" % (self.name, self.age, self.gender)
print(data)
def dbj(self):
data = "%s,今年%s,性别%s,喜欢大保健" % (self.name, self.age, self.gender)
print(data)
obj = LaoWang('老王', 30, '男')
obj.kc()
obj.bc()
obj.dbj()
@总结:
函数:定义简单,调用简单
面向对象:定义复杂,调用复杂;归类,将某些类似的函数写在一起。
函数编程和面向对象编程各有优缺点,在不同的场景可选用不同编程方式。
python可支持函数和面向对象两种编程方式。
三、面向对象三大特性——封装、继承、多态
1、封装
封装,故名思意就是将内容封装到某个地方,以后再去调用被封装再某处的内容。所以在封装面向对象特性的时候,需要:
将内容封装到某处
从某处调用被封装的内容
第一步:将内容封装到某处(将数据封装到一个对象中)
# 创建类 class Foo: def __init__(self, name, age): # 称为构造方法,根据类创建对象时自动执行 self.name = name self.age = age # 根据类Foo创建对象 # 自动执行Foo类的__init__ 方法 obj1 = Foo("张无忌", 18) # 将张无忌和18分别封装到obj1(self)的name和age属性中 obj2 = Foo("赵敏", 20) # 将赵敏和20分别封装到obj1(self)的name和age属性中
==============================================================================
self是一个形式参数,当执行obj1 = Foo('张无忌',18)时,self等于obj1
当执行obj2 = Foo('赵敏',20)时,self等于obj2
所以,内容其实就被封装到了对象obj1和obj2中,每个对象中都有name和age属性,在内存里类似下图来保存
第二部:从某处调用被封装的类容(将相关功能封装到一个类中)
调用被封装的内容时,有两种情况:
通过对象直接调用
通过self间接调用
1、通过对象直接调用被封装的内容
# 创建类 class Foo: def __init__(self, name, age): # 称为构造方法,根据类创建对象时自动执行 self.name = name self.age = age # 根据类Foo创建对象 # 自动执行Foo类的__init__ 方法 obj1 = Foo("张无忌", 18) # 将张无忌和18分别封装到obj1(self)的name和age属性中 print(obj1.name) # 直接调用obj1对象的name属性 print(obj1.age) # 直接调用obj1对象的age属性 obj2 = Foo("赵敏", 20) # 将赵敏和20分别封装到obj1(self)的name和age属性中 print(obj2.name) # 直接调用obj2对象的name属性 print(obj2.age) # 直接调用obj2对象的age属性
2、通过self间接调用被封装的内容 ——执行类中的方法时,需要通过self间接调用被封装的内容
class Foo1: def __init__(self, name, age): self.name = name self.age = age def detail(self): print(self.name) print(self.age) obj1 = Foo1("周芷若", 20) obj1.detail() # pyhton默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此方法内部的self = obj1 obj2 = Foo1("小昭", 21) obj2.detail() # pyhton默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此方法内部的self = obj2
总结:对于面向对象的封装来说,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接获取被封装的内容。
2、继承
(1)什么时继承?
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或者超类,新建的类称为子类或者派生类。
python中类的继承分为:单继承和多继承
class ParentClass1: # 定义父类 pass class ParentClass2: # 定义父类 pass class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass1 pass class SubClass2(ParentClass1, ParentClass2): # 多继承,python支持多继承,用逗号隔开多个继承的类 pass
查看继承:
class ParentClass1: # 定义父类 pass class ParentClass2: # 定义父类 pass class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass1 pass class SubClass2(ParentClass1, ParentClass2): # 多继承,python支持多继承,用逗号隔开多个继承的类 pass print(SubClass1.__bases__) # __bases__只是查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类 # (,) print(SubClass2.__bases__) # (, )
提示:如果没有指定基类,python会默认继承object类,object是所有python类的基类,它提供了一些常见的方法(如__str__)的实现。
class ParentClass1: # 定义父类 pass class ParentClass2: # 定义父类 pass class SubClass1(ParentClass1): # 单继承,基类是ParentClass1,派生类是SubClass1 pass class SubClass2(ParentClass1, ParentClass2): # 多继承,python支持多继承,用逗号隔开多个继承的类 pass print(ParentClass1.__bases__) # 没有指定基类,默认继承object类 # (,) print(ParentClass2.__bases__) # (,)
(2)继承与抽象(先抽象在继承)
抽象即抽取类似或者说比较像的部分
抽象分成两个层次:
例如:1、将奥巴马和梅西这两个对象比较像的部分抽取成类;
2、将人、猪、狗这三类比较像的部分抽取成类。
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)
继承:继承是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象只是分析和设计的过程中一个动作或者一种技巧,通过抽象可以得到类。
(3)继承与重用性


######################第一部分####################################### 例如: 猫可以:吃、喝、爬树 狗可以:吃、喝、看家 如果我们要分别为猫和狗创建一个类,那么就需要为猫和狗实现他们的功能,伪代码如下: # 猫和狗有大量相同的内容 class 猫: 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 #####################第二部分########################## 上述代码不难看出,吃、喝、都是猫狗都具有的功能,而我们却分别在猫和狗的类中编写了两次。如果使用继承的思想,如下实现: 动物:吃、喝 猫:爬树(猫继承动物的功能) 狗:看家(狗继承动物的功能) 伪代码如下: class 动物: 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) class Cat(Animal): def __init__(self, name): self.name = name self.breed = '猫' def climb(self): print('爬树') class Dog(Animal): def __init__(self,name): self.name = name self.breed = '狗' def look_after_house(self): print('汪汪叫') ##########执行################# c1 = Cat('张无忌家中的小黑猫') c2 = Cat('赵敏家中的小白猫') c2.drink() d2 = Dog('周芷若家中的小花狗') d2.eat()
在开发过程中,如果我们定义了一个类A,然后又想创建另一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承概念。
通过继承的方式来创建类B,让B继承A,B会遗传A的所有的属性(数据属性和函数属性),实现代码的重用。
class Animal: """ 人和狗都是动物,所以创建一个Animal的基类 """ def __init__(self,name,aggressivity,life_value): self.name = name self.aggressivity = aggressivity self.life_value = life_value def eat(self): print("%s is eating" %self.name) class Dog(Animal): pass class Person(Animal): pass egg = Person('egon',10,1000) ha2 = Dog('二愣子',50,1000) egg.eat() ha2.eat()
提示:用已经有的类建立一个新类,这样就重用了已经有的软件中的一部分设置大部分,大大省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就缩短了软件开发周期,对大型软件来说意义重大。
(4)派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己的为准了。
class Animal: """ 人和狗都是动物,所以创建一个Animal的基类 """ def __init__(self, name, aggressivity, life_value): self.name = name self.aggressivity = aggressivity self.life_value = life_value def eat(self): print("%s is eating " % self.name) class Dog(Animal): """ 狗类,继承Animal """ def bite(self, people): """ 派生:狗咬人的技能 :param people: :return: """ people.life_value -= self.aggressivity class Person(Animal): """ 人类,继承Anmial """ def attack(self, dog): """ 派生:人有攻击的技能 :param dog: :return: """ dog.life_value -= self.aggressivity egg = Person('egon', 10, 1000) ha2 = Dog('二愣子', 50, 1000) print(ha2.life_value) print(egg.attack(ha2)) print(ha2.life_value)
注意:像ha2.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时,就用调用普通函数无异了,因此即便是self参数也要为其传值。
在python3中,子类执行父类的帆帆发也可以直接用super方法。


class A: def hahaha(self): print('A') class B(A): def hahaha(self): super().hahaha() print("B") a = A() b = B() b.hahaha() super(B, b).hahaha()
class Animal: ''' 人和狗都是动物,所以创造一个Animal基类 ''' def __init__(self,name,aggressivity,life_value): self.name = name #人和狗都有自己的昵称 self.aggressivity = aggressivity #人和狗都有自己的攻击力 self.life_value = life_value # 人和狗都有自己的生命值 def eat(self): print("%s i eatimg" %self.name) class Dog(Animal): ''' 狗类,继承Animal类 ''' def __init__(self,name,breed,aggressivity,life_value): super().__init__(name,aggressivity,life_value) # 执行父类Animal的init方法 self.breed = breed # 派生出了新的属性 def bite(self,people): ''' 派生出了新技能:狗有咬人的功能 :param people: :return: ''' people.life_value -= self.aggressivity def eat(self): print('from Dog') class Person(Animal): ''' 人类,继承Animal ''' def __init__(self,name,aggressivity,life_value,money): super().__init__(name,aggressivity,life_value) # 执行父类的__init__方法 self.money = money # 派生出了新的属性 def attack(self,dog): ''' 派生出了新的技能,人有攻击的技能 :param dog: :return: ''' dog.life_value -= self.aggressivity def eat(self): Animal.eat(self) print('form Person') egg = Person('egon',10,1000,600) ha2 = Dog('二愣子','哈士奇',10,1000) print(egg.name) print(ha2.name) egg.eat()
通过继承建立了派生类与基类之间的关系,它是一种“是”的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师。
class Teacher: def __init__(self, name, gender): self.name = name self.gender = gender def teach(self): print('teaching') class Professor(Teacher): pass p1 = Professor('egon', 'male') p1.teach() # 结果: teaching
(5)抽象类与接口类
1)接口类:
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某个基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名),并且未实现接口的功能,子类继承接口类,并且实现接口类中的功能。
class Alipay: """ 支付宝支付 """ def pay(self, money): print('支付宝支付了%s元' % money) class Applepay: ''' apple pay支付 ''' def pay(self, money): print('apple pay 支付了%s元' % money) def pay(payment, money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 :param payment: :param money: :return: ''' payment.pay(money) p = Alipay() pay(p, 200)
开发中容易出现的问题
class Alipay: """ 支付宝支付 """ def pay(self, money): print('支付宝支付了%s元' % money) class Applepay: ''' apple pay支付 ''' def pay(self, money): print('apple pay 支付了%s元' % money) class Wechatpay: def fuqian(self, money): ''' 实现了pay的功能,但是名字不一样了 :param money: :return: ''' print('微信支付了%s元' % money) def pay(payment, money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 :param payment: :param money: :return: ''' payment.pay(money) p = Wechatpay() pay(p, 200) #执行会报错
接口初成:手动报异常:NotImplemented来解决开发中遇到的问题
class Payment: def pay(self): raise NotImplementedError class Wechatpay: def fuqian(self, money): ''' 实现了pay的功能,但是名字不一样了 :param money: :return: ''' print('微信支付了%s元' % money) p = Wechatpay() # 这里不报错 pay(p, 200) #这里会报错
借用abc模块来实现接口
from abc import ABCMeta, abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self, money): pass class Wechatpay(Payment): def fuqian(self, money): ''' 实现了pay的功能,但是名字不一样了 :param money: :return: ''' print('微信支付了%s元' % money) p = Wechatpay() #不调就报错
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现了强耦合。
继承的第二种含义非常重要,它又叫接口继承
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可以一视同仁的处理实现了特定的接口的所有对象”——这在程序上叫归一化。
归一化使得高层的外部使用者可以不加区分的处理所有的接口兼容的对象集合——就好像Linux的泛文件概念 一样,所有的东西都可以当文件一样处理,不必关心他是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
依赖倒置原则:
高层模块不应该依赖底层模块,二者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程。
在python中根本就没有一个叫interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口,如果非要去模仿接口的概念,可以借助第三方模块:
http://pypi.python.org/pypi/zope.interface
文档https://zopeinterface.readthedocs.io/en/latest/
设计模式:https://github.com/faif/python-patterns


接口提取了一群类共同的函数,可以把接口当做一个函数的集合。
然后让子类去实现接口中的函数。
这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。
再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样
2)抽象类
什么是抽象类?
与Java一样,python也有抽象类的概念,但是同样需要借助模块来实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化。
为什么要有抽象类?
如果说类是从一堆对象中抽取相同的内容而来,那么抽象类就是从一堆类中抽取抽相同的内容而来的,内容包括数据属性和函数属性。