抽象是隐藏多余细节的艺术。在面向对象的概念中,抽象的直接表现形式通常为类。虽然Python是解释性语言,但是它是面向对象的,从设计之初就已经是一门面向对象的语言。Python基本上提供了面向对象编程语言的所有元素,如果你已经至少掌握了一门面向对象语言,那么利用Python进行面向对象程序设计将会相当容易。下面就来了解一下如何在Python中进行对象编程。
了解python面向对象编程先讲述相关术语:类,类对象,实例对象,属性,函数和方法。
术语
类、类对象、实例对象
类是对现实世界中一些事物的封装,定义一个类可以采用下面的方式来定义:
class className:
block
注意:注意类名后面有个冒号,在block块里面就可以定义属性和方法
当一个类定义完之后,就产生了一个类对象。类对象支持两种操作:
引用:通过类对象去调用类中的属性或者方法
实例化:产生出一个类对象的实例
example
class people:
name = 'jack'
def printName(self):
print self.name
def fun():
print people.name
if __name__ == "__main__":
p = people()
print people.name #'jack'
说明:name == “main“相当于java中的main函数
类对象:people.name中people就是一个全局类对象,类对象可以访问类中属性和方法
实例对象: p = people()中p为实例化的对象,也成为类对象
属性:name为属性
方法:printName(),与某个对象进行绑定的函数称为方法,一般在类里面定义的函数与类对象或者实例对象绑定了,所以称作为方法
函数:fun() ,在类外定义的函数一般没有同对象进行绑定,就称为函数
属性
属性也分为公有属性及私有属性,类中我们可以定义一些属性,比如:
class people:
name = 'jack'
age = 12
if __name__ == "__main__":
p = people() #实例化对象p
print p.name,p.age #'jack' 12
print people.age # 12
上面,定义了一个people类,里面定义了name和age属性,默认值分别为’jack’和12
公有属性:name,age, 可以直接在类外通过对象名进行访问
私有属性: 在属性名前加”__” (如下), 不能够在类外通过对象名来进行访问的
class people:
__name = 'jack'
__age = 12
if __name__ == "__main__":
p = people() #实例化对象p
print p.__name,p.__age # 报错:AttributeError: people instance has no attribute '__name'
方法
类中可以根据需要定义一些方法,定义方法采用def关键字,在类中定义的方法至少会有一个参数,一般以名为’self’或’cls’(却别后面讲)的变量作为该参数(用其他名称也可以),而且需要作为第一个参数(self相当与C++中的this指针)
class people:
__name = 'jack'
__age = 12
def getName(self):
print self.__name
def getAge(self):
print self.__age
if __name__ == "__main__":
p = people()
print p.getName() #jack
print p.getAge() # 12
如上,当我们在类外不能对私有属性访问,可以通过构造方法进行间接访问p.getName()
类中内置方法
在Python中有一些内置的方法,这些方法命名都有比较特殊的地方(其方法名以2个下划线开始然后以2个下划线结束)
方法说明:
* 构造方法init(self,….)
在生成对象时调用,可以用来进行一些初始化操作,不需要显示去调用,系统会默认去执行。构造方法支持重载,如果用户自己没有重新定义构造方法,系统就自动执行默认的构造方法。
class people:
def __init__(self,name):
self.name = name
def printName(self):
print self.name
if __name__ == "__main__":
p = people("jack")
print p.printName()
‘init‘中定义的属性和类属性不太一样
- 析构方法del(self)
在释放对象时调用,支持重载,可以在里面进行一些释放资源的操作,不需要显示调用。 - new()
class Singleton:
__instance = None
def __init__(self):
pass
def __new__(cls, *args, **kwargs):
if Singleton.__instance is None:
Singleton.__instance = object.__new__(cls, *args, **kwargs)
return Singleton.__instance
- getattr()、setattr()和getattribute()
getattr(): 当读取对象的某个属性时,python会自动调用该方法(fruit.color将转换为fruit.getattr(color))
setattr(): 当使用赋值语句对属性进行设置时,python会自动调用
getattribute(): getattribute()的功能与getattr()类似,用于获取属性的值。但是getattribute()能提供更好的控制,代码更健壮
注意:python中并不存在setattribute()方法
class Fruit(object):
def __init__(self, color="red", price=0):
self.__color = color
self.__price = price
def __getattribute__(self, item): # 获取属性的方法
return object.__getattribute__(self, item)
def __setattr__(self, key, value):
self.__dict__[key] = value
if __name__ == "__main__":
fruit = Fruit("blue", 10)
print fruit.__dict__.get("_Fruit__color") # 获取color属性=blue
fruit.__dict__["_Fruit__price"] = 5
print fruit.__dict__.get("_Fruit__price") # 获取price属性= 5
原文处:Python不允许实例化的类访问私有数据,但你可以使用object._className__attrName访问这些私有属性不懂!
* getitem()
如果类把某个属性定义为序列,可以使用getitem()输出序列属性中的某个元素
class FruitShop:
def __getitem__(self, i): # 获取水果店的水果
return self.fruits[i]
if __name__ == "__main__":
shop = FruitShop()
shop.fruits = ["apple", "banana"]
print shop[1] # banana
for item in shop: # 输出水果店的水果:apple \n banana
- str()
表示对象代表的含义,返回一个字符串.实现了str()方法后,可以直接使用print语句输出对象,也可以通过函数str()触发str()的执行。这样就把对象和字符串关联起来,便于某些程序的实现,可以用这个字符串来表示某个类
class Fruit:
'''''Fruit类''' #为Fruit类定义了文档字符串
def __str__(self): # 定义对象的字符串表示
return self.__doc__
if __name__ == "__main__":
fruit = Fruit()
print str(fruit) # 调用内置函数str()触发__str__()方法,输出结果为:Fruit类
print fruit #直接输出对象fruit,返回__str__()方法的值,输出结果为:Fruit类
- call()
类中实现call()方法,可以在对象创建时直接返回call()的内容。使用该方法可以模拟静态方法
class Fruit:
class Growth: # 内部类
def __call__(self):
print 'grow...'
grow = Growth() #调用Growth(),此时将类Growth作为函数返回,即为外部类Fruit定义方法grow(),grow()将执行__call__()内的代码
if __name__ == "__main__":
fruit = Fruit()
fruit.grow() # grow...
Fruit.grow() # grow...
术语详解
类属性、实例属性
类属性:类对象所拥有的属性,它被所有类对象的实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量有点类似
实例属性:实例属性是不需要在类中显示定义的
class people:
name = 'jack' #公有的类属性
__age = 12 #私有的类属性
p = people()
p.age = 12 # 实例属性类外定义,为实例对象p特有
print p.age #正确
print people.age #错误, 类对象people并不拥有此实例属性
print p.name #正确
print people.name #正确
print p.__age #错误,不能在类外通过实例对象访问私有的类属性
print people.__age #错误,不能在类外通过类对象访问私有的类属性
解释: 类外对类对象people进行实例化之后,产生了一个实例对象p,然后p.age = 12这句给p添加了一个实例属性age,赋值为12
实例化对象的时候给age赋值
class people:
name = 'jack'
#__init__()是内置的构造方法,在实例化对象时自动调用
def __init__(self,age):
self.age = age #age为实例属性
p = people(12)
print p.name #正确
print p.age #正确
print people.name #正确
print people.age #错误, age为实例属性
上述,在’int‘中定义的age为实例属性,类对象不能访问
结论:类对象不能访问实例属性,实例对象对类属性及实例属性都可以访问
类外修改类属性:必须通过类对象去引用然后进行修改,如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性
class people:
country = 'china'
if __name__ == "__main__":
print people.country #china
p = people()
print p.country #china
p.country = 'japan'
print p.country # 实例属性会屏蔽掉同名的类属性,japan
print people.country #china
del p.country # 删除实例属性
print p.country #china
类方法、实例方法、静态方法
- 类方法
类对象所拥有的方法,需要用修饰器”@classmethod”来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以”cls”作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以’cls’作为第一个参数的名字,就最好用’cls’了),能够通过实例对象和类对象去访问
class people:
country = 'china'
#类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
p = people()
print p.getCountry() #可以用过实例对象引用
print people.getCountry() #可以通过类对象引用
用途:对类属性进行修改
class people:
country = 'china'
# 类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
@classmethod
def setCountry(cls, country):
cls.country = country
if __name__ == "__main__":
p = people()
print p.getCountry() # 可以用过实例对象引用 china
print people.getCountry() # 可以通过类对象引用 china
p.setCountry('japan') #结果显示在用类方法对类属性修改之后,通过类对象和实例对象访问都发生了改变
print p.getCountry() # japan
print people.getCountry() # japan
- 实例方法(self参数.)
类中最常定义的成员方法,它至少有一个参数并且必须以实例对象作为其第一个参数,一般以名为’self’的变量作为第一个参数(当然可以以其他名称的变量作为第一个参数)。在类外实例方法只能通过实例对象去调用,不能通过其他方式去调用
class people:
country = 'china'
#实例方法
def getCountry(self):
return self.country
p = people()
print p.getCountry() #正确,可以用过实例对象引用
print people.getCountry() #错误,不能通过类对象引用实例方法
- 静态方法:需要通过修饰器”@staticmethod”来进行修饰,静态方法不需要多定义参数
class people:
country = 'china'
@staticmethod
#静态方法
def getCountry():
return people.country
print people.getCountry()
访问:对于类属性和实例属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的实例属性,则实例属性会屏蔽类属性,即引用的是实例属性,若实例对象没有该名称的实例属性,则引用的是类属性
修改:如果在实例方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的实例属性,则修改的是实例属性,若实例对象没有该名称的实例属性,则会创建一个同名称的实例属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改
总结:
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
继承
类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装
在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。
#父类
class superClassName:
block
#子类
class subClassName(superClassName):
block
定义一个类的时候,可以在类名后面紧跟一对括号,在括号中指定所继承的父类,如果有多个父类,多个父类名之间用逗号隔开
1. example
以大学里的学生和老师举例,可以定义一个父类UniversityMember,然后类Student和类Teacher分别继承类UniversityMember:
class UniversityMember:
def __init__(self,name,age):
self.name = name
self.age = age
def getName(self):
return self.name
def getAge(self):
return self.age
class Student(UniversityMember):
def __init__(self,name,age,sno,mark):
UniversityMember.__init__(self,name,age) #注意要显示调用父类构造方法,并传递参数self
self.sno = sno
self.mark = mark
def getSno(self):
return self.sno
def getMark(self):
return self.mark
class Teacher(UniversityMember):
def __init__(self,name,age,tno,salary):
UniversityMember.__init__(self,name,age)
self.tno = tno
self.salary = salary
def getTno(self):
return self.tno
def getSalary(self):
return self.salary
大学中的每个成员都有姓名和年龄,而学生有学号和分数这2个属性,老师有教工号和工资这2个属性,从上面的代码中:
1. 在Python中,如果父类和子类都重新定义了构造方法init( ),在进行子类实例化的时候,子类的构造方法不会自动调用父类的构造方法,必须在子类中显示调用
2. 如果需要在子类中调用父类的方法,需要以”父类名.方法“这种方式调用,以这种方式调用的时候,注意要传递self参数过去
继承: 子类继承了父类所有的公有属性和方法,可以在子类中通过父类名来调用,而对于私有的属性和方法,子类是不进行继承的,因此在子类中是无法通过父类名来访问的
* 多重集成
注意:Python支持多继承,能够让一个子类有多个父类
class SubClass(SuperClass1,SuperClass2)
如果SubClass没有重新定义构造方法,它会自动调用哪个父类的构造方法?这里记住一点:以第一个父类为中心。如果SubClass重新定义了构造方法,需要显示去调用父类的构造方法,此时调用哪个父类的构造方法由你自己决定;若SubClass没有重新定义构造方法,则只会执行第一个父类的构造方法。并且若SuperClass1和SuperClass2中有同名的方法,通过子类的实例化对象去调用该方法时调用的是第一个父类中的方法。
多态
多态即多种形态,在运行时确定其状态,在编译阶段无法确定其类型,这就是多态。Python中的多态和Java以及C++中的多态有点不同,Python中的变量是弱类型的,在定义时不用指明其类型,它会根据需要在运行时确定变量的类型(个人觉得这也是多态的一种体现),并且Python本身是一种解释性语言,不进行预编译,因此它就只在运行时确定其状态,故也有人说Python是一种多态语言。在Python中很多地方都可以体现多态的特性,比如内置函数len(object),len函数不仅可以计算字符串的长度,还可以计算列表、元组等对象中的数据个数,这里在运行时通过参数类型确定其具体的计算过程,正是多态的一种体现。这有点类似于函数重载(一个编译单元中有多个同名函数,但参数不同),相当于为每种类型都定义了一个len函数。这是典型的多态表现。有些朋友提出Python不支持多态,我是完全不赞同的。
本质上,多态意味着可以对不同的对象使用同样的操作,但它们可能会以多种形态呈现出结果。len(object)函数就体现了这一点。在C++、Java、C#这种编译型语言中,由于有编译过程,因此就鲜明地分成了运行时多态和编译时多态。运行时多态是指允许父类指针或名称来引用子类对象,或对象方法,而实际调用的方法为对象的类类型方法,这就是所谓的动态绑定。编译时多态有模板或范型、方法重载(overload)、方法重写(override)等。而Python是动态语言,动态地确定类型信息恰恰体现了多态的特征。在Python中,任何不知道对象到底是什么类型,但又需要对象做点什么的时候,都会用到多态。
* example
_metaclass_=type # 确定使用新式类
class calculator:
def count(self,args):
return 1
calc=calculator() #自定义类型
from random import choice
obj=choice(['hello,world',[1,2,3],calc]) #obj是随机返回的 类型不确定
print type(obj)
print obj.count('a') #方法多态
对于一个临时对象obj,它通过Python的随机函数取出来,不知道具体类型(是字符串、元组还是自定义类型),都可以调用count方法进行计算,至于count由谁(哪种类型)去做怎么去实现我们并不关心。
* 鸭子类型(duck typing)
_metaclass_=type # 确定使用新式类
class Duck:
def quack(self):
print "Quaaaaaack!"
def feathers(self):
print "The duck has white and gray feathers."
class Person:
def quack(self):
print "The person imitates a duck."
def feathers(self):
print "The person takes a feather from the ground and shows it."
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
in_the_forest函数而言,参数对象是一个鸭子类型,它实现了方法多态。但是实际上我们知道,从严格的抽象来讲,Person类型和Duck完全风马牛不相及。
* 运算符多态
def add(x,y):
return x+y
print add(1,2) #输出3
print add("hello,","world") #输出hello,world
print add(1,"abc") #抛出异常 TypeError: unsupported operand type(s) for +: 'int' and 'str'
Python的加法运算符是”多态“的,理论上,我们实现的add方法支持任意支持加法的对象,但是我们不用关心两个参数x和y具体是什么类型
* 运算符重载
class Vector:
def __init__(self, a, b):
self.a = a
self.b = b
def __str__(self):
return 'Vector (%d, %d)' % (self.a, self.b)
def __add__(self,other):
return Vector(self.a + other.a, self.b + other.b)
v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2
一两个示例代码当然不能从根本上说明多态。普遍认为面向对象最有价值最被低估的特征其实是多态。我们所理解的多态的实现和子类的虚函数地址绑定有关系,多态的效果其实和函数地址运行时动态绑定有关。在C++, Java, C#中实现多态的方式通常有重写和重载两种,从上面两段代码,我们其实可以分析得出Python中实现多态也可以变相理解为重写和重载。在Python中很多内置函数和运算符都是多态的。
抽象类及抽象方法
参考博客:抽象类、接口
抽象类ji
由于python 没有抽象类、接口的概念,所以要实现这种功能得abc.py 这个类库
example
from abc import ABCMeta, abstractmethod
#抽象类
class Headers(object): # class Headers(metaclass=abc.ABCMeta)
__metaclass__ = ABCMeta
def __init__(self):
self.headers = ''
@abstractmethod
def _getBaiduHeaders(self):pass
def __str__(self):
return str(self.headers)
def __repr__(self):
return repr(self.headers)
编写子类实现:
#实现类
class BaiduHeaders(Headers):
def __init__(self, url, username, password):
self.url = url
self.headers = self._getBaiduHeaders(username, password)
def _getBaiduHeaders(self, username, password):
client = GLOBAL_SUDS_CLIENT.Client(self.url)
headers = client.factory.create('ns0:AuthHeader')
headers.username = username
headers.password = password
headers.token = _baidu_headers['token']
return headers
问题:子类不实现父类的_getBaiduHeaders方法,则抛出TypeError: Can’t instantiate abstract class BaiduHeaders with abstract methods 异常
接口
父类不实现方法,子类继承实现
class IorderRepository: ##接口
def fetch_one_by(self,nid):
'''
获取单条数据的方法,所有的继承呢当前类的类必须继承
:param nid:
:return:
'''
# raise Exception('子类中必须包含该方法')
class OrderReposititory(IorderRepository): #类
def fetch_one_by(self,nid):
print(nid)
obj = OrderReposititory()
obj.fetch_one_by(1)