OOP
OOP概念
- OOP思想
- Object Oriented Programming, 简称OOP,是一种程序设计思想
- 接触到任意一个任务,首先想到的是任务这个世界的构成,是由模型构成的
- 面向对象的实现过程: OOA(分析)->OOD(设计)->OOI(实现)
- 类和对象的概念
- 类:抽象名词,代表一个集合,共性的事物
- 对象:具象的事物,单个个体
- 类跟对象的关系
- 一个具象,代表一类事物的某一个个体
- 一个是抽象,代表的是一大类事物
- 类中的内容,应该具有两个内容
- 表明事物的特征,叫做属性(变量)
- 表明事物功能或动作,称为成员方法(函数)
类的基本实现
-
类的命名规范、实例化类、访问对象成员
-
可以通过默认内置变量检查类和对象的所有成员
- 对象所有成员检查:obj.dict
- 类所有成员: class_name.dict
>>> class A(): ... pass >>> a = A() >>> a.__dict__ {} >>> A.__dict__ {'__module__': '__main__', '__doc__': None}
-
类和对象的成员分析
- 类和对象都可以存储成员,成员可以归类所有
- 类存储成员时使用的是与类关联的一个对象
- 独享存储成员是存储在当前对象中
- 对象访问一个成员时,如果对象中没有该成员,尝试访问类中的的同名成员
如果对象中有此成员,一定使用对象中的成员 - 创建对象的时候,类中的成员不会放入对象当中,而是得到一个空对象,没有成员
- 通过对象对类中成员重新赋值或者通过对象添加成员时,对应成员会保存在对象中,而不会修改该成员
-
self.类变量与类名.类变量的区别:
- 两个类型不同,一个是类,一个是对象
- self.类变量实际上会创建只与那个对象相关的新实例变量,类变量不会改变。例如:
运行结果:class TempRes(object): RetDc1 = 1 RetDc2 = 1 def __init__(self): pass def setRet(self,value): self.RetDc1=value TempRes.RetDc2=value obj1=TempRes() obj2=TempRes() obj1.setRet(333) print obj1.RetDc1 print obj2.RetDc1 # 1 ,并没有改变,因为obj1设置只会影响obj1对象 print obj1.RetDc2 print obj2.RetDc2
333
1
333
333
- 关于self
- self在对象的方法中表示当前对象本身,如果通过对象调用一个方法,那么该对象会自动传入到当前方法的第一个参数中
- self并不是关键字,只是一个用于接受对象的普通参数,理论上也以是用任何一个普通变量名代替
- 方法中有self形参的方法称为非绑定类的方法,可以通过对象访问,没有self的是绑定类的方法,只能通过类访问
- 使用类访问绑定类的方法时,如果类方法中需要访问当前类的成员,可以通过__class__成员名来访问
def sayAgain():
print (class.name)
print (class.age)
print (“hello world”)
Teacher.sayAgain()
面向对象的三大特性
1. 封装:对对象的成员进行访问限制
- 封装的三个级别:
- 公开:public
- 受保护的:protected
- 私有:private
- public/private/protected不是关键字
- 判断对象的位置
- 对象内部
- 对象外部
- 子类中
- 私有
- 私有成员是最高级别的封装,只能在当前类或对象中访问
- Python的私有不是真私有,是一种称为name mangling的改名策略
可以使用对象._classname_attributename访问
class Person():
name = "bobo"
__age = 18
p = Person()
#print Person.__dict__
Person._Person__age=19
print Person._Person__age
- 受保护的
- 受保护的封装是将对象成员进行一定级别的封装,然后,在类中或者子类中都可以进行访问
但是在外部不可以 - 封装方法:在成员名称前添加一个下划线
- 公共的:对成员没有任何操作,任何地方都可以访问
- 受保护的封装是将对象成员进行一定级别的封装,然后,在类中或者子类中都可以进行访问
2. 继承
-
继承就是一个类可以获得另一个类的成员属性和成员方法
-
作用:减少代码,增加代码的复用功能,同时可以设置类与类直接的关系
-
继承与被继承的概念
- 被继承的类叫父类,或基类,超类
- 继承自超类的类叫子类,或派生类
- 之间存在is-a关系
-
继承的特征
- 所有的类都继承自object类,即所有的类都是object类的子类(自动继承,也可以公开声明继承自object)
- 子类一旦继承父类,即可以使用父类中除私有成员外的所有内容
- 子类继承父类后并没有将父类成员完全复制到子类中,而是通过引用关系访问调用
- 子类中可以定义独有的成员属性和方法
- 子类中定义的成员和父类成员如果相同,则优先使用子类成员
- 子类如果想扩充父类的方法,可以在定义新方法的同时访问父类成员来进行代码重用
- 调用方法: 父类名.父类成员 或 super().父类成员
-
继承变量函数的查找顺序问题
- 优先查找自己的变量
- 没有则查找父类
- 构造函数如果本类中没有定义,则自动查找调用父类构造函数
- 如果本类有定义,则不再继续向上查找 -
构造函数
- 是一类特殊的函数,在类进行实例化之前进行调用
- 如果定义了构造函数,则实例化时使用构造函数,不查找父类构造函数
- 如果没定义,则自动查找父类构造函数
- 如果子类没定义,父类的构造函数带参数,则构造对象时的参数应该按父类参数构造
-
super
- super不是关键字,而是一个类
- super的作用是获取MRO(MethodResolusionOrder)列表中的第一个类
- super与父类之间没有任何实质性关系,但可以通过super调用父类
- super使用两个方法
- 案例:
super().init(name, email)
-
单继承和多继承
- 单继承:每个类只能继承一个类
- 多继承:每个类允许继承多个类
- 比较:
单继承:传承有序逻辑清晰语法简单隐患少;缺点是功能不能无限扩展,只能在当前唯一的继承链中扩展
多继承:优点:类的功能扩展方便;缺点:继承关系混乱
-
菱形继承/钻石继承问题
- 多个子类继承自同一个父类,这些子类由被同一个类继承,于是继承关系图形成一个菱形图谱
- 关于多继承的MRO
- MRO就是多继承中,用于保存继承顺序的一个列表
- python本身采用C3算法来解决多继承的菱形继承进行计算的结果
- MRO列表的计算原则
- 子类永远在父类前面
- 如果多个父类,则根据继承语法中括号内类的书写顺序存放
-
不同集合的关系
比较好的处理方式:
将所有的参数改为关键字参数,并且将空字符作为默认值。
同时确保**kwargs参数能够捕获所有多余的参数,并通过super将这些参数传递给下一个类# 多重继承案例 class ContactList(list): def search(self,name): """return all contacts that contain the search value""" matching_contacts = [] for contain in self: if name in contain.name: matching_contacts.append(contain) return matching_contacts class Contact: all_contacts = ContactList () def __init__(self, name='', email='',**kwargs): super().__init__(**kwargs) self.name = name self.email = email self.all_contacts.append(self) class Superlier(Contact): def order(self, order): print("If this were a real system we would send" " '{}' order to '{}'".format(order, self.name)) class AddressHolder: def __init__(self, street='', city='', state='', code='', **kwargs): super().__init__(**kwargs) self.street = street self.city = city self.state = state self.code = code class Friend(Contact, AddressHolder): def __init__(self, phone='', **kwargs): super().__init__(**kwargs) self.phone =phone ```
-
构造函数
- 在对象进行实例化的时候,系统自动调用的一个函数叫构造函数,通常此函数用来对实例对象进行初始化
- 构造函数一定要有,如果不存在,则向上安装MRO顺序查找父类构造函数
- 继承中如果想扩展构造函数,两种方法:
1.通过父类名调用
2.通过super调用:
super(C,self).init(name) #C为本类
3. 多态
- 多态就是同一个对象在不同情况下有不同的状态出现
- 多态不是语法,是一种设计思想
- 多态性:一种调用方式,不同的调用效果
- Mixin设计模式
- 主要采用多继承方式对类的功能进行扩展
- 我们使用多继承语法来实现Minxin
- 使用Minxin实现多继承的时候非常小心
1.首先它必须表示某一单一功能,而不是某个物品
2.职责必须单一,如果有多个功能,则写多个Mixin
3.Mixin不能依赖于子类的实现
4.子类即使没有继承这个Mixin类,也能照常工作,只是缺少了某个功能 - 优点:
1.使用Mixin可以在不对类进行任何修改的情况下,扩展功能
2.可以方便的组织和维护不同功能组件的划分
3.可以根据需要任意调整功能类的组合
4.可以避免创建很多新的类,导致类的继承混乱 - 多态的案例
class AudioFile:
def __init__(self, filename):
if not filename.endswith(self.ext):
raise Exception("Invalid file format.")
self.filename = filename
class MP3File(AudioFile):
ext = "mp3"
def play(self):
print("playing {} as mp3".format(self.filename))
class WavFile(AudioFile):
ext = "wav"
def play(self):
print("playing {} as wav".format(self.filename))
class OggFile(AudioFile):
ext = "ogg"
def play(self):
print("playing {} as ogg".format(self.filename))
4. 类的相关函数
- issubclass:检测一个类是否是另一个类的子类
- isinstance:检测一个对象是否是一个类的实例
- hasattr:检测一个对象是否有成员xx
- getattr: get attr
- setattr: setattr(x, ‘y’, v) is equivalent to ``x.y = v’’
- delattr: delete attribute
- dir:获取对象成员列表
5. 类的成员描述符(属性)
- 类的成员描述符是为了在类中对类的成员属性进行相关操作而创建的一种方式
get:
set:
delete: - 如果想使用类的成员描述符,三种方法:
1.使用类实现描述器
2.使用属性修饰符
3.使用property函数
property(fget,fset,fdel,doc) - 三种方法的区别:
1.类的方式:适合多个类中的多个属性共用一个描述符
2.property:使用当前类中使用,可以控制一个类中多个属性
6. 类的内置属性
dict: 以字典的方式显示类的成员组成
doc: 获取类的文档信息
name:获取类的名称,如果在模块中使用,获取模块的名称
bases: 获取某个类的所有父类,以元组的方式显示
7. 类的常用魔术方法
- 魔术方法就是不需要人为调用的方法,基本是在特定的时刻自动触发
- 魔术方法的统一特征:方法名被前后各两个下划线包裹
init:构造函数
new: 对象实例化方法,此函数比较特殊,一般不需要使用
call: 对象当函数使用的时候触发
str:当对象被当做字符串使用的时候调用(推荐)
repr:返回字符串,注意与__str__区别class A(): def __call__(self, *args, **kwargs): print(" haha") def __str__(self): return "str" a = A() a() print(a)
- 描述符相关
set
get
delete - 属性操作相关描述符
getattr: 访问一个不存在的属性时触发
setattr:对成员属性进行设置的时候触发
参数:1.self用来获取当前对象 2.被设置的属性名称 3.需要对属性名称设置的值
作用:进行属性设置的时候进行验证或修改
注意:在该方法中不能对属性直接进行赋值操作,否则死循环
def setattr(self, name, value):
print ("{0}".format(name))
#self.name = value #会引发死循环
super().setattr(name, value) #解决死循环 - 运算分类相关魔术方法
gt: 进行大于判断的时候触发的函数
参数: self,第二个对象,返回值可以是任意值
读懂UML类图
类的属性表示方法
在UML类图中,类使用包含类名、属性(field)和方法(method)且带有分割线的矩形表示,比如图:
其中:
+表示public
-表示private
#表示protected(friendly归于这类)
:后表示返回值类型
因此,Employee类有三个私有属性和一个公有方法。
类与类之间的关系
关联关系:分为单向关联、双向关联和自关联
- 单向关联:用一个带箭头的直线表示。
图表示:每个顾客都有一个地址,这通过让Customer类持有一个类型为Address的成员变量类实现
- 双向关联:用不带箭头的直线表示。双方各自持有对方类型的成员变量。
图表示:在Customer类中维护一个Product[]数组,表示一个顾客购买了那些产品;在Product类中维护一个Customer类型的成员变量表示这个产品被哪个顾客所购买
- 自关联:用一个带有箭头且指向自身的直线表示
图表示:Node类包含类型为Node的成员变量,也就是“自己包含自己”
聚合关系:
Car类与Engine类就是聚合关系(Car类中包含一个Engine类型的成员变量)。由上图我们可以看到,UML中聚合关系用带空心菱形和箭头的直线表示。聚合关系强调是“整体”包含“部分”,但是“部分”可以脱离“整体”而单独存在。比如上图中汽车包含了发动机,而发动机脱离了汽车也能单独存在
组合关系:与聚合关系最大不同:”部分“脱离”整体“便不复存在。
如图:嘴是头的一部分且不能脱离头而单独存在。
依赖关系:
Driver的drive方法只有传入了一个Car对象才能发挥作用,因此我们说Driver类依赖于Car类。在UML类图中,依赖关系用一条带有箭头的虚线表示。
继承关系
继承关系对应的是extend关键字,在UML类图中用带空心三角形的直线表示,如下图所示中,Student类与Teacher类继承了Person类
类和对象的三种方法
- 实例方法
需要实例化对象才能使用的方法,使用过程中可能需要截止对象的其他对象的方法完成 - 静态方法 @staticmethod
不需要实例化,需要类直接访问:类名.方法名() - 类方法 @classmethod
不需要实例化,直接调用:类名.方法名()
class Person:
# 正常函数,只能通过类实例p调用
def eat(self):
print(self)
print("eating..")
# 类方法的第一个参数一般命名cls;可以作为类或类实例的方法使用
@classmethod
def play(cls):
print(cls)
print("play...")
# 静态方法:不需要第一个参数表示自身
@staticmethod
def say():
print("say..")
p = Person()
p.eat()
Person.play()
p.play()
p.say()
Person.say()
定义类Person,包含了三个函数,eat为实例方法,play为类方法,say为静态函数,用@staticmethod装饰器装饰
- 静态方法和类方法区别
虽然都可以直接用:类名.方法名()调用,但区别是:
@staticmethod不需要自身对象的self和自身类的cls参数,就根使用函数一样
@classmethod不需要self参数,但第一个参数必须是cls
如果在@staticmethod中调用该类的属性方法,只能直接类名.属性名或类名.方法名。
而如果@classmethod有cls参数,可用调用类属性、类方法、实例化对象等
案例:
class A(object):
a = 'a'
@staticmethod
def foo1(name):
print 'hello', name
print A.a # 正常
print A.foo2('mamq') # 报错: unbound method foo2() must be called with A instance as first argument (got str instance instead)
def foo2(self, name):
print 'hello', name
@classmethod
def foo3(cls, name):
print 'hello', name
print A.a
print cls().foo2(name)
抽象类
- 抽象方法:没有具体实现内容的方法称为抽象方法
- 抽象方法的主要意义是规范了子类的行为和接口
- 抽象类的使用需要借助abc模块
- 抽象类:包含抽象方法的类叫抽象类,通常称为ABC类
- 抽象类的使用:
1.抽象类可以包含具体方法,也可以包含具体方法
2.抽象类中可以有方法也可以有属性
3.抽象类不允许直接实例化
4.必须继承才可以使用,且继承的子类必须实现所有子类的方法
5.嘉定子类没有实现所有继承的抽象方法,则子类也不能实例化
6.抽象类的主要作用是设定类的标准,以便于开发时具有统一的规范 - 抽象基类
- 大多数抽象基类存在于collections模块
from collections import Container
Container.abstractmethods
自定义类
类其实是一个类定义和各种方法的自由组合
- 可以定义类和函数,然后自己通过类直接赋值
- 可以借助于MethodType实现
from types import MethodType
class A():
pass
def say(self):
print("saying..")
a=A()
a.say = MethodType(say,A)
a.say()
- 借助于type实现
def say(self):
print("saying...")
def talk(self):
print("talking")
# type创建类
A = type("AName", (object,), {"class_say":say, "class_talk":talk})
a = A()
a.class_say()
-
利用元类实现-MetaClass
1.元类是类
2.被用来创建别的类# 元类写法是固定的,必须继承于type # 元类一般命名以MetaClass结尾
class TulingMetaClass(type):
def __new__(cls, name, bases, attrs):
# 自己的业务处理
print("我是元类")
attrs['id'] = '0000'
attrs['addr'] = '杭州'
return type.__new__(cls, name, bases, attrs)
- 元类定义完就可以使用
class Teacher(object, metaclass=TulingMetaClass):
pass
t = Teacher()
t.id
案例02
要求:设计一个简单的房地产应用,允许经纪人管理可出售或出租的房产。可能有两类房产:公寓和房屋。经纪人应该可以输入与房产相关的细节信息,列出当前所有空闲的房产,将房产标记为售出或已出租。(为简单起见,暂不考虑修改已出售房产的细节信息)
设计的类图如下:(注意继承关系)
代码:
def get_valid_input(input_string, valid_option):
input_string += "({})".format(", ".join(valid_option))
response = input(input_string)
while response.lower() not in valid_option:
response = input(input_string)
return response
class Property:
def __init__(self, square_feet='',beds='',baths='',**kwargs):
super().__init__(**kwargs)
self.square_feet = square_feet
self.num_bedrooms = beds
self.num_baths = baths
def display(self):
print("PROPERTY DETAILS")
print("================")
print("square footage: {}".format(self.square_feet))
print("bedrooms: {}".format(self.num_bedrooms))
print("bathsrooms: {}".format(self.num_baths))
print()
def prompt_init():
return dict(square_feet=input("Enter the square feet: "),
beds=input("Enter numbers of bedrooms: "),
baths=input("Enter numbers of baths: "))
prompt_init = staticmethod(prompt_init)
class Apartment(Property):
valid_laundries = ("coin","ensuite","none")
valid_balconies = ("yes", "no", "solarium")
def __init__(self, balcony='', laundry='', **kwargs):
super().__init__(**kwargs)
self.balcony = balcony
self.laundry = laundry
def display(self):
super().display()
print("PROPERTY DETAILS")
print("laundry: %s" % self.laundry)
print("has balcony: %s" % self.balcony)
"""
def prompt_init():
parent_init = Property.prompt_init()
laundry = ''
while laundry.lower() not in Apartment.valid_laundries:
laundry = input("What laundry facilities does the property have? ({})".format(", ".join(Apartment.valid_laundries)))
balcony = ''
while balcony.lower() not in Apartment.valid_balconies:
laundry = input("does the property have a balcony? ({})".format(", ".join(Apartment.valid_balconies)))
parent_init.update({
"laundry":laundry,
"balcony":balcony})
return parent_init
prompt_init = staticmethod(prompt_init)
"""
def prompt_init():
parent_init = Property.prompt_init()
laundry = get_valid_input("What laundry facilities does the property have? ", Apartment.valid_laundries)
balcony = get_valid_input("does the property have a balcony? ", Apartment.valid_balconies)
parent_init.update({
"laundry":laundry,
"balcony":balcony
})
return parent_init
prompt_init = staticmethod(prompt_init)
class House(Property):
valid_garage = ("attached", "detached", "none")
valid_fenced = ("yes", "no")
def __init__(self, num_stories='', garage='', fenced='', **kwargs):
super().__init__(**kwargs)
self.garage = garage
self.fenced = fenced
self.num_stories = num_stories
def display(self):
super().display()
print("HOUSE DETAILS")
print("# of stories: {}".format(self.num_stories))
print("garage: {}".format(self.garage))
print("fenced yard: {}".format(self.fenced))
def prompt_init():
parent_init = Property.prompt_init()
fenced = get_valid_input("Is the yard fenced? ", House.valid_fenced)
garage = get_valid_input("Is there a garage? ", House.valid_garage)
num_stories = input("How many stories? ")
parent_init.update({
"fenced":fenced,
"garage":garage,
"num_stories":num_stories
})
return parent_init
prompt_init = staticmethod(prompt_init)
class Purchase:
def __init__(self, price='',taxes='',**kwargs):
super().__init__(**kwargs)
self.price = price
self.taxes = taxes
def display(self):
super().display()
print("PURCHASE DETAILS")
print("selling print: {}".format(self.price))
print("estimated taxes: {}".format(self.taxes))
def prompt_init():
return dict(
price = input("What is the selling price? "),
taxes = input("What are the estimated taxes? ")
)
prompt_init = staticmethod(prompt_init)
class Rental:
def __init__(self, furnished='', utilities='', rent='', **kwargs):
super().__init__(**kwargs)
self.furnished = furnished
self.rent = rent
self.utilities = utilities
def display(self):
super().display()
print("RENTAL DETAILS")
print("rent: {}".format(self.rent))
print("estimated utilities: {}".format(self.utilities))
print("furnished: {}".format(self.furnished))
def prompt_init():
return dict(
rent = input("What is the monthly rent? "),
utilities = input("What are the estimated utilities? "),
furnished = get_valid_input("Is the property furnished? ", ("yes", "no")))
prompt_init = staticmethod(prompt_init)
class HouseRental(Rental, House):
def prompt_init():
init = House.prompt_init()
init.update(Rental.prompt_init())
return init
prompt_init = staticmethod(prompt_init)
class ApartmentRental(Rental, Apartment):
def prompt_init():
init = Apartment.prompt_init()
init.update(Apartment.prompt_init())
return init
prompt_init = staticmethod(prompt_init)
class ApartmentPurchase(Purchase, Apartment):
def prompt_init():
init = Apartment.prompt_init()
init.update(Purchase.prompt_init())
return init
prompt_init = staticmethod(prompt_init)
class HousePurchase(Purchase, House):
def prompt_init():
init = House.prompt_init()
init.update(Purchase.prompt_init())
return init
prompt_init = staticmethod(prompt_init)
class Agent:
def __init__(self):
self.property_list = []
def display_properties(self):
for property in self.property_list:
property.display()
type_map = {
("house", "rental"): HouseRental,
("house", "purchase"): HousePurchase,
("apartment", "rental"): ApartmentRental,
("apartment", "purchase"): ApartmentPurchase
}
def add_property(self):
property_type = get_valid_input("What type of property?", ("house", "apartment")).lower()
payment_type = get_valid_input("What payment type?", ("purchase", "rental")).lower()
PropertyClass = self.type_map[(property_type, payment_type)]
init_args = PropertyClass.prompt_init()
self.property_list.append(PropertyClass(**init_args))
运行结果测试:
>>> from apartment import Agent
>>> agent = Agent()
>>> agent.add_property()
What type of property?(house, apartment)house
What payment type?(purchase, rental)rental
Enter the square feet: 1
Enter numbers of bedrooms: 2
Enter numbers of baths: 3
Is the yard fenced? (yes, no)no
Is there a garage? (attached, detached, none)none
How many stories? 4
What is the monthly rent? 5
What are the estimated utilities? 6
Is the property furnished? (yes, no)no
>>> agent.add_property()
What type of property?(house, apartment)apartment
What payment type?(purchase, rental)purchase
Enter the square feet: 5
Enter numbers of bedrooms: 5
Enter numbers of baths: 5
What laundry facilities does the property have? (coin, ensuite, none)none
does the property have a balcony? (yes, no, solarium)no
What is the selling price? 5
What are the estimated taxes? 5
>>> dir(agent)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'add_property', 'display_properties', 'property_list', 'type_map']
>>> agent.display()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Agent' object has no attribute 'display'
>>> agent.display_properties()
PROPERTY DETAILS
================
square footage: 1
bedrooms: 2
bathsrooms: 3
HOUSE DETAILS
# of stories: 4
garage: none
fenced yard: no
RENTAL DETAILS
rent: 5
estimated utilities: 6
furnished: no
PROPERTY DETAILS
================
square footage: 5
bedrooms: 5
bathsrooms: 5
PROPERTY DETAILS
laundry: none
has balcony: no
PURCHASE DETAILS
selling print: 5
estimated taxes: 5