一、创建类和对象
面向对象编程,其实就是对“类”和“对象”的使用。
类就好比一个模板,而模板里面可以包含许多方法,这些方法可以被实例化的对象进行调用。
对象则是根据模板创建的实例,通过对象可以调用类中的方法。
首先,我们来创建一个类和对象:
class Animal: def eat(self): print("I'am eating") def run(self): print("I'am running") def sleep(self): print("I'am sleep") def bark(self): print("I'am barking") cat = Animal() # 创建一个Animal类的对象 cat cat.eat() cat.sleep() cat.run() cat.bark()
注:类中定义的方法,第一个参数必须是self(除特例外),这也是方法与函数区别所在。
在面向对象编程中,方法都是通过对象来进行调用的;而在面向过程编程中,是直接调用函数。
函数式编程主要应用在函数之间独立且无数据交互,而面向对象中,类中的数据都被对象所共享。
二、面向对象的三大特性
1.封装
顾名思义,是把内容保存在一段空间内,以后要用的时候再去调用。
第一,将内容保存在某处
cat = Animal() cat.name = 'xiaomi' # 将'xiaomi'封装到cat.name中
注:我们先暂时不提这个字符串被保存在哪里了.在下面类的成员中,我会详细解释的.
第二,需要再调用
调用被封装的内容,有两种方式:
- 通过对象直接调用
- 通过self间接调用
(1)通过对象直接调用
cat = Animal() rabbit = Animal() cat.name = 'xiaomi' # 将'xiaomi'封装到cat.name中 rabbit.name = 'xiaohua' # 将'xiaohua'封装到rabbit.name中 print(cat.name,rabbit.name) ###输出结果###### xiaomi xiaohua
(2)通过self间接调用
class Animal: def __init__(self,name): self.name = name def eat(self): print("I'am eating") def getname(self): print(self.name) cat = Animal('xiaomi') rabbit = Animal('xiaohua') cat.getname() rabbit.getname() #####输出结果为 xiaomi xiaohua
注:这里的self是什么,这里先不说,下面我会详细解释。
2.继承
继承,在面向对象中和现实生活中是一样。父类的东西,作为子类都可以拿来使用.
class Animal: def eat(self): print "%s 吃 " %self.name def drink(self): print "%s 喝 " %self.name def shit(self): print "%s 拉 " %self.name def pee(self): print "%s 撒 " %self.name class Cat(Animal): # 猫继承动物类 def __init__(self, name): self.name = name self.breed = '猫' def cry(self): print '喵喵叫' class Dog(Animal): # 狗继承动物类 def __init__(self, name): self.name = name self.breed = '狗' def cry(self): print '汪汪叫' # ######### 执行 ######### c1 = Cat('小白家的小黑猫') c1.eat() c2 = Cat('小黑的小白猫') c2.drink() d1 = Dog('胖子家的小瘦狗') d1.eat()
所以,在面向对象中,继承其实就是将多个类共有的部分提取到父类中,子类只需要继承调用,而不必要每个子类都写一次。
那么问题就来,如果一个类继承了多个父类,其继承顺序如何?
这里的继承顺序要看类的新类还是经典类;
- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
class D: def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> D --> C # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar()
class D(object): def bar(self): print 'D.bar' class C(D): def bar(self): print 'C.bar' class B(D): def bar(self): print 'B.bar' class A(B, C): def bar(self): print 'A.bar' a = A() # 执行bar方法时 # 首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错 # 所以,查找顺序:A --> B --> C --> D # 在上述查找bar方法的过程中,一旦找到,则寻找过程立即中断,便不会再继续找了 a.bar()
经典类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去D类中找,如果D类中么有,则继续去C类中找,如果还是未找到,则报错
新式类:首先去A类中查找,如果A类中没有,则继续去B类中找,如果B类中么有,则继续去C类中找,如果C类中么有,则继续去D类中找,如果还是未找到,则报错
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
3.多态
python不支持多态,其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)
一般情况下,面向对象使用场景:
- 多函数需使用共同的值,如:数据库的增、删、改、查操作都需要连接数据库字符串、主机名、用户名和密码
- 需要创建多个事物,每个事物属性个数相同,但是值的需求
最后来说明一下类和对象在内存中是如何保存的?
class Animal: def eat(self,name): self.name = name def run(self): pass def sleep(self): pass cat = Animal("xiaomi") rabbit = Animal('xiaohua')
类以及类中的方法在内存中只有一份,而根据类创建的每一个对象都在内存中需要存一份。也就是上面的name,只要创建一个对象,其内存空间就会保存一份。
三、类的成员
类成员可以分为三大类:字段,方法,属性
注:其中,只有普通字段保存在对象中,其他的全部都保存在类中。即不管创建多少对象,只有普通字段与对象的个数一致,而其他的在内存中只保存一份,均属于类。
1.字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同
- 普通字段属于对象
- 静态字段属于类
class foo: country = '中国' # 静态字段,保存在类中,仅此一份 def __init__(self,name): self.name = name # 普通字段,封装到对象中 def show(self): print(self.name) obj = foo("xiaoxiao") print(obj.name) # 通过对象直接访问普通字段 print(foo.country) # 通过类名直接访问静态字段 print(obj.country) # 对象间接访问静态字段 print(foo.name) #报错,
由上述代码可以看出【普通字段需要通过对象来访问】【静态字段通过类访问直接访问,也可以通过对象间接访问】,在使用上可以看出普通字段和静态字段的归属是不同的。其在内容的存储方式类似如下图:
由上图可知:
- 静态字段在内存中只保存一份
- 普通字段在每个对象中都要保存一份
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段。且可以放在初始化__init__()函数中,让这个函数帮我们自动完成对象数据的封装.
如:
class foo: country = '中国' # 静态字段,保存在类中,仅此一份 def __init__(self,name): self.name = name # 普通字段,封装到对象中 obj = foo("xiaoxiao") # 每个对象中都封装了一份self.name的对象数据 obj2 = foo("huahua") print(obj.name,obj2.name)
2.方法
方法包括:普通方法、静态方法和类方法.三种方法在内存中都属于类,区别在于调用方式不同。
class foo: def __init__(self,age): self.name = 'davve' # 这样就固定死了,每个对象内部都封装一样的数据 self.age = age # 这样封装到对象中,而且每个对象封装的age都不一样 def show(self): # 普通方法 print('common method') @classmethod def class_fuc(cls): # 类方法,必须含有cls参数 print('class_function') @staticmethod def static_func(): # 静态方法,类似类外部定义的方法 print('static_function') f = foo('18') f.show() # 函数对象调用普通方法 foo.show(f) # 类调用普通方法 f.class_fuc() # 对象调用类方法 foo.class_fuc() # 类名调用类方法 f.static_func() # 对象调用类方法 foo.static_func() # 类名调用类方法
总结:类中方法,除了类调用类中普通方法不一样,其余的全部都可以通过类或者对象来调用。
3.属性---类中普通方法的变种
a.基本使用
class Foo: def func(self): pass @property def prop(self): print('property function') return 'egon' foo = Foo() result = foo.prop print(result)
由属性的定义和调用要注意一下几点:
- 定义时,在普通方法的基础上添加 @property 装饰器;
- 定义时,属性仅有一个self参数
- 调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
实例:对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
class Paper: def __init__(self,current_page): self.current_page = current_page self.per_items = 10 @property def start(self): val = (self.current_page - 1) * self.per_items return val @property def end(self): val = self.current_page * self.per_items return val p = Paper(1) print(p.start) print(p.end)
Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。
b.定义方式
属性的定义方式有两种:
- 装饰器 即:在方法上应用装饰器
- 静态字段 即:在类中定义值为property对象的静态字段
装饰器方式:在类的普通方法上应用@property装饰器
在经典类中,具有一种方式:@property
class Animal: @property def ani(self): return 'huahua' p = Animal() print(p.ani)
在新式类中,具有三种方式:@property、@方法名.setter、@方法名.deleter修饰的方法。由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object): def __init__(self): self.original_price = 100 self.discount = 0.8 @property def price(self): new_price = self.original_price * self.discount return new_price @price.setter def price(self,value): self.discount = value @price.deleter def price(self): del self.original_price obj = Goods() print(obj.price) obj.price = 200 # 调用price.setter下面的price 来修改price print(obj.price) del obj.price # 调用price.deleter下面的price来修改price print(obj.price)
静态字段方式,创建值为property对象的静态字段
class Foo: def get_bar(self): return 'alex' BAR = property(get_bar) obj = Foo() result = obj.BAR # 调用get_bar并获取其返回值 print(result)
property的构造方法中有个四个参数
- 第一个参数是方法名,调用
对象.属性
时自动触发执行方法 - 第二个参数是方法名,调用
对象.属性 = XXX
时自动触发执行方法 - 第三个参数是方法名,调用
del 对象.属性
时自动触发执行方法 - 第四个参数是字符串,调用
对象.属性.__doc__
,此参数是该属性的描述信息
class Foo: def get_bar(self): return 'wupeiqi' # *必须两个参数 def set_bar(self, value): return return 'set value' + value def del_bar(self): return 'wupeiqi' BAR = property(get_bar, set_bar, del_bar, 'description...') obj = Foo() obj.BAR # 自动调用第一个参数中定义的方法:get_bar obj.BAR = "alex" # 自动调用第二个参数中定义的方法:set_bar方法,并将“alex”当作参数传入 del Foo.BAR # 自动调用第三个参数中定义的方法:del_bar方法 obj.BAE.__doc__ # 自动获取第四个参数中设置的值:description...
class property(fget=None, fset=None, fdel=None, doc=None) Return a property attribute. fget is a function for getting an attribute value. fset is a function for setting an attribute value. fdel is a function for deleting an attribute value. And doc creates a docstring for the attribute. A typical use is to define a managed attribute x: class C: def __init__(self): self._x = None def getx(self): return self._x def setx(self, value): self._x = value def delx(self): del self._x x = property(getx, setx, delx, "I'm the 'x' property.") If c is an instance of C, c.x will invoke the getter, c.x = value will invoke the setter and del c.x the deleter. If given, doc will be the docstring of the property attribute. Otherwise, the property will copy fget‘s docstring (if it exists).
由于静态字段方式创建属性具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
class Goods(object): def __init__(self): # 原价 self.original_price = 100 # 折扣 self.discount = 0.8 def get_price(self): # 实际价格 = 原价 * 折扣 new_price = self.original_price * self.discount return new_price def set_price(self, value): self.original_price = value def del_price(self, value): del self.original_price PRICE = property(get_price, set_price, del_price, '价格属性描述...') obj = Goods() obj.PRICE # 获取商品价格 obj.PRICE = 200 # 修改商品原价 del obj.PRICE # 删除商品原价
四、类成员修饰符
类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
私有成员和公有成员的定义不同:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)
class C: def __init__(self): self.name = '公有字段' self.__foo = '私有字段'
私有成员和公有成员的访问限制不同:
静态字段:
class C: cname = '公有静态字段' def __init__(self): self.name = '公有字段' self.__foo = '私有字段' def getname(self): print(self.cname) class D(C): def __init__(self): pass c = C() print(c.cname) # 对象访问静态公有字段 print(C.cname) # 类名访问静态公有字段 c.getname() # 类内部函数访问静态公有字段 d = D() print(d.cname) # 派生类也可以访问静态公有字段
class C: __name = '私有静态字段' def func(self): print(C.__name) class D(C): def show(self): print(C.__name__) # C.__name # 类访问错误 obj = C() obj.func() # 类内部可以访问,正确 obj_son = D() obj.son.show() # 派生类不能访问
总结:公有静态字段,可以被对象,类内部,派生类都可以访问;私有静态字段,仅类内部才能访问。
普通字段:
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
- 私有普通字段:仅类内部可以访问;
class C: def __init__(self): self.foo = "公有字段" def func(self): print self.foo # 类内部访问 class D(C): def show(self): print self.foo # 派生类中访问 obj = C() obj.foo # 通过对象访问 obj.func() # 类内部访问 obj_son = D(); obj_son.show() # 派生类中访问
class C: def __init__(self): self.__foo = "私有字段" def func(self): print self.foo # 类内部访问 class D(C): def show(self): print self.foo # 派生类中访问 obj = C() obj.__foo # 通过对象访问 ==> 错误 obj.func() # 类内部访问 ==> 正确 obj_son = D(); obj_son.show() # 派生类中访问 ==> 错误
五、类的特殊成员
1. __doc__
类的描述信息。
class Foo: """ 描述类信息,这是用于看片的神奇 """ def func(self): pass print Foo.__doc__ #输出:类的描述信息
2. __module__ 和 __class__
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
class C: def __init__(self): self.name = 'alex' print(C.__name__) # 输出类名 print(C.__class__) # 输出类 print(C.__module__) # 输出模块
3. __del__
析构方法,当对象在内存中被释放时,自动触发执行。
4. __call__
对象后面加括号,触发执行。
注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 __call__ 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print '__call__' obj = Foo() # 执行 __init__ obj() # 执行 __call__
5. __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'}
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'}
6. __str__
如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值。
class Provice: def __str__(self): return 'hello world' pp = Provice() print(pp)
7、__getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo: def __getitem__(self, item): print('__getitem__', item) def __setitem__(self, key, value): print("__setitem__", key, value) def __delitem__(self, key): print('__delitem__', key) obj = Foo() result = obj['k1'] # 自动触发执行 __getitem__ obj['k2'] = 'xiaoxiao' # 自动触发执行了 __setitem__ del obj['k1'] # 自动触发执行了 __delitem__
8、__getslice__、__setslice__、__delslice__
切片操作时候被调用.
9、__iter__
用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 __iter__
class Foo(object): pass obj = Foo() for i in obj: print(i)
class Foo(object): def __iter__(self): pass obj = Foo() for i in obj: print(i) ######报错信息####### TypeError: iter() returned non-iterator of type 'NoneType'
class Foo(object): def __init__(self,sq): self.sq = sq def __iter__(self): return iter(self.sq) # iter()返回一个迭代器对象 obj = Foo([1,2,3,4,5]) for i in obj: print(i)
因此,for循环迭代的其实是:iter([1,2,3,4,5])
上述就变为了:
obj = iter([1,2,3,4,,5,6]) for i in obj: print(i)
obj = iter([1,2,3,4,,5]) while True: val = obj.next() print val
10. __new__ 和 __metaclass__
class Foo(object): def __init__(self): pass obj = Foo() # obj是通过Foo类实例化的对象
obj是通过Foo类实例化的对象,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 world")
b.特殊方式(type类的构造函数)
def func(self): print('hello world') Foo = type('Foo',(object,),{'func':func}) #type第一个参数:类名 #type第二个参数:当前类的基类 #type第三个参数:类的成员
python中的类是由type类实例化产生的。
那么问题来了,类默认是由 type 类实例化产生,type类中如何实现的创建类?类又是如何创建对象?
答:类中有一个属性 __metaclass__,其用来表示该类由 谁 来实例化创建,所以,我们可以为 __metaclass__ 设置一个type类的派生类,从而查看 类 创建的过程。
class MyType(type): def __init__(self, what, bases=None, dict=None): super(MyType, self).__init__(what, bases, dict) def __call__(self, *args, **kwargs): obj = self.__new__(self, *args, **kwargs) self.__init__(obj) class Foo(object): __metaclass__ = MyType def __init__(self, name): self.name = name def __new__(cls, *args, **kwargs): return object.__new__(cls, *args, **kwargs) # 第一阶段:解释器从上到下执行代码创建Foo类 # 第二阶段:通过Foo类创建obj对象 obj = Foo()