面向对象 (Object Oriented,OO)
对软件开发相当重要,它的概念和应用甚至已超越了程序设计和软件开发,扩展到如数据库系统、交互式界面、应用结构、应用平台、分布式系统、网络管理结构、CAD
技术、人工智能等领域。面向对象是一种 对现实世界理解和抽象的方法,是计算机编程技术发展到一定阶段后的产物。
特点
封装
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。继承
继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来类的情况下对这些功能进行扩展。通过继承创建的新类称为「子类」或「派生类」,被继承的类称为「基类」、「父类」或「超类」。
要实现继承,可以通过 继承和组合 来实现。多态性
多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单说就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有两种方式,覆盖和重载。两者的区别在于:覆盖在运行时决定,重载是在编译时决定。并且覆盖和重载的机制不同。例如在 Java
中,重载方法的签名必须不同于原先方法的,但对于覆盖签名必须相同。
经典方法:
from abc import ABC, abstractmethod
基础类属性
MyClass()实际上调用的是 __init__(self) 方法,可以不定义,如果没有定义会在实例化后隐式调用。
作用:对实例进行初始化
print(MyClass) #不会调用 可以直接当作基类继承
print(MyClass()) #调用__init__ 有实例化才会初始化
a = MyClass() #调用__init__
初始化函数可以多个参数,请注意第一个位置必须是self,例如 __init__(self, name, age) 注意:
__init__() 方法不能有返回值,也就是只能是return None__init__ 方法的第一参数 self就是指代某一个实例自身。
self 这个名字只是一个惯例,它可以修改,但是请不要修改,否则影响代码的可读性
类实例化后,得到一个实例对象,调用方法时采用 <实例化对象>.<实例函数>()的方式,实例对象会绑定到方法上。
但是该函数签名是 <实例对象>(self)
这个<实例对象>.<实力函数>()调用时,会把方法的调用者<实例对象>实例作为第一参数self的实参传入。
self.name就是 <实例对象> 对象的name,name是保存在了<实例对象>上,而不是Person类上。所以,称为实例变量。
实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法
特殊属性 含义 | 特殊属性 含义 |
---|---|
__name__ | 对象名 |
__class__ | 对象的类型 |
__dict__ | 对象的属性的字典 |
__qualname__ | 类的限定名 |
__module__ | 类定义所在的模块名 |
__bases__ | 类的基类的元组,顺序为它们在基类列表中出现的顺序 |
__mro__ | 类的mro,class.mro()返回的结果的保存在__mro__ |
__dir__ | 返回类或者对象的所有成员名称列表。dir()函数操作实例就是调用__dir__() |
dir(obj)对于不同类型的对象obj具有不同的行为: 如果对象是模块对象,返回的列表包含模块的属性名和变量名
如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它的祖先类的属性名 如果是类的实
注意:
Python中每一种对象都拥有不同的属性。函数、类都是对象,类的实例也是对象。
class Person:
age = 3
def __init__(self,name):
self.name =name
print('----class----')
print(Person.__class__,type(Person))
print(sorted(Person.__dict__.items()),end='\n\n')#属性字典
tom = Person('Tom')
print('----instance tom----')
print(tom.__class__,type(tom))
print(sorted(tom.__dict__.items()),end='\n\n')
print("----tom's class----")
print(tom.__class__.__name__)
print(sorted(tom.__class__.__dict__.items()),end='\n\n')# type(tom).__dict__
装饰类 封装
类 做装饰器
增加类变量
#增加类变量
def add_name(name,cls):
cls.NAME = name#动态增加类属性
#改进成装饰器
def add_name(name,cls):
def wrapper(cls):
cls.NAME = name #动态增加类属性
return cls #cls代表的是类的本身,相对应的self则是类的一个实例对象。
return wrapper
@add_name('Tom')
class Person:
AGE = 3
之所以能够装饰,本质上是为类对象动态的添加了一个属性,而Person这个标识符指向这个类对象。
静态方法
1.在类定义中,使用@staticmethod装饰器修饰的方法
2.调用时,不会隐式的传入参数
静态方法,只是表明这个方法属于这个名词空间。函数归在一起,方便组织管理。
class Person:
def method(self):
print("{}'s method".format(self))
@classmethod #面向对象中实例方法有哪些作用,classmethod也就有哪些作用,只是这个面向的“对象”是类本身
def class_method(cls): #cls代表的是类的本身,相对应的self则是类的一个实例对象。
print('class = {0.__name__} ({0})'.format(cls))
cls.HEIGHT = 170
@staticmethod
def static_methd():
print(Person.HEIGHT)
使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用
@staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
属性装饰器
一般好的设计是:把实例的某些属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。
class Person:
def __init__(self,name,age=18):
self.name = name
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
self.__age =age
@age.deleter
def age(self):
# del self.__age
print('del')
tom = Person('Tom')
print(tom.age)
tom.age =20
print(tom.age)
del tom.age
特别注意:使用property装饰器的时候这三个方法同名
property装饰器
后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性
setter装饰器
与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写
deleter装饰器
可以控制是否删除属性。很少用
property装饰器必须在前,setter、deleter装饰器在后。
property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果
class Person:
def __init__(self,name,age=18):
self.name =name
self.__age =age
age = property(lambda self:self.__age)
tom = Person('Tom')
print(tom.age)
#调用父类的__init__方法的顺序有时决定着show方法的结果
需要在子类的__init__中导入父类
例如:
class Test(Person):
def __init__():
super().__init__(age)
多继承
Python使用MRO(method resolution order方法解析顺序)解决基类搜索顺序问题。 历史原因,MRO有三个搜索算法:
经典算法
按照定义从左到右,深度优先策略。
新式类算法
是经典算法的升级,深度优先,重复的只保留最后一个。
C3算法
在类被创建出来的时候,就计算出一个MRO有序列表。 C3算法解决多继承的二义性
经典算法有很大的问题,如果C中有覆盖A的方法,就不会访问到了,因为先访问A(深度优先)。
新式类算法,依然采用了深度优先,解决了重复问题,但是同经典算法一样,没有解决继承的单调性。
C3算法,解决了继承的单调性,它阻止创建之前版本产生二义性的代码。求得的MRO本质是为了线性化,且确定
了顺序。
单调性:
假设有A、B、C三个类,C的mro是[C, A, B],那么C的子类的mro中,A、B的顺序一致就是单调的。
类的类型继承
所有新类型的缺省类型是type(可以使用元类来改变)
特殊类型type是所有对象的缺省类型,也包括type自己。但它又是一个对象,因此从object继承
特殊类型object是继承树的顶层,它是python所有类型的最终基类
也就是说,继承都来自object,类型都看type。type也是对象继承自object,object也有类型是type。
这俩又特殊,type类型是它自己,object没有基类。
魔术方法
分类:
创建、初始化与销毁
__new__
__new__方法很少使用,即使创建了该方法,也会使用 return super().new(cls)基类object的__new__方法来创建实例并返回。
__init__
__del__
可视化
@functools.total_ordering装饰器
@functools.total_ordering装饰器就可以大大简化代码。
from functools import total_ordering
@total_ordering
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __eq__(self,other):
return self.age == other.age
def __gt__(self,other):
return self.age > other.age
tom = Person('tom',20)
jerry = Person('jerry',16)
print(tom>jerry)
print(tom<jerry)
print(tom>=jerry)#
print(tom<=jerry)#
上例中大大简化代码,但是一般来说比较实现等于或者小于方法也就够了,其它可以不实现,所以这个装饰器只是
看着很美好,且可能会带来性能问题,建议需要什么方法就自己创建,少用这个装饰器。
面向对象 容器
方法 | 意义 |
---|---|
__len__ | |
__iter__ | 迭代容器时,调用,返回一个新的迭代器对象 |
__contains__ | n成员运算符,没有实现,就调用__iter__方法遍历 |
__getitem__ | 实现self[key]访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为hashable,key不存在引发KeyError |
__setitem__ | 和__setitem__的访问类似,是设置值的方法 |
__missing__ | 字典或其子类使用 getitem()调用时,key不存在执行该发放 |
class Cart:
def __init__(self):
self.items = []
def __len__(self):
return len(self.items)
def additem(self,item):
self.items.append(item)
def __iter__(self):
# yield from self.items
return iter(self.items)
def __getitem__(self,index):#索引访问
return self.items[index]
def __setitem__(self,key,value):#索引赋值
self.items[key] =value
def __str__(self):
return str(self.items)
def __add__(self,other):# +
self.items.append(other)
return self
cart = Cart()
cart.additem(1)
cart.additem('abc')
cart.additem(3)
可调用对象
Python中一切皆对象,函数也不例外。
def foo():
print(foo.__module__,foo.__name__)
foo()
#等价于
foo.__call__()
函数即对象,对象foo加上(),就是调用此函数对象的 可调用对象
给类也定义一个call 方法
class Point:
def __init__(self,x,y):
self.x =x
self.y =y
def __call__(self,*args,**kwargs):
return"<Point {}:{}>".format(self.x,self.y)
p = Point(4,5)
print(p)
print(p())
使用python的魔术方法实现with
class Point:
def __init__(self):
print('init ~~~~~~~~')
def __enter__(self):
print('enter ~~~~~~~~')
return self#增加返回值
def __exit__(self,exc_type,exc_val,exc_tb):
print('exit ============')
p =Point()
with pasf:
print('in with-------------')
print(p == f)
print('with over')
print('=======end==========')
with语法,会调用with后的对象的__enter__方法,如果有as,则将该方法的返回值赋给as子句的变量。 上例,可以等价为f = p.__enter__()
contextlib.contextmanager
它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现__enter__和 __exit__方法。
对下面的函数有要求:必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。
也就是这个装饰器接收一个生成器对象作为参数。
import contextlib
@contextlib.contextmanager
def foo():#
print('enter')#相当于__enter__()
yield# yield 5,yield的值只能有一个,作为__enter__方法的返回值
print('exit')#相当于__exit__()
with foo() as f:
#raise Exception()
print(f)
总结
如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的方式加 __enter__和 __exit__ 方法方便。