Python学习-08-面向对象编程进阶和异常处理

一、类的进阶

1)静态方法:

跟类没有关系了。是一个单纯的函数。调用的时候,必须用类名来调用。(台湾与大陆的关系。)相当于类下面的一个函数,跟类没有关系,不能直接调用类的参数。

静态方法:只是名义上归类管理,实际上静态方法访问不了类或实例中的任何属性。 

通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了,它与类唯一的关联就是需要通过类名来调用这个方法

 1 class Dog(object):
 2 
 3     def __init__(self,name):
 4 
 5         self.name = name
 6 
 7     @staticmethod #把eat方法变为静态方法
 8 
 9     def eat(self):
10 
11         print("%s is eating" % self.name)
12 
13 d = Dog("张三")
14 
15 d.eat()

上面的调用会出以下错误,说是eat需要一个self参数,但调用时却没有传递,没错,当eat变成静态方法后,再通过实例调用时就不会自动把实例本身当作一个参数传给self了。

TypeError: eat() missing 1 required positional argument: 'self'

想让上面的代码可以正常工作有两种办法

1. 调用时主动传递实例本身给eat方法,即d.eat(d)

2. 在eat方法中去掉self参数,但这也意味着,在eat中不能通过self.调用实例中的其它变量了

 1 class Dog(object):
 2 
 3     def __init__(self,name):
 4 
 5         self.name = name
 6 
 7     @staticmethod
 8 
 9     def eat():
10 
11         print(" is eating")
12 
13 d = Dog("张三")
14 
15 d.eat()

2)类方法:只能访问类变量,不是访问实例变量。用途无。@classmethod

 1 class Dog(object):
 2     def __init__(self,name):
 3         self.name = name
 4  
 5     @classmethod
 6     def eat(self):
 7         print("%s is eating" % self.name)
 8  
 9  
10  
11 d = Dog("张三")
12 d.eat()

执行报错如下,说Dog没有name属性,因为name是个实例变量,类方法是不能访问实例变量的

AttributeError: type object 'Dog' has no attribute 'name'

 1 class Dog(object):
 2     name = "我是类变量"
 3     def __init__(self,name):
 4         self.name = name
 5  
 6     @classmethod
 7     def eat(self):
 8         print("%s is eating" % self.name)
 9 
10 d = Dog("张三")
11 d.eat()
12   
13 #执行结果
14  
15 我是类变量 is eating
 

3)属性方法:@property。把一个方法变成静态属性。就是不能传参数。(重点)

举例:一个方法变成属性,如何修改它,如何删除它

 1 class Dog(object):
 2     '''这个类是描述狗这个对象的'''
 3     def __init__(self,name):
 4         self.name = name
 5         self.__food = None
 6     @property #attribute属性方法,把方法变成了属性,无法调用参数
 7     def eat(self):
 8         print("%s is eating %s" %(self.name,self.__food))
 9     @eat.setter#修改属性方法,通过self.__food,修改属性方法
10     def eat(self,food):
11         print("set to food:",food)
12         self.__food = food
13     @eat.deleter#输出属性方法
14     def eat(self):
15         del self.__food
16         print("删完了")
17     def talk(self):
18         print("%s is talking"% self.name)
19 d = Dog("张三")
20 d.eat
21 d.eat=("包子")
22 d.eat
23 del d.eat

属性方法的应用:

既然想要静态变量,那直接定义成一个静态变量不就得了么?well, 以后你会需到很多场景是不能简单通过定义静态属性来实现的,比如,你想知道一个航班当前的状态,是到达了、延迟了、取消了、还是已经飞走了(第三方公司),想知道这种状态你必须经历以下几步:

1. 连接航空公司API查询

2. 对查询结果进行解析

3. 返回结果给你的用户

因此这个status属性的值是一系列动作后才得到的结果,所以你每次调用时,其实它都要经过一系列的动作才返回你结果,但这些动作过程不需要用户关心,用户只需要调用这个属性就可以,明白了么?

 1 class Flight(object):                    
 2 
 3     def __init__(self,name):
 4 
 5         self.flight_name = name
 6 
 7     def checking_status(self):
 8 
 9         print("checking flight %s status " % self.flight_name)
10 
11         return  1
12 
13     @property
14 
15     def flight_status(self):
16 
17         status = self.checking_status()
18 
19         if status == 0 :
20 
21             print("flight got canceled...")
22 
23         elif status == 1 :
24 
25             print("flight is arrived...")
26 
27         elif status == 2:
28 
29             print("flight has departured already...")
30 
31         else:
32 
33             print("cannot confirm the flight status...,please check later")
34 
35 f = Flight("CA980")
36 
37 f.flight_status

cool , 那现在我只能查询航班状态,既然这个flight_status已经是个属性了,那我能否给它赋值呢?试试吧

