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')
异常处理
在编程过程中,为了增加项目的友好型,程序中出现的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)