创建类和对象
面向对象编程是一种编程方式,此编程方式的落地需要使用 “类” 和 “对象” 来实现,所以,面向对象编程其实就是对 “类” 和 “对象” 的使用。
类就是一个模板,模板里可以包含多个函数,函数里实现一些功能
对象则是根据模板创建的实例,通过实例对象可以执行类中的函数
class是关键字,表示类创建对象,类名称后加括号即可
注:类中的函数第一个参数必须是self(详细见:类的三大特性之封装)
类中定义的函数叫做 “方法”
#创建类
class Foo:
def Bar(self):
print("Bar")
def Hello(self,name):
print("I love %s" %name)
#根据Foo类创建obj对象
obj = Foo()
obj.Bar() #执行Bar方法
obj.Hello('python') #执行Hello方法
self参数相当于php面向对象中的$this,谁调用它就指向谁
面向对象的三大特性
面向对象的三大特性是指:封装、继承和多态。
一、封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。
所以,在使用面向对象的封装特性时,需要:
- 将内容封装到某处
- 从某处调用被封装的内容
第一步:将内容封装到某处
class Foo:
def __init__(self,name,age):##__init__称为构造方法,根据类创建对象时自动执行
self.name = name
self.age = age
obj1 = Foo('poe',21)##poe and 21分别封装到obj1 self的name and age属性中
print(obj1.name,obj1.age)
obj2 = Foo('jet',22)##jet and 22分别封装到obj2 self的name and age属性中
print(obj2.name,obj2.age)
self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
1、通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名
class Foo:
def __init__(self,name,age):##__init__称为构造方法,根据类创建对象时自动执行
self.name = name
self.age = age
obj1 = Foo('poe',21)##poe and 21分别封装到obj1 self的name and age属性中
print(obj1.name,obj1.age)# 直接调用obj1对象的name和age属性
obj2 = Foo('jet',22)##jet and 22分别封装到obj2 self的name and age属性中
print(obj2.name,obj2.age)# 直接调用obj2对象的name和age属性
2、通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
class Foo:
def __init__(self,name,age):##__init__称为构造方法,根据类创建对象时自动执行
self.name = name
self.age = age
def detail(self):
print(self.name)
print(self.age) obj1 = Foo("poe",21)
obj1.detail()# Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 poe ;self.age 是 21
obj2 = Foo("jet",22)
obj2.detail()# Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 jet ;self.age 是 22
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
二、继承
继承,面向对象中的继承和现实生活中的继承相同,即:子可以继承父的内容。
class Animal:
def eat(self):
print("%s eat" %self.name)
def drink(self):
print("%s drink" %self.name) class Cat(Animal):
def __init__(self,name):
self.name = name
self.breed = 'cat'
def cry(self):
print("cry")
class Dog(Animal):
def __init__(self,name):
self.name = name
self.breed = 'dog'
def cry(self):
print("cry") c1 = Cat('little cat')
c1.eat()
c2 = Cat('big cat')
c2.eat() d1 = Dog('little black')
d1.eat()
所以,对于面向对象的继承来说,其实就是将多个类共有的方法提取到父类中,子类仅需继承父类而不必一一实现每个方法。
注:除了子类和父类的称谓,你可能看到过 派生类 和 基类 ,他们与子类和父类只是叫法不同而已。
那么问题又来了,多继承呢?
- 是否可以继承多个类
- 如果继承的多个类每个类中都定了相同的函数,那么那一个会被使用呢?
1、Python的类可以继承多个类,Java和C#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式如下图:
假设G类在继承时C类在前,F类在后,如:G(C,F)
黑色的箭头表示继承关系:B类继承A类,C类继承B类,D类继承A类,F类继承D类,G类继承C类与F类
橘黄色箭头表示查找顺序,上图中的查找顺序为:G->C->B->F->D->A
如果A类没有的继承关系,那么查找顺序如下图:
class Animal:
def eat(self):
print("%s eat" %self.name)
def drink(self):
print("%s drink" %self.name)
def piao(self):
print("Animal piao") class Uncle:
def piao(self):
print('Uncle piao')
def du(self):
print('du') class Cat(Uncle,Animal):#继承了Uncle类和Animal类
def __init__(self,name):
self.name = name
self.breed = 'cat'
def cry(self):
print("cry") c1 = Cat('little cat')
c1.piao()
c2 = Cat('big cat')
c2.piao()
上面的Cat类继承了Unlcle类和Animal类,两个类中又都有piao()方法,那么在调用该方法的时候,是按什么顺序执行的呢?执行哪个类里面的piao()方法呢?
1:如果Cat类自身中有piao()方法,那就在调用piao()方法时先执行自身类中的piao()方法
2:如果Cat类自身中没有piao()方法,像上面的代码,就按继承时的顺序执行,class Cat(Uncle,Animal),这两个类哪个写在前面,就优先执行哪个类中的piao()方法
三、多态
Pyhon不支持Java和C#这一类强类型语言中多态的写法,但是原生多态,其Python崇尚“鸭子类型”。
class F1:
pass
class S1(F1):
def show(self):
print('S1.show')
class S2(F1):
def show(self):
print("S2.show") def Func(obj):
print(obj.show())
s1_obj = S1()
Func(s1_obj) s2_obj = S2()
Func(s2_obj)
扩展:
重载:函数名相同,参数个数不同(python不支持)
重写:派生类中重新实现基类中的方法
接口:python中没有接口这一说
类和对象在内存中是如何保存?
类以及类中的方法在内存中只有一份,而根据类创建的每一个对象都在内存中需要存一份,大致如下图:
如上图所示,根据类创建对象时,对象中除了封装 name 和 age 的值之外,还会保存一个类对象指针,该值指向当前对象的类。
当通过 obj1 执行 【方法一】 时,过程如下:
- 根据当前对象中的 类对象指针 找到类中的方法
- 将对象 obj1 当作参数传给 方法的第一个参数 self
执行基类构造方法:
class A:
def __init__(self):
print('A contruct')
class B(A):
def __init__(self):
print('B contruct')
super(B,self).__init__()#推荐使用此方法
#A.__init__(self)#此方法也可以执行基类的构造方法
obj = B()
利用反射操作对象成员
class Foo:
def __init__(self,name):
self.name = name
def show(self):
print('show') obj = Foo('poe')
#反射:类,只能找到类里的成员
#反射:对象,既可以找对象,也可以找类的成员
print(hasattr(Foo,'show'))
print(hasattr(obj,'name'))
##commons.py文件
class Foo:
def __init__(self,name):
self.name = name
gender = 'male'
def show(self):
print('show') ##index.py文件 #导入模块
m = __import__('commons',fromlist = True)
#找到模块中的类
class_name = getattr(m,'Foo')
#根据类创建对象
obj = class_name("bruce")
#利用对象去找name的值
val = getattr(obj,'name')
print(val)
一个类中存在普通字段,静态字段,普通方法,静态方法,类方法那么在访问这些成员的时候优先选择自己的成员自己去访问:
通过类去访问的有:静态字段,静态方法,类方法
通过对象去访问的有:普通字段,普通方法
但请注意:这么做只是为了代码的规范,这四个成员利用对象都是可以访问到的
class Province:
#静态字段,保存类中,内存中只有一个
country = "China"
def __init__(self,name):
#普通字段,保存对象中!有几个对象,就有几个字段
self.name = name
#普通方法,保存在类中
def show(self):
print('show')
#静态方法,静态方法中不需要self参数
@staticmethod
def xo(arg1,arg2):
return arg1+arg2
#类方法,类方法也不需要self参数,但要有一个cls参数(class)
@classmethod
def xxoo(cls):
print("xxoo",cls) hunan = Province('hunan')
hunan.show()
print(hunan.xo(3,4))
print(Province.country)
print(Province.xo(1,2))
Province.xxoo()#调用类方法,cls参数会自动获得当前类的类名
类的特性:
class Province:
def start(self):
temp = "%s is hero" %self.name
return temp
#特性,将方法伪造成一种字段
@property
def end(self):
temp = "%s is hero" %self.name
return temp obj = Province('poe')
print(obj.start())
print(obj.end)#访问特性时,后面不需要加括号,所以也就无法传递参数
类的特性默认情况下是无法像普通字段那样在类外进行重新赋值,如果需要对特性进行重新赋值,需要用到一个装饰器:
class Province:
#静态字段,保存类中,内存中只有一个
country = "China"
def __init__(self,name):
#普通字段,保存对象中!有几个对象,就有几个字段
self.name = name
#普通方法,保存在类中
def show(self):
print('show')
#静态方法,静态方法中不需要self参数
@staticmethod
def xo(arg1,arg2):
return arg1+arg2
#类方法,类方法也不需要self参数,但要有一个cls参数(class)
@classmethod
def xxoo(cls):
print("xxoo",cls) def start(self):
temp = "%s is hero" %self.name
return temp
#特性,将方法伪造成一种字段
@property
def end(self):
temp = "%s is hero" %self.name
return temp
@end.setter
def end(self,value):
print(value)
self.name = value obj = Province('poe')
p = obj.end
print(p)
#设置特性
obj.end = "bruce"
print(obj.end)
################################################
poe is hero
bruce
bruce is hero
快速判断成员由类执行还是对象执行:
有self的,对象调用,无self的类调用
成员修饰符
对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能访问
私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)
class A:
def __init__(self):
self.name = 'public'
self.__nick = 'private' obj = A()
print(obj.name)
print(obj.__nick)
##################################################
public
Traceback (most recent call last):
File "index.py", line 11, in <module>
print(obj.__nick)
AttributeError: 'A' object has no attribute '__nick'
如何访问类中的静态成员:两种方法
法一:在类中添加一个方法
class A:
def __init__(self):
self.name = 'public'
self.__nick = 'private' def fetch(self):
print(self.__nick) obj = A()
obj.fetch()
法二:使用python的特定语法(此方法不推荐使用)
class A:
def __init__(self):
self.name = 'public'
self.__nick = 'private' obj = A()
print(obj._A__nick)#A前面一个下划线
类的特殊成员
1:__init__:构造方法
构造方法,通过类创建对象时,自动触发执行。
class Foo: def __init__(self, name):
self.name = name
self.age = 18 obj = Foo('poe') # 自动执行类中的 __init__ 方法
2:__del__:析构方法
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class Foo: def __del__(self):
pass
3:__call__方法用于实例自身的调用
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo:
def __init__(self):
print('init')
# __call__方法用于实例自身的调用
def __call__(self,*args,**kwargs):
print('call')
return 1
# obj = Foo()
# obj()
r = Foo()()
print(r)
##################################################
init
call
1
4:__getitem__ , __setitem__ , __delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo: def __getitem__(self,item):
print(item)
def __setitem__(self,key,value):
print(key,value)
def __delitem__(self,key):
print(key) obj = Foo()
obj['kk']
obj['aa'] = 123
del obj['kk']
obj['kk']
5:__dict__查询类中的成员,对象中的成员
class Foo:
def __init__(self):
self.name = 'poe'
def __call__(self):
print('call')
def __getitem__(self,item):
print(item)
def __setitem__(self,key,value):
print(key,value)
def __delitem__(self,key):
print(key) obj = Foo() #__init__
print(obj.__dict__)
print(Foo.__dict__)
6: __iter__
用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 __iter__
class Foo:
def __iter__(self):
yield 1
yield 2
yield 3
obj = Foo()
for i in obj:
print(i)
7:__new__ 和 __metaclass__
阅读以下代码:
class Foo(object): def __init__(self):
pass obj = Foo() # obj是通过Foo类实例化的对象
上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。
如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是通过执行某个类的 构造方法 创建。
print type(obj) # 输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建
print type(Foo) # 输出:<type 'type'> 表示,Foo类对象由 type 类创建
所以,obj对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。
那么,创建类就可以有两种方式:
a). 普通方式
class Foo(object): def func(self):
print('hello python')
b).特殊方式(type类的构造函数)
def func(self):
print('hello python') Foo = type('Foo',(object,),{'func':func})
print(Foo,type(Foo))
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员
那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看 类 创建的过程。
8:__doc__
表示类的描述信息
class Foo:
""" 描述类信息,这是用于看片的神奇 """ def func(self):
pass print Foo.__doc__
#输出:类的描述信息
9:__str__
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo: def __str__(self):
return('poe') obj = Foo()
print(obj)
# 输出:poe
10:__module__ 和 __class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
# lib/commons.py
class C:
def __init__(self):
self.name = 'poe'
# index.py
from lib.commons import C obj = C()
print(obj.__module__)# 输出 lib.commons,即:输出模块
print(obj.__class__)# 输出 lib.commons.C,即:输出类
异常处理
1、异常基础
在编程过程中为了增加友好性,在程序出现bug时一般不会将错误信息显示给用户,而是显示一个提示的页面,通俗来说就是不让用户看见大黄页!!!
try:
pass
except Exception,ex:
pass
需求:将用户输入的两个数字相加
while True:
num1 = raw_input('num1:')
num2 = raw_input('num2:')
try:
num1 = int(num1)
num2 = int(num2)
result = num1 + num2
except Exception, e:
print '出现异常,信息如下:'
print e
2、异常种类
python中的异常种类非常多,每个异常专门用于处理某一项异常!!!
AttributeError 试图访问一个对象没有的属性,比如foo.x,但是foo没有属性x
IOError 输入/输出异常;基本上是无法打开文件
ImportError 无法引入模块或包;基本上是路径问题或名称错误
IndentationError 语法错误(的子类) ;代码没有正确对齐
IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
KeyError 试图访问字典里不存在的键
KeyboardInterrupt Ctrl+C被按下
NameError 使用一个还未被赋予对象的变量
SyntaxError Python代码非法,代码不能编译(个人认为这是语法错误,写错了)
TypeError 传入对象类型与要求的不符合
UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,
导致你以为正在访问它
ValueError 传入一个调用者不期望的值,即使值的类型是正确的
常用异常
ArithmeticError
AssertionError
AttributeError
BaseException
BufferError
BytesWarning
DeprecationWarning
EnvironmentError
EOFError
Exception
FloatingPointError
FutureWarning
GeneratorExit
ImportError
ImportWarning
IndentationError
IndexError
IOError
KeyboardInterrupt
KeyError
LookupError
MemoryError
NameError
NotImplementedError
OSError
OverflowError
PendingDeprecationWarning
ReferenceError
RuntimeError
RuntimeWarning
StandardError
StopIteration
SyntaxError
SyntaxWarning
SystemError
SystemExit
TabError
TypeError
UnboundLocalError
UnicodeDecodeError
UnicodeEncodeError
UnicodeError
UnicodeTranslateError
UnicodeWarning
UserWarning
ValueError
Warning
ZeroDivisionError 更多异常
更多异常
dic = ["Jet", 'Jacky']
try:
dic[10]
except IndexError, e:
print e
实例:IndexError
s1 = 'hello'
try:
int(s1)
except ValueError, e:
print e
ValueError
对于上述实例,异常类只能用来处理指定的异常情况,如果非指定异常则无法处理。
# 未捕获到异常,程序直接报错 s1 = 'hello'
try:
int(s1)
except IndexError,e:
print e
所以,写程序时需要考虑到try代码块中可能出现的任意异常,可以这样写:
s1 = 'hello'
try:
int(s1)
except IndexError,e:
print e
except KeyError,e:
print e
except ValueError,e:
print e
万能异常 在python的异常中,有一个万能异常:Exception,他可以捕获任意异常,即:
s1 = 'hello'
try:
int(s1)
except Exception,e:
print e
接下来你可能要问了,既然有这个万能异常,其他异常是不是就可以忽略了!
答:当然不是,对于特殊处理或提醒的异常需要先定义,最后定义Exception来确保程序正常运行。
s1 = 'hello'
try:
int(s1)
except KeyError,e:
print '键错误'
except IndexError,e:
print '索引错误'
except Exception, e:
print '错误'
3、异常其他结构
try:
# 主代码块
pass
except KeyError,e:
# 异常时,执行该块
pass
else:
# 主代码块执行完,执行该块
pass
finally:
# 无论异常与否,最终执行该块
pass
4、主动触发异常
try:
raise Exception('错误了。。。')
except Exception,e:
print e
5、自定义异常
class MyException(Exception): def __init__(self, msg):
self.message = msg def __str__(self):
return self.message try:
raise MyException('我的异常')
except MyException,e:
print e
6、断言
# assert 条件 assert 1 == 1 assert 1 == 2