python基础-第七篇-7.2面向对象(进阶篇)
进入到今天的探索前,我先对上节内容进行一下回顾:
- 面向对象是一种编程方式,此编程方式的实现是基于对类和对象的使用
- 类是一个模板,模板中包装了多个函数可供使用
- 对象是基于类创建的,实例用于调用被包装在类中的函数
- 对于c#和java--只能用面向对象编程,而对于ruby和python则可以用函数编程和面向对象编程
- 面向对象不是所有的情况都适用,当两个东西不存在公共的部分,最好用函数编程
- 对象编程里的self是形式参数,是python内部把对象当参数传递进去的
- __init__ 构造方法--当实例化对象时,会自动执行类里的这个方法,并在此方法里执行具体的封装操作
- __del__ 解释器销毁对象的时候自动调用的方法,叫析构方法
类的成员
类的成员可以分为三大类:字段、方法和属性
注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段。而其他的成员,则都是保存在类中,即:无论对象的多少,在内存中只创建一份。
一、字段
字段分普通字段和静态字段
另外它们在内存上也有所区别
- 在前面我们知道,实例化对象的过程其实也是对象带他的普通字段再开辟一份内存空间
- 静态字段属于类,而普通字段属于对象
那现在我们来看看静态字段在类调用和对象调用过程中的内存变化--
class schoolmember: human_num = 10000 def __init__(self,name,age,salary): self.name = name self.age = age self.salary = salary def sleep(self): print('%s go to sleep....'%self.name) obj1 = schoolmember('小明',22,10) obj2 = schoolmember('小红',56,10) obj1.salary += 1000 obj2.salary += 2000 print(obj1.salary) #11000 print(obj2.salary) #12000 print('*'*20) obj1.human_num += 3000 obj2.human_num += 4000 print(schoolmember.human_num) #10000 print(obj1.human_num) #13000 print(obj2.human_num) #14000 print('*'*20) schoolmember.human_num += 5000 print(schoolmember.human_num) #15000 print(obj1.human_num) #13000 print(obj2.human_num) #14000
我们从内存的角度来看这个问题,当我们使用类名.字段名去调用静态变量的时候,这个变量是存在类的内存中的,这个时候各个对象都可以取到类的内存中的变 量,但是当我们使用对象名.字段名去修改这个静态变量的时候,我们其实是在对象的内存中又开辟了一块空间去存储这个字段和修改后的值,所以一个对象的字段 发生改变,类和其他对象的该字段并不会发生变化。哎呦,总之解决这个问题的最终办法就是始终用 类名.字段名 去使用类中的静态变量就对了!
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段
二、方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
- 普通方法:由对象调用;至少一个self参数;执行普通方法时,自动将调用该方法的对象赋值给self;
- 类方法:由类调用; 至少一个cls参数;执行类方法时,自动将调用该方法的类复制给cls;
- 静态方法:由类调用;无默认参数,可以不用创建就能实现某个功能,等同在类中实现函数式编程
class Cat: def __init__(self,name): self.name = name #普通方法:至少有一个self参数 def eat(self): print('%s is eating...'%self.name) #静态方法,无默认参数 @staticmethod def drink(something,obj): #可以把对象当做普通参数传入 print('%s is drinking %s.'%(obj.name,something)) #类方法,至少有一个cls参数 @classmethod def sleep(cls,obj): print('%s is sleeping...'%obj.name) ca = Cat('小花猫') ca.eat() Cat.drink('water',ca) Cat.sleep(ca)
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。
不同点:方法调用者不同、调用方法时自动传入的参数不同。
三、属性
特性:普通方法的变种,将方法伪造成一种字段,定义时就self参数,执行的结果为return返回值
# ############### 定义 ############### class Foo: def func(self): pass # 定义属性 @property def prop(self): pass # ############### 调用 ############### foo_obj = Foo() foo_obj.func() foo_obj.prop #调用属性
由属性的定义和调用要注意一下几点:
- 定义时,在普通方法的基础上添加 @property 装饰器;
- 定义时,属性仅有一个self参数
- 调用时,无需括号
方法:foo_obj.func()
属性:foo_obj.prop
注意:属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
实例:对于主机列表页面,每次请求不可能把数据库中的所有内容都显示到页面上,而是通过分页的功能局部显示,所以在向数据库中请求数据时就要显示的指定获取从第m条到第n条的所有数据(即:limit m,n),这个分页的功能包括:
- 根据用户请求的当前页和总数据条数计算出 m 和 n
- 根据m 和 n 去数据库中请求数据
# ############### 定义 ############### class Pager: def __init__(self, current_page): # 用户当前请求的页码(第一页、第二页...) self.current_page = current_page # 每页默认显示10条数据 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 = Pager(1) p.start 就是起始值,即:m p.end 就是结束值,即:n
从上述可见,Python的属性的功能是:属性内部进行一系列的逻辑计算,最终将计算结果返回。
类成员的修饰符
刚才我们讲到类的成员大体还可以分为这么两类
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法,私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__等)
class Animal: __ke = '属' def __init__(self,name,sex): self.__name = name self.__sex = sex def get_putongziduan(self): print(self.__name,self.__sex) def __eat(self): print('eat') def __drink(self): print('drink') def get_putongfangfa(self): self.__drink() self.__eat() def get_jingtaiziduan(self): print(self.__ke) def sleep(self): print('sleep') class Cat(Animal): def jiao(self): print('jiao') def get_info(self): print(self.__name) print(self.__ke) self.__drink() #子类创建的对象 cat = Cat('小白猫','male') #父类创建的对象 animal = Animal('小花狗','man') #父类外部调用私有普通字段 # print(animal.__name) #子类外部外部调用私有普通字段 # print(cat.__name) #父类外部调用私有方法 # animal.__drink() #子类外部调用私有方法 # cat.__drink() #父类内部部调用私有普通字段 animal.get_putongziduan() cat.get_putongziduan() #子类内部调用私有字段和私有方法 # cat.get_info()
结论:私有的只有自己能用,就算你儿子,你孙子来了也用不了
类的特殊成员
- __doc__ 表示类的描述信息
class Foo: """ 描述类信息,这是用于看片的神奇 """ def func(self): pass print Foo.__doc__ #输出:类的描述信息
- __module__ 表示当前操作的对象在哪个模块
- __class__ 表示当前操作的对象的类是什么
#!/usr/bin/env python # -*- coding:utf-8 -*- 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,即:输出类
- __init__ 构造方法,通过创建对象时,自动执行
- __del__ 析构方法,当对象在内存中被释放时,自动执行
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
- __call__ 对象后面加括号,触发执行 --对象()或类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print '__call__' obj = Foo() # 执行 __init__ obj() # 执行 __call__
- __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'}
- __str__ 如果一个类中定义了__str__方法,那么在打印 对象 时,默认输出该方法的返回值
class Foo: def __str__(self): return 'wupeiqi' obj = Foo() print obj # 输出:wupeiqi
- __getitem__、__setitem__、__delitem__ 用于索引操作,如字典。分别表示获取、设置、删除数据
#!/usr/bin/env python # -*- coding:utf-8 -*- 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'] = 'wupeiqi' # 自动触发执行 __setitem__ del obj['k1'] # 自动触发执行 __delitem__
- __getslice__、__setslice__、__delslice__ 该三个方法用于分片操作,如:列表
#!/usr/bin/env python # -*- coding:utf-8 -*- class Foo(object): def __getslice__(self, i, j): print '__getslice__',i,j def __setslice__(self, i, j, sequence): print '__setslice__',i,j def __delslice__(self, i, j): print '__delslice__',i,j obj = Foo() obj[-1:1] # 自动触发执行 __getslice__ obj[0:1] = [11,22,33,44] # 自动触发执行 __setslice__ del obj[0:2] # 自动触发执行 __delslice__
- __enter__、__exit__ 上下文管理时,一进一出
import pymysql class DBConnection(object): def __init__(self, host, port, username, password, db_name): self.host = host self.port = port self.user = username self.passwd = password self.db = db_name def __enter__(self): print(self.__dict__) self.conn = pymysql.connect(**self.__dict__) # self.conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='123456', db='testdb') self.cursor = self.conn.cursor() return self def __exit__(self, exc_type, exc_val, exc_tb): self.cursor.close() self.conn.close() def fetch_all(self): self.cursor.execute("select * from app01_token") rows = self.cursor.fetchall() print(rows) with DBConnection('127.0.0.1',3306,'root','123456','testdb') as db: db.fetch_all()
- __getattr__, __setattr__ 支持属性访问,和属性赋值
class Foo: def __getattr__(self, item): print(item) def __setattr__(self, key, value): print(key, value) obj = Foo() obj.xx = 123 obj.xx
- __iter__ 用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 __iter__
class Foo(object): pass obj = Foo() for i in obj: print i # 报错:TypeError: 'Foo' object is not iterable
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) obj = Foo([11,22,33,44]) for i in obj: print i
- __new__和__metaclass__
class Foo(object): def __init__(self): pass obj = Foo() # obj是通过Foo类实例化的对象
上述代码中,obj 是通过 Foo 类实例化的对象,其实,不仅 obj 是一个对象,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 wupeiqi'
b).特殊方式(type类的构造函数)
def func(self): print 'hello wupeiqi' Foo = type('Foo',(object,), {'func': func}) #type第一个参数:类名 #type第二个参数:当前类的基类 #type第三个参数:类的成员
==》 类 是由 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()
静态字段和类对象指针
本人搔师博客文章
欢迎大家对我的博客内容提出质疑和提问!谢谢
笔者:拍省先生