萌小帅 一路向前

要有朴素的生活和遥远的梦想,不管明天天寒地冻,路遥马亡...^-^...

python 面向对象进阶

CONTENTS

  • 面向对象进阶语法内容:
    •  经典类与新式类
    •  静态方法:@staticmethod
    •  类方法: @classmethod
    •  属性方法:@property
    • 类的特殊成员方法
    • 反射
  • 异常处理
  • 动态模块导入

面向对象进阶语法

经典类与新式类的爱恨情仇

经典类与新式类形式上的区别:

1 # 经典类
2 class Dog:
3     pass
4 
5 # 新式类
6 class Cat(object):
7     pass

从形式上看,新式类与经典类只是在创建时多了一个object的声明。但实际上,经典类与新式类有很多方面的不同:

1.  类的继承策略(体现在多继承)

  • 横向继承(广度优先)
  • 纵向继承(深度优先) 

假设有A, B, C, D四个类,B, C继承A, D继承B和C,如下图所示。横向继承,也就是广度优先的继承顺序D-B-C-A;纵向继承,也就是深度优先的继承顺序D-B-A。

在python2.x中,经典类利用的是深度优先的继承策略;新式类利用的是广度优先的继承策略。

而在python3.x中,经典类和新式类均利用的是广度优先的继承策略。

 

在python3.6中执行下述代码,可以看出多继承关于构造函数,方法的继承顺序,也可以调换B,C的继承顺序。简而言之,在多继承的过程中,只是按继承顺序继承第一个有相应的构造函数或者方法,搜索的顺序遵循广度优先的继承策略。

 1 class A(object):
 2     def __init__(self):
 3         self.n = "a"
 4         print("A class")
 5     def fun(self):
 6         print("in the fun A")
 7 
 8 class B(A):
 9     # def __init__(self):
10     #     self.n = "b"
11     # def fun(self):
12     #     print("in the fun B")
13     pass
14 
15 class C(A):
16     # def __init__(self):
17     #     self.n = "c"
18     # def fun(self):
19     #     print("in the fun C")
20     pass
21 class D(B,C):
22     pass
23 d = D()
24 print(d.n)
25 d.fun()

 2.  新式类增添了一些新的类的特殊成员方法(详见类的特殊成员方法)。

静态方法

 通过@staticmethod 装饰器可以把类中的一个方法变为静态方法。静态方法和原来的方法有什么不同呢?普通的方法通过类的实例化便可以直接访问,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量,也就是说,静态方法实际上已经和类没有什么关系,只是名义上还需要用类的实例来调用。对于下述代码,eat方法中的self并不能直接自动传入d的实例化,若还想实现self.name传参,需要手动将实例化d传入,即d.eat(d)

 1 class Dog(object):
 2     # 类方法使用
 3     # name = "huazai"
 4     def __init__(self, name):
 5         self.name = name
 6         self.__food = None
 7     # 此时可以调用,因为是类的方法,可以通过实例调用
 8     # def eat(self, food):
 9     #     print("%s is eating %s" %(self.name, food))
10 
11     # 加静态方法后
12     @staticmethod    # 静态方法
13     def eat(self):
14         print("%s is eating %s"% (self.name,'dd'))
15 d = Dog("erha")
16 d.eat(d)

类方法

 通过@classmethod 装饰器可以把类中的一个方法变为类方法。类方法的不同在于,其只能访问类变量,不能访问实例变量

 执行下述两段代码,第一段代码中直接调用加了类方法的eat,会出现AttributeError,显示Dog中没有name这个属性,也就是说,此时的类方法self已经不能再访问实例变量self.name

 1 class Dog(object):
 2     # 类方法使用
 3     # name = "labuladuo"
 4     def __init__(self, name):
 5         self.name = name
 6         self.__food = None
 7     # 加类方法后
 8     @classmethod
 9     def eat(self):
10         print("%s is eating %s"%(self.name, 'dd'))
11 
12 d = Dog("erha")
13 d.eat()

第二段代码:在第三行声明类变量name后,便可以执行。

 1 class Dog(object):
 2     # 类方法使用
 3     name = "labuladuo"    #类方法只能调用类变量,就是此处的name
 4     def __init__(self, name):
 5         self.name = name
 6         self.__food = None
 7     # 加类方法后
 8     @classmethod
 9     def eat(self):
10         print("%s is eating %s"%(self.name, 'dd'))
11 
12 d = Dog("erha")
13 d.eat()

 属性方法

