面向对象 VS 面向过程
面向过程
面向过程的程序设计的核心是过程(流水线式思维)
优点:
极大的降低了写程序的复杂度
缺点:
一整个过程(流水线)只能解决一个问题,若问题更改,代码几乎要全部重写。
应用场景:
一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
更加适合瀑布流式的产品开发环境中
面向对象
面向对象的程序设计的核心是对象(上帝式思维)
优点是:
解决了程序的扩展性
缺点:
可控性差,无法像面向过程那样及其精准的处理某一问题
应用场景:
需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等
更加适合敏捷开发(迭代式)产品开发环境中
类
基本格式
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) #引用人的走路方法,注意,这里不是在调用
实例化:
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
实例化的过程就是类——>对象的过程
语法:对象名 = 类名(参数)
egg = Person('egon') #类名()就等于在执行Person.__init__()
执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。
查看属性和调用方法
print(egg.name) #查看属性直接 对象名.属性名
print(egg.walk()) #调用方法,对象名.方法名()
self:
在实例化时自动将对象/实例本身传给__init__的第一个参数
类属性的补充
一:我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
二:特殊的类属性
类名.__name__# 类的名字(字符串)
类名.__doc__# 类的文档字符串
类名.__base__# 类的第一个父类
类名.__bases__# 类所有父类构成的元组
类名.__dict__# 类的字典属性
类名.__module__# 类定义所在的模块
类名.__class__# 实例对应的类(仅新式类中)
对象
对象是关于类而实际存在的一个例子,即实例
面向对象的定义及调用的固定格式
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 #查看对象的属性,直接用 对象名.属性名 即可
对象名.方法名() #调用类中的方法,直接用 对象名.方法名() 即可
类命名空间与对象、实例的命名空间
创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性
其中类的数据属性是共享给所有对象的
而类的动态属性是绑定到所有对象的
类的两种属性:
静态属性和动态属性
静态属性就是直接在类中定义的变量
动态属性就是定义在类中的方法
组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
面向对象三大特性:继承、封装、多态
继承
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
单继承与多继承实例
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
pass
继承与抽象
继承:
是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
抽象:
只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
继承与重用性
派生
super
在python3中,子类执行父类的方法可以直接用super方法.
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师
抽象类与接口类
接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,
子类继承接口类,并且实现接口中的功能
继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,
可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合
依赖倒置原则:
高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。
换言之,要针对接口编程,而不是针对实现编程
抽象类
抽象类只能被继承,不能被实例化
抽象类的本质还是类,指的是一组类的相似性,包括数据属性和函数属性,而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
在python中,并没有接口类这种东西,即便不通过专门的模块定义接口,我们也应该有一些基本的概念。
多继承问题
在继承抽象类的过程中,应该尽量避免多继承;
而在继承接口的时候,应该要多继承接口
接口隔离原则:
使用多个专门的接口,而不使用单一的总接口。即客户端不应该依赖那些不需要的接口。
方法的实现
在抽象类中,我们可以对一些抽象方法做出基础实现;
而在接口类中,任何方法都只是一种规范,具体的功能需要子类实现
继承
类如果继承了多个类,寻找其方法的方式有两种:深度优先和广度优先
经典类 ------- 深度优先
新式类 ------- 广度优先
取决于当前类或者父类是否继承object类
继承的作用:
减少代码的重用
提高代码可读性
规范编程模式
多态
多态指的是一类事物有多种形态
多态性
什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例
封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式。
优点:
将变化隔离、便于使用、提高复用性、提高安全性
封装原则
将不需要对外提供的内容都隐藏起来;
把属性都隐藏,提供公共方法对其访问。
私有变量和私有方法
在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)
property
classmethod
staticmethod
面向对象之进阶
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
issubclass(sub, super)检查sub类是否是 super 类的派生类
反射
通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)
hasattr 检测是否含有某属性
getattr 获取属性
setattr 设置属性
delattr 删除属性
内置方法:
改变对象的字符串显示__str__,__repr__
自定制格式化字符串__format__
__del__
析构方法,当对象在内存中被释放时,自动触发执行。无需定义,无需关心python内存分配与释放
item系列
__getitem__
__setitem__
__delitem__
__new__
__call__ 对象后面加括号,触发执行。
__len__
__hash__
__eq__
单例模式:
保证一个类只有一个实例,并提供一个访问它的全局访问点
使用场景:当类只有一个实例而且用户可以从一个众所周知的访问点访问它时。比如:数据库链接、Socket创建链接
单例模式的优点:
对唯一实例的受控访问
单利相当于全局变量,但防止了命名空间被污染
与单利模式功能相似的概念:
全局变量、静态变量(方法)
为什么用单例模式,不用全局变量呢?
全局变量可能会有名称空间的干扰,如果有重名的可能会被覆盖
单例模式实现的四种方式:
方式一(文件导入的形式):
s1.py class Foo(object): def test(self): print("123") v = Foo() #v是Foo的实例 s2.py from s1 import v as v1 print(v1,id(v1)) #<s1.Foo object at 0x0000000002221710> 35788560 from s1 import v as v2 print(v1,id(v2)) #<s1.Foo object at 0x0000000002221710> 35788560 # 两个的内存地址是一样的 # 文件加载的时候,第一次导入后,再次导入时不会再重新加载。
方式二(基于类实现的单例模式):
# ======================单例模式:无法支持多线程情况=============== class Singleton(object): def __init__(self): import time time.sleep(1) @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance import threading def task(arg): obj = Singleton.instance() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() # ====================单例模式:支持多线程情况================、 import time import threading class Singleton(object): _instance_lock = threading.Lock() def __init__(self): time.sleep(1) @classmethod def instance(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): with Singleton._instance_lock: #为了保证线程安全在内部加锁 if not hasattr(Singleton, "_instance"): Singleton._instance = Singleton(*args, **kwargs) return Singleton._instance def task(arg): obj = Singleton.instance() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() time.sleep(20) obj = Singleton.instance() print(obj) # 使用先说明,以后用单例模式,obj = Singleton.instance() # 示例: # obj1 = Singleton.instance() # obj2 = Singleton.instance() # print(obj1,obj2) # 错误示例 # obj1 = Singleton() # obj2 = Singleton() # print(obj1,obj2)
方式三(基于__new__实现的单例模式):
# =============单线程下执行=============== import threading class Singleton(object): _instance_lock = threading.Lock() def __init__(self): pass def __new__(cls, *args, **kwargs): if not hasattr(Singleton, "_instance"): with Singleton._instance_lock: if not hasattr(Singleton, "_instance"): # 类加括号就回去执行__new__方法,__new__方法会创建一个类实例:Singleton() Singleton._instance = object.__new__(cls) # 继承object类的__new__方法,类去调用方法,说明是函数,要手动传cls return Singleton._instance #obj1 #类加括号就会先去执行__new__方法,在执行__init__方法 # obj1 = Singleton() # obj2 = Singleton() # print(obj1,obj2) # ===========多线程执行单利============ def task(arg): obj = Singleton() print(obj) for i in range(10): t = threading.Thread(target=task,args=[i,]) t.start() # 使用先说明,以后用单例模式,obj = Singleton() # 示例 # obj1 = Singleton() # obj2 = Singleton() # print(obj1,obj2)
方式四(基于metaclass实现的单例模式):
""" 1.对象是类创建,创建对象时候类的__init__方法自动执行,对象()执行类的 __call__ 方法 2.类是type创建,创建类时候type的__init__方法自动执行,类() 执行type的 __call__方法(类的__new__方法,类的__init__方法) # 第0步: 执行type的 __init__ 方法【类是type的对象】 class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): pass # 第1步: 执行type的 __call__ 方法 # 1.1 调用 Foo类(是type的对象)的 __new__方法,用于创建对象。 # 1.2 调用 Foo类(是type的对象)的 __init__方法,用于对对象初始化。 obj = Foo() # 第2步:执行Foo的 __call__ 方法 obj() """ # ===========类的执行流程================ class SingletonType(type): def __init__(self,*args,**kwargs): print(self) #会不会打印? #<class '__main__.Foo'> super(SingletonType,self).__init__(*args,**kwargs) def __call__(cls, *args, **kwargs): #cls = Foo obj = cls.__new__(cls, *args, **kwargs) obj.__init__(*args, **kwargs) return obj class Foo(metaclass=SingletonType): def __init__(self,name): self.name = name def __new__(cls, *args, **kwargs): return object.__new__(cls, *args, **kwargs) ''' 1、对象是类创建的,创建对象时类的__init__方法会自动执行,对象()执行类的__call__方法 2、类是type创建的,创建类时候type类的__init__方法会自动执行,类()会先执行type的__call__方法(调用类的__new__,__init__方法) Foo 这个类是由SingletonType这个类创建的 ''' obj = Foo("hiayan") # ============第三种方式实现单例模式================= import threading class SingletonType(type): _instance_lock = threading.Lock() def __call__(cls, *args, **kwargs): if not hasattr(cls, "_instance"): with SingletonType._instance_lock: if not hasattr(cls, "_instance"): cls._instance = super(SingletonType,cls).__call__(*args, **kwargs) return cls._instance class Foo(metaclass=SingletonType): def __init__(self,name): self.name = name obj1 = Foo('name') obj2 = Foo('name') print(obj1,obj2)