1. 类的成员
python 类的成员有三种:字段、方法、属性
字段
字段包括:普通字段和静态字段,他们在定义和使用中有所区别,而最本质的区别是内存中保存的位置不同,
- 普通字段
属于对象,只有对象创建之后,才会有普通字段,而且只能通过对象来调用 - 静态字段
属于类,解释器在加载代码的时候已经创建,对象和类都可以调用 - 例子:
class Province: country = ‘中国‘ #静态字段 def __init__(self,name): self.name = name #普通字段 #调用字段: obj = Province(‘河南‘) #创建对象 res1 = obj.name #对象调用普通字典 res2 = obj.country #对象调用静态字段 print(‘对象调用普通字典:‘,res1) print(‘对象调用静态字段:‘,res2) res3 = Province.country #类调用静态字段 res4 = Province.name #类调用普通字段,会报错 print(‘类调用静态字段:‘,res3) print(‘类调用普通字段:‘,res4) #报错 输出结果: 对象调用普通字典: 河南 对象调用静态字段: 中国 类调用静态字段: 中国 Traceback (most recent call last): File "D:/study-file/git/gitlab/study/code/day08/成员.py", line 24, in <module> res4 = Province.name # 类调用普通字段,会报错 AttributeError: type object ‘Province‘ has no attribute ‘name‘ 因为对象没有创建,所以在内存中并没有name这个字段,所以,类直接调用会报错
- 总结:
静态字段在内存中只保存一份
普通字段在每个对象中都要保存一份
应用场景: 通过类创建对象时,如果每个对象都具有相同的字段,那么就使用静态字段。普通字段只能用对象访问,静态字段对象和类都可以访问(优先使用类访问)
方法
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同
- 普通方法
属于类,由对象去调用执行,参数至少有一个self,执行普通方法时,自动将调用该方法的对象赋值给self;
- 静态方法
属于类,由类直接调用.当方法内部不需要对象中封装的值时,可以将方法写成静态,并且使用 @staticmethoe装饰,并且参数中不带self,参数可有可无
- 类方法
静态方法的特殊形式,至少有一个cls参数 由类执行 @classmethoe装饰,执行类方法时,自动将调用该方法的类复制给cls
- 举例:
class Province: country = ‘中国‘ # 静态字段 def __init__(self, name): self.name = name # 普通字段 def show(self): #普通方法 print(self.country,self.name) @staticmethod def f1(arg): #静态方法 print(arg) @classmethod def f2(cls): #类方法 cls为类名 print(cls) # 调用字段: obj = Province(‘河南‘) # 创建对象 obj.show() #类调用普通方法执行 obj.f1(‘对象调用静态方法执行‘) Province.f1(‘类调用静态方法执行‘) Province.f2() #类调用类方法执行,返回类名 执行结果: 中国 河南 对象调用静态方法执行 类调用静态方法执行 <class ‘__main__.Province‘>
- 总结
相同点:对于所有的方法而言,均属于类(非对象)中,所以,在内存中也只保存一份。不同点:方法调用者不同、调用方法时自动传入的参数不同。
属性
属性是普通方法的变种,使用 @property来装饰,所以具有方法的表现形式,使用字段调用的方法来调用方法,所以也具有字段的访问形式。由对象来调用
- 属性的基本使用
class Province: country = ‘中国‘ # 静态字段 def __init__(self, name): self.name = name # 普通字段 def show(self): #普通方法 print(self.country,self.name) @staticmethod def f1(arg): #静态方法 print(arg) @classmethod def f2(cls): #类方法 cls为类名 print(cls) @property def f3(self): # 属性 print(self.name) @f3.deleter def f3(self): print(‘del f3‘) @f3.setter def f3(self,arg): print(‘set f3‘,arg) #调用属性 obj = Province(‘河南‘) # 创建对象 obj.f3 #调用属性,自动执行@f3.getter装饰的方法 此形态类似于静态字段的调用 del obj.f3 #自动执行@f3.deleter装饰的方法,类似于静态字段的del obj.f3 = ‘123‘ #自动执行@f3.setter装饰的方法,类似静态字段的set方法 执行结果: 河南 del f3 set f3 123
从执行结果中可以看出,常规类中方法的调用是obj.方法()的形式,但是此时调用属性是obj.方法,不加括号,这种形式和静态字段调用的形式一样,所以说有静态字段的调用方法;而在代码中看,属性的表现形式都是普通方法的形式,即函数,然后使用property来装饰,所以说有普通方法的表现形式
- 属性的表现形式
- 装饰器:
即在一个方法上应用@property装饰器,使方法变为一个属性
class Foo: @property def f1(self): pass @f1.deleter def f1(self): pass @f1.setter def f3(self): pass
* 静态字段: 在类中定义値为property对象的静态字段
class Province: country = ‘中国‘ # 静态字段 def __init__(self, name): self.name = name # 普通字段 def show(self): #普通方法 print(self.country,self.name) @staticmethod def f1(arg): #静态方法 print(arg) @classmethod def f2(cls): #类方法 cls为类名 print(cls) def f4(self): print(1234) def f5(self,arg): print(‘执行set‘) def f6(self): print(‘执行del‘) foo = property(fget=f4, fset=f5, fdel=f6) # 属性的静态字段表达方式 #调用属性 obj = Province(‘河南‘) # 创建对象 obj.foo #自动执行f4方法 del obj.foo #自动执行f6方法 obj.foo = ‘123‘ #自动执行f5方法 输出结果: 1234 执行del 执行set
- 总结:
属性存在意义是:访问属性时可以制造出和访问字段完全相同的假象,按字段的操作来执行对象类中定义的属性中特定的方法,如执行obj.foo会自动执行f4方法,del obj.foo 会自动执行f6方法,此映射关系都使用foo = property(fget=f4, fset=f5, fdel=f6)定义好,属性只是伪造了字段的操作方式而已,不会删除对应的东西,只是根据字段的操作方式来执行对应的方法,而具体执行什么方法,方法有什么功能,这都是自己灵活定义
属性由方法变种而来,如果Python中没有属性,方法完全可以代替其功能。
2. 类的成员修饰符
类的成员修饰符使用类的所有成员,包括如下:
- 公有:在任何地方都能访问和调用
- 私有:只能在类内部进行调用
- 定义:私有成员命名时,前两个字符是下划线。(特殊成员除外,例如:__init__、__call__、__dict__ 等)
class Foo: contry = ‘china‘ #公有静态字段 __contry1 = ‘china‘ #私有静态字段 def __init__(self,name): self.name = name #公有普通字段 self.__name1 = name #私有普通字段 def __f1(self): #私有方法 print(self.name) def f2(self): #公有方法 print(self.__contry) self.__f1()
- 特例
如果想要强制访问私有字段,可以通过 对象._类名__ 私有成员名访问
如:obj._Foo__\f1, obj_Foo__contry1, 不建议强制访问私有成员
3. 类的特殊成员
python的特殊成员是采用__方法名__ 表示含有特殊意义的成员
- __init__ 构造方法,该方法在对象创建时自动创建
class Foo: def __init__(self,name): self.name = name #公有普通字段
- __del__ 析构方法。
当对象在内存中被释放时,自动触发执行,此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行
- __doc__ 表示类的描述信息
class Foo: """ 描述类信息,牛逼的python """ def func(self): pass print(Foo.__doc__) #输出:类的描述信息 输出结果: 描述类信息,牛逼的python
- __module__ 和 class
__module__ 表示当前操作的对象在那个模块
__class__ 表示当前操作的对象的类是什么
class Foo: def f1(self): pass from test import Foo obj = Foo() print(obj.__class__) print(obj.__module__) 输出: <class ‘test.Foo‘> test
- __call__
对象后面加括号,触发执行。注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 call 方法的执行是由对象后加括号触发的,即:对象() 或者 类()()
class Foo: def __init__(self): pass def __call__(self, *args, **kwargs): print(‘__call__‘) obj = Foo() # 执行 __init__ obj() # 执行 __call__
- __dict__ 类或对象中的所有成员
class Foo: def __init__(self): self.name = 123 def f1(self): pass print(Foo.__dict__) #打印类的所有成员 obj = Foo() print(obj.__dict__) #打印对象中的所有成员 输出结果: {‘__init__‘: <function Foo.__init__ at 0x01FA1348>, ‘__module__‘: ‘__main__‘, ‘__weakref__‘: <attribute ‘__weakref__‘ of ‘Foo‘ objects>, ‘__dict__‘: <attribute ‘__dict__‘ of ‘Foo‘ objects>, ‘__doc__‘: None, ‘f1‘: <function Foo.f1 at 0x01FA11E0>} {‘name‘: 123}
- __str__ 指定print对象的时候输出的内容
class Foo: def __init__(self): self.name = 123 def f1(self): pass def __str__(self): return "打印对象输出结果" obj = Foo() print(obj) #打印对象 输出结果: 打印对象输出结果
- __getitem__、__setitem__、__delitem__
用于索引操作,如字典。以上分别表示获取、设置、删除数据
class Foo: def __init__(self): self.name = 123 def __getitem__(self, item): print(‘__getitem__‘,item) def __delitem__(self, key): print(‘__delitem‘,key) def __setitem__(self, key, value): print(‘__setitem__‘,key,value) obj = Foo() result = obj[‘k1‘] # 自动触发执行 __getitem__ obj[‘k2‘] = ‘hahhahhhha‘ # 自动触发执行 __setitem__ del obj[‘k1‘] ## 自动触发执行 __delitem__ 输出结果: __getitem__ k1 __setitem__ k2 hahhahhhha __delitem k1
- __iter__ 用于迭代器,之所以列表、字典、元组可以进行for循环,是因为类型内部定义了 iter
class Foo: def __init__(self,num): self.num = num def __iter__(self): i = 1 while True: if i <= self.num: yield i i += 1 else: break obj = Foo(4) for i in obj: print(i) 输出结果: 1 2 3 4
以上是常用的特殊成员,还有很多不常用的,不在举例
4. 面向对象其他
- isinstance(obj, cls)
检查是否obj是否是类 cls 的对象
class Foo(object): pass obj = Foo() isinstance(obj, Foo)
- issubclass(sub, super)
检查sub类是否是 super 类的派生类
class Foo(object): pass class Bar(Foo): pass issubclass(Bar, Foo)
- 执行父类的方法
默认情况下当子类和父类的方法一样时,优先执行子类的方法,如下:
class Foo: def f1(self): print(‘Foo.f1‘) class Bar(Foo): def f1(self): print(‘Bar.f1‘) obj = Bar() obj.f1() 输出结果: Bar.f1
如果想要强制执行父类的方法呢?可以使用super(子类名,self).父类方法 格式如下:
class Foo: def f1(self): print(‘Foo.f1‘) class Bar(Foo): def f1(self): super(Bar,self).f1() #使用super 来强制执行父类的f1方法 print(‘Bar.f1‘) obj = Bar() obj.f1() 输出结果: Foo.f1 #执行父类f1的结果 Bar.f1
- 应用1,扩展原来代码的功能
需求:一个开源的web框架,在保证不改变源码的情况下,个性定制自己的环境,适应需求。这就用到了类的继承,我新扩展的功能是在原来功能的基础上进行扩展的,所以,我只需要将新功能类继承源代码的相关功能类,然后使用super强制执行父类的方法,实现基本功能,最后在新类中扩展基本功能即可。此区别于装饰器,使用装饰器需要在原来的类上应用装饰器,那就改变了源码
#这是源代码类,实现打印输出 class Foo: def f1(self): print(‘源代码‘) print(‘基本功能执行完毕‘)
如果我要扩展该功能,需要在每次f1执行前打印一个start,执行结束之后,打印一个end,看下面代码
from test import Foo #从源代码中导入Foo类 class New(Foo): def f1(self): print(‘===start====‘) super(New,self).f1() print(‘===end===‘)
前端调用的时候,我直接调用自己创建的类即可,这就实现了基本的扩展,也不改变源代码
obj = New() obj.f1() 输出效果: ===start==== 源代码 基本功能执行完毕 ===end===
- 应用2 实现有序字典
字典key 的排序是无序的,如果要实现一个有序字典,可以根据类的继承来自己写一个有序字典类
实现思路:
1.继承dict类,使新定义的类有dict的所有方法
2.定一个列表,用来存放字典中的key,输出的时候循环这个列表,那么这个字典就变成有序输出
3.使用__setitem__特殊方法实现可以自定义key value
4.使用__str__特殊方法实现print字典代码如下:
class Mydict(dict): def __init__(self): self.li = [] super(Mydict,self).__init__() def __setitem__(self, key, value): #获取obj[‘k1‘] = ‘v1‘形式的赋值 self.li.append(key) #将key存入列表 super(Mydict, self).__setitem__(key,value) #强制执行父类的__setitem__,实现字典功能 def __str__(self): temp = [] for key in self.li: #循环列表中的key value = self.get(key) temp.append("‘%s‘:%s" % (key,value)) #将key value 组成元组存入一个临时列表 ret = "{" + ‘,‘.join(temp) + ‘}‘ #join 来替换key value中间的空格为冒号:,并拼接成字典形式 return ret
下面来测试
obj = Mydict() #创建一个字典 obj[‘k1‘] = ‘v1‘ #字典key value赋值 obj[‘k2‘] = ‘v2‘ print(obj) #打印字典 print(type(obj)) #打印类型 输出: {‘k1‘:v1,‘k2‘:v2} <class ‘__main__.Mydict‘>
5. 设计模式-单例模式
单例模式指的是是多个对象创建时,如果每次都需要创建一个实例,在通过该实例去执行指定的方法,这样每次频繁的创建实例,对内存的读写消耗很大,如果将他们共同的实例,通过一种判断机制,如果实例不存在,则创建实例,然后调用某个方法;如果实例存在,则直接调用某个方法,那么在内存中就仅仅保留了一份实例,这样岂不更好
看下面实例,如果class Mysql 是一个数据库连接池
class Mysql: def __init__(self): self.host = 127.0.0.1 self.port = 3306 self.dbname = test self.user = jeck self.passwd = 123123 def create(self): #执行create语句 pass def delete(self): #执行delete语句 pass
如果用户需要操作数据库,那么需要进行下面操作
user1 = Mysql() user1.create() user2 = Mysql() user2.delete()
发现,每来一个用户,都需要创建一个地址池实例,然后执行某个方法,这样在高并发的网站,直接就崩溃了
换种思路,如果,我只创建一个地址池对象,用户请求来之后,先进行判断,没有实例的话,就创建,有的话就直接使用,岂不更高效。
class Mysql: instance = False def __init__(self): self.host = ‘127.0.0.1‘ self.port = 3306 self.dbname = ‘test‘ self.user = ‘jeck‘ self.passwd = ‘123123‘ def create(self): # 执行create语句 pass def delete(self): # 执行delete语句 pass @classmethod def get_instance(cls): if cls.instance: #判断instence 是否有値,如果有的话,直接返回 return cls.instance else: obj = cls() #instence没有値的话,创建对象,并将对象赋给instence cls.instance = obj return obj obj1 = Mysql() #多例模式 obj2 = Mysql() #多例模式 obj3 = Mysql.get_instance() #单例模式 obj4 = Mysql.get_instance() #单例模式 #打印内存地址 print(‘多例模式:‘,obj1) print(‘多例模式:‘,obj2) print(‘单例模式:‘,obj3) print(‘单例模式:‘,obj4) 输出结果: 多例模式: <__main__.Mysql object at 0x013AAC70> 多例模式: <__main__.Mysql object at 0x013AACD0> 单例模式: <__main__.Mysql object at 0x013AAD30> 单例模式: <__main__.Mysql object at 0x013AAD30>
发现使用单例模式后,第二次创建的对象和第一次创建的对象内存地址是一样的,即使再有成千上万后实例,其都是公用的一个连接池
总结:单利模式存在的目的是保证当前内存中仅存在单个实例,避免内存浪费!!