1 f = Flight("CA980")
2 f.flight_status
3 f.flight_status =  2

输出, 说不能更改这个属性,我擦。。。。,怎么办怎么办。。。

checking flight CA980 status

flight is arrived... 

    f.flight_status =  2

AttributeError: can't set attribute

当然可以改,不过需要通过@proert

y.setter装饰器再装饰一下,此时 你需要写一个新方法, 对这个flight_status进行更改。

 1  class Flight(object):
 2     def __init__(self,name):
 3         self.flight_name = name
 4     def checking_status(self):
 5         print("checking flight %s status " % self.flight_name)
 6         return  1
 7     @property
 8     def flight_status(self):
 9         status = self.checking_status()
10         if status == 0 :
11             print("flight got canceled...")
12         elif status == 1 :
13             print("flight is arrived...")
14         elif status == 2:
15             print("flight has departured already...")
16         else:
17             print("cannot confirm the flight status...,please check later")  
18 
19     @flight_status.setter #修改
20     def flight_status(self,status):
21         status_dic = {
22 : "canceled",
23 :"arrived",
24 : "departured"
25         }
26         print("\033[31;1mHas changed the flight status to \033[0m",status_dic.get(status) )
27     @flight_status.deleter  #删除
28     def flight_status(self):
29         print("status got removed...")
30 f = Flight("CA980")
31 f.flight_status
32 f.flight_status =  2 #触发@flight_status.setter
33 del f.flight_status #触发@flight_status.deleter

注意以上代码里还写了一个@flight_status.deleter, 是允许可以将这个属性删除。

 4)类的特殊成员方法:

1. __doc__  表示类的描述信息

1 class Foo:
2 
3     """ 描述类信息,这是用于看片的神奇 """
4 
5     def func(self):
6 
7         pass
8 
9 print Foo.__doc__#输出:类的描述信息

2.  __init__ 构造方法,通过类创建对象时,自动触发执行。

3. __del__ 析构方法,当对象在内存中被释放时,自动触发执行。

注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的

4. __call__ 对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()

5. __dict__ 查看类或对象中的所有成员  

6.__str__ 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。

 1 class Dog(object):
 2     '''这个类是描述狗这个对象的'''
 3     def __init__(self,name):
 4         self.name = name
 5     def eat(self,food):
 6         print("%s is eating %s" %(self.name,food))
 7     def talk(self):
 8         print("%s is talking"% self.name)
 9     def __call__(self, *args, **kwargs):
10         print("running call",args,kwargs)
11     def __str__(self):#打印对象的时候,输入该方法的返回值。默认是内存地址
12         return "<obj:%s>" %self.name
13 
14 #print(Dog.__doc__) #打印描述信息
15 #测试__call__方法
16 d=Dog("张三")
17 #d(1,2,3,name=123)
18 #上面的可以换为:
19 #Dog("张三")()
20 #print(Dog.__dict__) #打印类里的所有属性,不包括实例属性
21 #print(d.__dict__) #打印所有实例属性,不包括类属性

7. __getitem__、__setitem__、__delitem__

用于索引操作,如字典。以上分别表示获取、设置、删除数据

 1 class Foo(object):
 2 
 3     def __getitem__(self, key):
 4 
 5         print('__getitem__',key)
 6 
 7     def __setitem__(self, key, value):
 8 
 9         print('__setitem__',key,value)
10 
11     def __delitem__(self, key):
12 
13         print('__delitem__',key)
14 
15 obj = Foo()
16 
17 result = obj['k1']      # 自动触发执行 __getitem__
18 
19 obj['k2'] = 'alex'   # 自动触发执行 __setitem__
20 
21 del obj['k1']  

8.__new__    、 __metaclass__ (底层框架要用) 

 1 class Foo(object):
 2 
 3      def __init__(self, name):
 4 
 5          self.name = name
 6 
 7 f = Foo("alex")
 8 
 9 print(type(f))
10 
11 print(type(Foo))

上述代码中,f是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。

如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是通过执行某个类的 构造方法 创建。

print type(f) # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类创建

print type(Foo) # 输出:<type 'type'>              表示,Foo类对象由 type 类创建

所以,f对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。

那么,创建类就可以有两种方式:

a). 普通方式

1 class Foo(object): 
2 
3     def func(self):
4 
5         print 'hello alex'

b). 特殊方式

 1 def func(self):
 2 
 3     print 'hello wupeiqi' 
 4 
 5 Foo = type('Foo',(object,), {'func': func})
 6 
 7 #type第一个参数:类名
 8 
 9 #type第二个参数:当前类的基类
10 
11 #type第三个参数:类的成员

a)      和b) 两个都是一样的,都是实现创建类的。

举例代码

 1 def func(self):
 2 
 3     print("hello %s"%self.name)
 4 
 5 def __init__(self,name,age):
 6 
 7     self.name = name
 8 
 9     self.age = age
