python面相对象进阶

Posted FuZZ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python面相对象进阶相关的知识,希望对你有一定的参考价值。

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>

 

发现使用单例模式后,第二次创建的对象和第一次创建的对象内存地址是一样的,即使再有成千上万后实例,其都是公用的一个连接池
总结:单利模式存在的目的是保证当前内存中仅存在单个实例,避免内存浪费!!

 

以上是关于python面相对象进阶的主要内容,如果未能解决你的问题,请参考以下文章

十七面相对象的进阶

019 python面相对象编程--系统整理

python中的面相对象

python之面相对象程序设计

Python最强学习知识点:面相对象基础语法

8.python之面相对象part.8(类装饰器)