Python 面向对象
本章内容
1、面向对象简介
2、析构函数
3、封装
4、继承
5、多态
6、类方法
7、特殊的成员方法
8、反射
9、异常处理
10、动态载入
面向对象简介
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数。
面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。
而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
以Student类为例,在Python中,定义类是通过class
关键字:
class Student(object):
n = 3 #类变量
age = 11 #类变量
def __init__(self, name, score,age): #构造函数,在实例化的时候做一些类的初始化工作 self.name = name #实例变量(静态属性),作用域就是实例的本身 self.score = score
self.age = age def print_score(self): print('%s: %s' % (self.name, self.score))
class
后面紧接着是类名,即Student
,类名通常是大写开头的单词,
紧接着是(object)
,表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
定义好了student类,就可以通过student类,创造出student的实例,
定义好了Student
类,就可以根据Student
类创建出Student
的实例,创建实例是通过类名+()实现的:
>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>
变量bart
指向的就是一个Student的object,后面的0x10a67a590
是内存地址,每个object的地址都不一样,而Student
本身则是一个类。
__init__()方法是可选的,如果不提供,Python 会给出默认的__init__方法
注意到__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。
变量的取值范围
a1 = Student('python','99','20') print('age,n',a1.age,a1.n) #运行结果 age,n 20 3 #age取的是实例变量,n取的是类变量 #结论,取的时候先取实例变量,如果没有的话择取类变量
增加类变量
##bart.high = '1.8m' ##bar.high '1.8m'
删除类变量
del a1.name print(a1.name) #运行结果 AttributeError: 'Student' object has no attribute 'name'
修改类变量
a1 = Student('python','99','20') a1.n = 18 print('from a1 n',a1.n) a2 = Student('c++','88','10') print('from a2 n',a2.n) #在a1中修改了n的值,那n2种会不会有影响呢? #运行结果: from a1 n :18 from a2 n :3 #实验证明,a1,a2各自有各自的内存空间,内存空间内存放着自己的变量,a1修改的是自己内存空间内部的,所以a2,不受影响。 #注意,这里的n如果是列表的话,a1修改,a2是能看到的,这里是有与list的类型有关
问个问题,类变量的用途?
当我听到这个问题的时候,有点懵逼,怎么总结呢?alex做的总结,--->大家公用的属性,定义一个就可以了,节省开销。这个场景应用与cn = 'china',如果14亿中国人都要放到构造函数中吗?当然不必要,直接一个类函数即可。
析构函数
在实例释放、销毁的时候自动执行的,通常用于做一些收尾的工作,如关闭一些数据库链接,或关闭打开的一些临时文件
class Student(object): def __init__(self, name, score,age): self.name = name self.score = score self.age = age def print_score(self): print('%s: %s' % (self.name, self.score)) def __del__(self): print('del -------')
a1 = Student('python','99','22')
del a1
上面的“__del__”就是一个析构函数了,当使用del 删除对象时,会调用他本身的析构函数,另外当对象在某个作用域中调用完毕,在跳出其作用域的同时析构函数也会被调用一次,这样可以用来释放内存空间
__del__()也是可选的,如果不提供,则Python 会在后台提供默认析构函数
封装
私有属性/方法
私有属性参考 http://www.cnblogs.com/blackmatrix/p/5600830.html
其实,Python并没有真正的私有化支持,但可用下划线得到伪私有。 尽量避免定义以下划线开头的变量!
(1)_xxx "单下划线 " 开始的成员变量叫做保护变量,意思是只有类实例和子类实例能访问到这些变量,需通过类提供的接口进行访问;不能用'from module import *'导入
(2)__xxx 类中的私有变量/方法名 (Python的函数也是对象,所以成员方法称为成员变量也行得通。)," 双下划线 " 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
(3)__xxx__ 系统定义名字,前后均有一个“双下划线” 代表python里特殊方法专用的标识,如 __init__()代表类的构造函数。
继承
class People(): def __init__(self,name,age): self.name = name self.age = age def talk(self): print('%s is talking ....' %self.name) def eat(self): print('%s is eating ....'%self.name) class man(People): def strong(self): print('%s is strong'%self.name) m1 = man('alex','22') m1.strong() m1.talk() #运行结果 alex is strong alex is talking ....
这就实现了man对people的继承,能实现了继承,那能对父类的重构呢?
class People(): def __init__(self,name,age): self.name = name self.age = age def talk(self): print('%s is talking ....' %self.name) def eat(self): print('%s is eating ....'%self.name) class man(People): def strong(self): print('%s is strong'%self.name) # 重构父类的talk方法 def talk(self): People.talk(self) print('man is talking...') m1 = man('alex','22') m1.strong() m1.talk() #运行结果 alex is strong alex is talking .... man is talking...
如果类中想增加变量,而继承的父类中个数有限,那需要怎么办?那就重构构造函数
class People(): def __init__(self,name,age): self.name = name self.age = age def talk(self): print('%s is talking ....' %self.name) def eat(self): print('%s is eating ....'%self.name) class man(People): def __init__(self,name,age,hoby): #对构造函数重构的两种方法。 #People.__init__(self,name,age) super(man,self).__init__(name,age) print('My hoby is %s'%hoby) def strong(self): print('%s is strong'%self.name) def talk(self): People.talk(self) print('man is talking...') m1 = man('alex','22','basketball') m1.strong() m1.talk() #运行结果 alex is strong alex is talking .... man is talking...
经典类&&新式类
class People : 经典类
class People(object) :新式类
People.__init__(self,name,age) :经典类
super(man,self).__init__(name,age) :新式类
多重继承
class People(): def __init__(self,name,age): self.name = name self.age = age def talk(self): print('%s is talking ....' %self.name) def eat(self): print('%s is eating ....'%self.name) class relation(): def makefriend(self,obj): print('%s is make friend with %s'%(self.name,obj.name)) #多重继承,继承People,与relation class man(People,relation): def __init__(self,name,age,hoby): super(man,self).__init__(name,age) print('My hoby is %s'%hoby) class woman(People): def born(self): print('%s born a body'%self.name) m1 = man('alex','22','basketball') w1 = woman('hanmeimei','21') m1.makefriend(w1)
python2 经典类是按照深度优先来继承的,新式类是按照广度优先来继承的
python3 都是通过广度优先来继承的
测试是广度优先还是深度优先的方法:
__author__ = "Alex Li" class A: def __init__(self): print("A") class B(A): pass # def __init__(self): # print("B") class C(A): pass # def __init__(self): # print("C") class D(B,C): pass # def __init__(self): # print("D") obj = D()
多态
一种方法,多种实现
class Animal: def __init__(self, name): # Constructor of the class self.name = name def talk(self): # Abstract method, defined by convention only pass #raise NotImplementedError("Subclass must implement abstract method") @staticmethod def animal_talk(obj): obj.talk() class Cat(Animal): def talk(self): print('Meow!') class Dog(Animal): def talk(self): print('Woof! Woof!') d = Dog("陈荣华") #d.talk() c = Cat("徐良伟") #c.talk() # # def animal_talk(obj): # obj.talk() Animal.animal_talk(c) Animal.animal_talk(d)
类方法
静态方法
通过@staticmethod装饰器即可把其装饰的方法变为一个静态方法,什么是静态方法呢?其实不难理解,普通的方法,可以在实例化后直接调用,并且在方法里可以通过self.调用实例变量或类变量,但静态方法是不可以访问实例变量或类变量的,一个不能访问实例变量和类变量的方法,其实相当于跟类本身已经没什么关系了,它与类唯一的关联就是需要通过类名来调用这个方法
class Dog(object): def __init__(self,name): self.name = name @staticmethod #把eat方法变为静态方法 def eat(self): print("%s is eating"% self.name) d1 = Dog('puppy') d1.eat() #改变为静态方法后,在运行的话就会有个错误 #TypeError: eat() missing 1 required positional argument: 'self' 解决办法: 1. 调用时主动传递实例本身给eat方法,即d.eat(d) 2. 在eat方法中去掉self参数,但这也意味着,在eat中不能通过self.调用实例中的其它变量了
类方法
类方法通过@classmethod装饰器实现,类方法和普通方法的区别是, 类方法只能访问类变量,不能访问实例变量
class Dog(object): #name = '我是类变量' def __init__(self,name): self.name = name @classmethod #使之成为类方法 def eat(self): print("%s is eating"% self.name) d1 = Dog('puppy') d1.eat() #运行的时候会有个报错 #AttributeError: type object 'Dog' has no attribute 'name' 解决方法: 如果把上面的类变量打开,那就问题了
属性方法
属性方法的作用就是通过@property把一个方法变成一个静态属性
class Dog(object): def __init__(self,name): self.name = name @property #使之变为属性方法 def eat(self): print("%s is eating"% self.name) d1 = Dog('puppy') d1.eat() #d1.eat #运行结果会有报错 #TypeError: 'NoneType' object is not callable 解决办法: 直接d1.eat 来调用
属性方法中的增加变量,删除属性
class Dog(object): def __init__(self,name): self.name = name self.__age = None @property #使之变为属性方法 def eat(self): print("%s is eating"% self.name,self.__age) @eat.setter #属性方法中,再添加参数 def eat(self,age): print("set age to " ,age) self.__age= age #对值进行保存 @eat.deleter #删除属性的方法 def eat(self): del d1.__age print("删除完毕!") d1 = Dog('puppy') d1.eat d1.eat = '11' #对属性进行复制 d1.eat del d1.eat #如果不添加eat.deleter ,直接调用这个方法会报错的
属性方法的应用场景
class Flight(object): def __init__(self,name): self.flight_name = name def checking_status(self): print("checking flight %s status " % self.flight_name) return 1 @property def flight_status(self): status = self.checking_status() if status == 0 : print("flight got canceled...") elif status == 1 : print("flight is arrived...") elif status == 2: print("flight has departured already...") else: print("cannot confirm the flight status...,please check later") f = Flight("CA980") f.flight_status #直接运行f.flight_status 就可以看到接口的查询结果了,相当于内部封装好,外部直接调用,和闭包很相像
类的特殊成员方法
1、__doc__ 表示类的描述信息
class Foo: """ 描述类信息,这是用于看片的神奇 """ def func(self): pass print Foo.__doc__ #输出:类的描述信息,这个注释必须放到类名的下面
2、__module__和__class__
#放到包下 class C: def __init__(self): self.name = 'wupeiqi' #在包外面 from lib.aa import C obj = C() print obj.__module__ # 输出 lib.aa,即:输出模块 print obj.__class__ # 输出 lib.aa.C,即:输出类
3、__init__ 构造方法,通过类创建对象时,自动触发执行。
4、__del__析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
5、__call__ 对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print '__call__' obj = Foo() # 执行 __init__ obj() # 执行 __call__
6. __dict__ 查看类或对象中的所有成员
class Province: country = 'China' def __init__(self, name, count): self.name = name self.count = count def func(self, *args, **kwargs): print 'func' # 获取类的成员,即:静态字段、方法、 print(Province.__dict__) # 输出:{'country': 'China', '__module__': '__main__', 'func': <function func at 0x10be30f50>, '__init__': <function __init__ at 0x10be30ed8>, '__doc__': None} obj1 = Province('HeBei', 10000) print(obj1.__dict__) # 获取 对象obj1 的成员 # 输出:{'count': 10000, 'name': 'HeBei'} obj2 = Province('HeNan', 3888) print(obj2.__dict__) # 获取 对象obj1 的成员 # 输出:{'count': 3888, 'name': 'HeNan'}
7、__str__ 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Foo: def __str__(self): return 'alex li' obj = Foo() print obj # 输出:alex li
8、__getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据,有啥用?还不知道
class Foo(object): def __getitem__(self, key): print('__getitem__',key) def __setitem__(self, key, value): print('__setitem__',key,value) def __delitem__(self, key): print('__delitem__',key) obj = Foo() result = obj['k1'] # 自动触发执行 __getitem__ obj['k2'] = 'alex' # 自动触发执行 __setitem__ del obj['k1']
9、__new__ \ __metaclass__
class Foo(object): def __init__(self,name): self.name = name f = Foo("alex")
print type(f) #输出:<class '__main__.Foo'> 表示,obj 对象由Foo类创建
print type(Foo) #输出:<type 'type'> 表示,Foo类对象由 type类创建
上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,Foo类本身也是一个对象,因为在Python中一切事物都是对象。如果按照一切事物都是对象的理论:obj对象是通过执行Foo类的构造方法创建,那么Foo类对象应该也是通过执行某个类的 构造方法 创建。
所以,f对象是Foo类的一个实例,Foo类对象是 type 类的一个实例,即:Foo类对象 是通过type类的构造方法创建。那么,创建类就可以有两种方式:
1、普通方法
class Foo(object): def func(self): print 'hello alex'
2、特殊方法
def func(self): print 'hello wupeiqi' Foo = type('Foo',(object,), {'func': func}) #type第一个参数:类名 #type第二个参数:当前类的基类 #type第三个参数:类的成员
特殊方法加上构造函数
def func(self): print("hello %s"%self.name) def __init__(self,name,age): self.name = name self.age = age Foo = type('Foo',(object,),{'func':func,'__init__':__init__}) f = Foo("jack",22) f.func()
反射
反射即想到4个内置函数分别为:getattr、hasattr、setattr、delattr 获取成员、检查成员、设置成员、删除成员下面逐一介绍先看例子:
class Foo(object): def __init__(self): self.name = 'abc' def func(self): return 'ok' obj = Foo() #获取并执行成员 res = getattr(obj,'func')() print(res) #运行结果:ok #检查成员 res = hasattr(obj,'func') print(res) #运行结果,因为有func,返回True #设置成员值 print(obj.name) res = setattr(obj,'name',19) print(obj.name) #运行结果: abc 19 #删除成员 print(obj.name) delattr(obj,'name') print(obj.name) #运行结果 #AttributeError: 'Foo' object has no attribute 'name'
如果setattr没有这个属性,那如何添加
class Foo(object): def __init__(self,name): self.name = name def func(self): return 'ok' def talk(self): print('%s is talking ...'%self.name) obj = Foo('Python') str = 'buck' res = setattr(obj,str,None) print(res) #如果第三位指定None或者其他字符,则可以直接返回 setattr(obj,str,talk) res = getattr(obj,str) res(obj) #如果第三位指定的是个函数,那需要用上面的这个方法
异常处理
在编程过程中为了增加友好性,在程序出现bug时一般不会将错误信息显示给用户,而是现实一个提示的页面,通俗来说就是不让用户看见大黄页!!!
python2 try: pass except Exception,ex: pass python3 try: pass except Exception as ex: #万能异常,捕获所有的异常 pass 终极版 try: pass except (aerror,berror) as e: pass except cerror as e: pass else: print('没有错误,则我执行,一切正常') finally: print('无论什么情况我都会执行')
主动出发异常
try: raise Exception('错误了。。。') except Exception,e: print e
自定义异常
class WupeiqiException(Exception): def __init__(self, msg): self.message = msg def __str__(self): return self.message try: raise WupeiqiException('我的异常') except WupeiqiException,e: print e
断言
# assert 条件 assert 1 == 1 assert 1 == 2
用断言判断前面的条件,只有断言的条件正确,才执行后面的程
需要注意的是,有些错误是捕捉不到的,如语法错误,缩进错误,导致pythno还没解析到捕捉错误的代码处就停止了,所以就不会有后面的捕捉动作了。
动态载入
动态引入,已字符串的形式引入。
动态导入这个模块,两种方式
1、lib = __import__('lib.aa')
lib.aa.C().aa #就可以实现调用了
2、import importlib
lib = importlib.import_modoule('lib.aa')
lib.C().name #这样就可以调用了,这是建议用法,比上面多引入了一层