10 
11 Foo = type('Foo',(object,),{'func':func,'__init__':__init__})
12 
13 f = Foo("jack",22)
14 
15 f.func()

So ,记住,类是由 type 类实例化产生

那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?

答:类中有一个属性 __metaclass__,其用来表示该类由谁来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看类创建的过程。 

参考代码;

 1 class MyType(type):
 2 
 3     def __init__(self, what, bases=None, dict=None):
 4 
 5         print("--MyType init---")
 6 
 7         super(MyType, self).__init__(what, bases, dict)
 8 
 9     def __call__(self, *args, **kwargs):
10 
11         print("--MyType call---")
12 
13         obj = self.__new__(self, *args, **kwargs)
14 
15         obj.data = {"name":111}
16 
17         self.__init__(obj, *args, **kwargs)
18 
19 class Foo(object):
20 
21     __metaclass__ = MyType
22 
23     def __init__(self, name):
24 
25         self.name = name
26 
27         print("Foo ---init__")
28 
29     def __new__(cls, *args, **kwargs):
30 
31         print("Foo --new--")
32 
33         #print(object.__new__(cls))
34 
35         return object.__new__(cls) #继承父亲的__new__方法
36 
37 # 第一阶段:解释器从上到下执行代码创建Foo类
38 
39 # 第二阶段:通过Foo类创建obj对象
40 
41 obj = Foo("Alex")
42 
43 print(obj.name)
View Code

new是用来创建实例的。

类的生成 调用 顺序依次是 __new__ --> __init__ --> __call__

metaclass 详解文章:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python 得票最高那个答案写的非常好

 9、反射:通过字符串映射或修改程序运行时的状态、属性、方法, 有以下4个方法:

hasattr():第一个是实例化名字,第二个是字符串形式  反射 判断一个对象里是否有字符串对应的方法

getattr():如果是方法,获取并调用,根据字符串获取obj对象里的对应的方法的内存地址。

            如果是动态属性,则直接返回属性的值

setattr():通过字符串设置属性。setattr(obj,’y’,’z’)  等价于x.y=z

delattr():删除属性或者方法

 1 def bulk(self):
 2     print("%s is yelling...." %self.name)
 3 
 4 class Dog(object):
 5     def __init__(self,name):
 6         self.name = name
 7 
 8     def eat(self,food):
 9         print("%s is eating..."%self.name,food)
10 
11 d = Dog("李四")
12 choice = input(">>:").strip()
13 
14 if hasattr(d,choice):#第一个是实例化名字,第二个是字符串形式  反射 判断一个对象里是否有字符串对应的方法
15 
16    getattr(d,choice)#如果是方法,获取并调用,根据字符串获取obj对象里的对应的方法的内存地址。
17                     #如果是动态属性,则直接返回属性的值
18 Delattr(d,choice)#删除属性或者方法
19 
20 else:
21 setattr(d,choice,bulk) #d.talk = bulk 通过字符串设置属性。
22 #setattr(obj,’y’,’z’)  等价于x.y=z。上面的choice是talk,d.talk=bulk
23 #d.talk(d)
24 
25     func = getattr(d, choice)
26 
27     func(d) 

再次举例:

 1 class Foo(object):
 2 
 3     def __init__(self):
 4 
 5         self.name = 'wupeiqi'
 6 
 7      def func(self):
 8 
 9         return 'func'
10 
11  obj = Foo()
12 
13  
14 
15 # #### 检查是否含有成员 ####
16 
17 hasattr(obj, 'name')
18 
19 hasattr(obj, 'func')
20 
21  
22 
23 # #### 获取成员 ####
24 
25 getattr(obj, 'name')
26 
27 getattr(obj, 'func')
28 
29  
30 
31 # #### 设置成员 ####
32 
33 setattr(obj, 'age', 18)
34 
35 setattr(obj, 'show', lambda num: num + 1)
36 
37  
38 
39 # #### 删除成员 ####
40 
41 delattr(obj, 'name')
42 
43 delattr(obj, 'func')
View Code

异常处理:

通过提前预判错误,打印提示

1 data={}
2 
3 try:
4 
5     data['name']
6 
7 except KeyError as e:
8 
9     print("没有这个Key",e)

写程序时需要考虑到try代码块中可能出现的任意异常,可以这样写:多个预判错误:

在Python执行时,当遇到第一个错误就会停止。

 1 data={}
 2 
 3 name=[]
 4 
 5 try:
 6 
 7     name[2]
 8 
 9     data['name']
10 
11 except KeyError as e:
12 
13     print("没有这个Key",e)
14 
15 except IndexError as e:
16 
17     print("列表操作错误",e)

还可以这样写:应用于无论什么错误类型,处理结果一样的情况。

 1 data={}
 2 
 3 name=[]
 4 
 5 try:
 6 
 7     name[2]
 8 
 9     data['name']