通过@property 装饰器可以把类中的一个方法变为属性方法,也就是把方法变成属性。变成属性后,在调用时不用加括号。  下面是一个简单的例子,注意在调用属性方法eat时,如第18行所示。              

 1 class Dog(object):
 2     # 类方法使用
 3     name = "labuladuo"
 4     def __init__(self, name):
 5         self.name = name
 6         self.__food = None
 7 
 8     # 加属性方法1
 9     @property
10     def eat(self):
11         print("%s is eating %s"%(self.name, 'dd'))
12 
13 d = Dog("erha")
14 # 属性方法
15 # 按第一行代码执行,会出现 nonetype object is not callable的错误
16 # 按第二行代码运行就可以了, 说明该装饰器property是将方法变成一个静态属性
17 # d.eat()
18 d.eat

但是,如果按照调用属性的方式调用一个方法,会存在一个问题:即没有办法给这个属性方法传入或删除参数

解决的办法是:利用两个装饰器@eat.setter,@eat.deleter对同名参数进行装饰,如下述代码:

 1 class Dog(object):
 2     # 类方法使用
 3     name = "labuladuo"
 4     def __init__(self, name):
 5         self.name = name
 6         self.__food = None    # 私有属性 __表示私有
 7     # 加属性方法2   想传参数
 8     @property
 9     def eat(self):
10         print("%s is eating %s"%(self.name,self.__food))
11     @eat.setter
12     def eat(self,food):
13         self.__food = food    # 通过属性来赋值,完善功能
14         print("set to food" ,food)
15 
16     @eat.deleter
17     def eat(self):
18         del self.__food
19         print("删除完成")
20 
21 d = Dog("erha")
22 d.eat = "baozi"  # 在有@eat.setter装饰器修饰时,此时由于已经变为属性,可以直接赋值 否则会出 can't set attribute 的错误
23 d.eat
24
25 del d.eat # 在没有加@eat.deleter时,是不能直接删的,虽然它是属性 会报can't delete attribute 的错误

 类的特殊成员方法

 1. __doc__  输出关于类的描述信息

1 class Dog(object):
2     '''关于狗的一个类'''
3     def __init__(self, name, age):
4         self.name = name
5         self.age = age
6     def bark(self):
7         print("一只名叫%s的狗正在barking!"%self.name)
8 print(Dog.__doc__)

在写类时,一般会在类下写上关于这个类的描述信息,增加代码的可读性。执行上述代码,会打印出“关于狗的一个类”的字样,即类的描述信息

2. __module__ 和 __class__

其中,__module__表示 当前操作对象在哪一个模块中

     __class__表示 当前操作对象的类名

1 class C:
2     def __init__(self):
3         self.name = 'Iris'
1 from lib.aa import C
2 obj = C()
3 print(obj.__module__)  # 输出 lib.aa,即:输出模块  C是从哪个模块导出的
4 print(obj.__class__)

3. __init__ 构造方法, 通过类创建对象时触发

4. __del__ 析构方法, 对象在内存中被释放时触发, 此方法一般无需定义

5. __call__ 类内声明,对象加括号触发执行,即 对象名() 或 类名()() 来触发执行 

 1 class Dog(object):
 2     def __init__(self, name, age):
 3         self.name = name
 4         self.age = age
 5     def bark(self):
 6         print("a dog whose name is %s is barking!"%self.name)
 7     def __call__(self, *args, **kwargs):
 8         print("in the call func",*args, **kwargs)
 9     def __str__(self):
10         return "<obj:%s, attribute:%s>" %(self.name, self.age)
11 d = Dog("pick","10")
12 d("1234")# 调用__call__
13 
14 print(Dog.__dict__) #返回类里的所有属性,不包含实例
15 print(d.__dict__)   # 只有实例属性
16 
17 print(d) # __str__ 的效果,在打印对象时,会返回return的结果

6. __dict__ 查看类或对象中的所有成员(实例代码见5)

  • 类.__dict__  返回类里的所有属性,不包含实例
  • 对象.__dict__ 只返回实例属性,也就是在__init__ 中声明的变量

7. __str__ 类内声明,打印对象时,返回的不再是内存地址,而是输出该方法的返回值(实例代码见5)

   执行代码,print(d)的结果为__str__函数中return的结果,如下图所示。

8. __getitem__、 __setitem__和 __delitem__