10 
11 except (KeyError,IndexError) as e:
12 
13     print("没有这个Key",e)

万能异常 在python的异常中,有一个万能异常:Exception,他可以捕获任意异常,即(万能错误类型)

 1 data={}
 2 
 3 name=[]
 4 
 5 try:
 6 
 7     name[2]
 8 
 9     data['name']
10 
11 except Exception as e: # Exception 任意错误,一般不用不容易调试判断
12 
13     print("没有这个Key",e)

windows中的未知错误?怎么做到的呢?我们用Python试一试?

 1 data={}
 2 
 3 name=[]
 4 
 5 try:
 6 
 7     open("a.txt")
 8 
 9 except KeyError as e:
10 
11     print("没有这个Key",e)
12 
13 except IndexError as e:
14 
15     print("列表操作错误",e)
16 
17 except Exception as e:
18 
19 print("未知错误",e)

else 和 finally 用法:

 1 data={}
 2 
 3 name=[]
 4 
 5 try:
 6 
 7    #open("a.txt")
 8 
 9    a=1
10 
11 except KeyError as e:
12 
13     print("没有这个Key",e)
14 
15 except IndexError as e:
16 
17     print("列表操作错误",e)
18 
19 except Exception as e:
20 
21     print("未知错误",e)
22 
23 else:
24 
25     print("一切正常")
26 
27 finally:
28 
29     print("无论有没有错,都会执行")

注意:

1)Python3中的as在Python2.7中用的是‘,’。

2)一些抓不到的错误,比如说缩进、语法导致的错误。IndentationError: unexpected indent

常用的错误类型:

 1 AttributeError 试图访问一个对象没有的树形,比如foo.x,但是foo没有属性x
 2 IOError 输入/输出异常;基本上是无法打开文件
 3 ImportError 无法引入模块或包;基本上是路径问题或名称错误
 4 IndentationError 语法错误(的子类) ;代码没有正确对齐
 5 IndexError 下标索引超出序列边界,比如当x只有三个元素,却试图访问x[5]
 6 KeyError 试图访问字典里不存在的键
 7 KeyboardInterrupt Ctrl+C被按下
 8 NameError 使用一个还未被赋予对象的变量
 9 SyntaxError Python代码非法,代码不能编译
10 TypeError 传入对象类型与要求的不符合
11 UnboundLocalError 试图访问一个还未被设置的局部变量,基本上是由于另有一个同名的全局变量,导致你以为正在访问它
12 ValueError 传入一个调用者不期望的值,即使值的类型是正确的
View Code

更多的错误

 1 ArithmeticError
 2 
 3 AssertionError
 4 
 5 AttributeError
 6 
 7 BaseException
 8 
 9 BufferError
10 
11 BytesWarning
12 
13 DeprecationWarning
14 
15 EnvironmentError
16 
17 EOFError
18 
19 Exception
20 
21 FloatingPointError
22 
23 FutureWarning
24 
25 GeneratorExit
26 
27 ImportError
28 
29 ImportWarning
30 
31 IndentationError
32 
33 IndexError
34 
35 IOError
36 
37 KeyboardInterrupt
38 
39 KeyError
40 
41 LookupError
42 
43 MemoryError
44 
45 NameError
46 
47 NotImplementedError
48 
49 OSError
50 
51 OverflowError
52 
53 PendingDeprecationWarning
54 
55 ReferenceError
56 
57 RuntimeError
58 
59 RuntimeWarning
60 
61 StandardError
62 
63 StopIteration
64 
65 SyntaxError
66 
67 SyntaxWarning
68 
69 SystemError
70 
71 SystemExit
72 
73 TabError
74 
75 TypeError
76 
77 UnboundLocalError
78 
79 UnicodeDecodeError
80 
81 UnicodeEncodeError
82 
83 UnicodeError
84 
85 UnicodeTranslateError
86 
87 UnicodeWarning
88 
89 UserWarning
90 
91 ValueError
92 
93 Warning
94 
95 ZeroDivisionError
View Code

主动触发异常:

1 try:
2 
3     raise Exception('错误了。。。')
4 
5 except Exception,e:
6 
7     print e

自定义异常:注意不要覆盖已经存在的异常。

 1 class AlexError(Exception):
 2 
 3     def __init__(self, msg):
 4 
 5         self.message = msg
 6 
 7 try:
 8 
 9     raise AlexError('数据库连不上')
10 
11 except AlexError as e:
12 
13     print(e)

 参考文献:

http://www.cnblogs.com/alex3714/articles/5213184.html

 http://www.cnblogs.com/wupeiqi/articles/5017742.html   

posted on 2017-07-13 17:56  a阿军  阅读(308)  评论(0编辑  收藏  举报

导航