用于索引操作,如字典。换句话说,就是写一个类,类内声明可以实现让用户通过字典的形式调用这个类,包含获取,设置和删除的功能。

 1 # 写一个类,然后让用户通过字典的形式调用(获取,设置查询)
 2 class Foo(object):
 3     def __init__(self):
 4         self.data = {}
 5     def __getitem__(self, key):
 6         print('__getitem__', key)
 7         return self.data.get(key)
 8     def __setitem__(self, key, value):
 9         print('__setitem__', key, value)
10         self.data[key] = value
11     def __delitem__(self, key):
12         print('__delitem__', key)
13 
14 obj = Foo()
15 
16 result = obj['k1']  # 自动触发执行 __getitem__
17 obj['k2'] = 'Iris'  # 自动触发执行 __setitem__
18 print(obj["k2"])   # 以字典的形式查询
19 del obj['k1']   # 自动触发执行__delitem__

9. __new__ \ __metaclass__

在8中的代码,在进行如下操作:

1 print(type(obj))
2 print(type(Foo))

返回的结果:对象obj的类型是Foo类,而类Foo的类型是type,类还有类 !。

实际上,对象obj是通过类Foo创建的,但在Python中一切皆对象,Foo类本身也是对象,类的起源是 type,也就是类的类,type的实例化。

那么,类就有两种创建方式,还有一种通过type创建的方式:

第一种:普通方式(常用)

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

第二种:特殊方式(不常用)

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

说好的介绍__new__和__metaclass__ ,怎么又扯到类的创建了?__new__是用来创建实例的,并且先于__init__,看下面这段代码。

 1 class Foo(object):
 2     def __init__(self, name, age):
 3         self.name = name
 4         self.age = age
 5         print("__init__执行")
 6     def __new__(cls, *args, **kwargs):
 7         print("__new__执行")
 8         return object.__new__(cls)   # 如果把这句注释掉,__init__便不会执行, 实例化也就不成功
 9 obj = Foo("Iris","11")
10 obj.age

metaclass,直译为元类,简单的解释就是:

当定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,可以把类看成是metaclass创建出来的“实例”。” 基本上不会有用到的情况。

反射

通过字符串映射或修改程序运行时的状态、属性、方法。

反射四大方法:

  • hasattr     判断对象里是否有对应的方法
  • getattr   根据字符串获取对象里对应方法的地址
  • setattr      如果没有该字符串属性或方法,则添加
  • delattr      删除某个属性或方法
 1 class Foo(object):
 2  
 3     def __init__(self, name):
 4         self.name = 'Iris'
 5  
 6     def func(self):
 7         print(" hello ")
 8  obj = Foo()
 9  
10 # #### 检查是否含有成员 ####
11 hasattr(obj, 'name')
12 hasattr(obj, 'func')
13  
14 # #### 获取成员 ####
15 getattr(obj, 'name')
16 getattr(obj, 'func')
17  
18 # #### 设置成员 ####
19 setattr(obj, 'age', 18)
20 setattr(obj, 'show', lambda num: num + 1)
21  
22 # #### 删除成员 ####
23 delattr(obj, 'name')
24 delattr(obj, 'func')
View Code

异常处理

 在编程过程中,为了增加项目的友好型,程序中出现的bug信息一般不会直接显示给用户。这些bug需要通过代码进行捕捉,也就是异常处理。

 1 data = {"name": "alex", "age": 28}
 2 try:
 3     data["name1"]
 4 except KeyError as e:
 5     print(e)
 6 except Exception:
 7     print("在所有错误里")
 8 else:
 9     print("一切正常")
10 finally:
11     print("你管我,我就执行")

第4行代码会捕捉一些已知可能会发生的错误,e中包含着错误信息;第6行代码表示抓取所有错误(但是有些错误是捕捉不到的);第10行代码finally不管有没有捕捉到异常都会执行。  

 有时也会使用断言assert指令,用于一些预先判断的场合。如果断言正确,程序正常运行;如果断言错误,程序报错。

如果a的值异常会对后面程序的运行产生很大的影响,即后面的代码不能出错,则可以运行前对a的值进行一次断言,当报错时会报AssertionError的错误,可以用异常处理去抓取。

1 ...
2 assert a==1
3 ...

动态模块导入

 importlib模块

 该模块可以导入以字符串的形式导入模块。主要用于反射或延迟加载模块。

1 import importlib
2 #aa = __import__("lib.aa")  #与下述方法效果相同
3 aa = importlib.import_module("lib.aa")
4 obj = aa.C()
5 print(obj.name)

 

 

 

                                                                                                                                                                                                                                                                                           

 

posted on 2018-08-29 16:29  墨殇浅尘  阅读(244)  评论(0编辑  收藏  举报

导航