反射的实际应用、面向对象中的各种双下方法、元类

今日学习内容总结

      通过上周的学习,我们对面向编程的学习也已经走向正轨,对面向对象的三大特性的学习,只要做好复习。再配合上一些小练习对所学的知识进行一个灵活运用。就能从中体验编程思想。而今日就是对面向编程学习内容的收尾了。

反射的实际应用

      反射的实际应用:

  # 利用面向对象编写系统终端功能
  class WinCmd(object):
      def ls(self):
          print('windows系统正在执行ls命令')
      def dir(self):
          print('windows系统正在执行dir命令')
      def cd(self):
          print('windows系统正在执行cd命令')

  class LinuxCmd(object):
      def ls(self):
          print('Linux系统正在执行ls命令')
      def dir(self):
          print('Linux系统正在执行dir命令')
      def cd(self):
          print('Linux系统正在执行cd命令')

  # 创建对象
  obj1 = WinCmd()
  obj2 = LinuxCmd()

  # 反射的使用
  def run(obj):
      while True:
          cmd = input('请输入您的指令>>>:')
          if hasattr(obj, cmd):
              func_name = getattr(obj, cmd)
              func_name()
          else:
              print('cmd command not found')
  run(obj1)
  run(obj2)
  # 反射提供了一种不需要考虑代码的前提下操作数据和功能。

      实际运行后会发现,在run(obj1)中,我们确实能够根据输入的指令来执行WinCmd类中的效果。并且判断当WinCmd类中不存在您输入的指令的时候返回一个 cmd command not found 。在run(obj2)中同样如此,执行了 LinuxCmd 类中的方法。这也是反射的强大之处

面向对象中的各种双下方法

      面向对象中有很多双下方法,并且这些方法被一些人称之为魔法方法。而某些双下方法不需要可以调用,而是达到某个条件会自动触发。比如:init 在对象实例化的时候会自动触发。而现在我们就对这些双下方法进行学习与总结。

1__str__

      对象被执行打印(print、前端展示)操作的时候自动触发,该方法必须返回字符串类型的数据,很多时候用来更加精准的描述对象。案例:

  class A:
      def __init__(self,name,age):
          self.name = name
          self.age = age

      def __str__(self):
          return f'姓名:{self.name} 年龄:{self.age}'

  a = A('海狗',18)
  print(a) # 姓名:海狗 年龄:18  打印对象触发__str__方法

2__del__

      对象被执行(被动、主动)删除操作之后自动执行。案例:

  class Foo:
      def __del__(self):
          print('执行我啦')
  f1=Foo()
  del f1  # 执行我啦

3__getattr__

      对象查找不存在名字的时候自动触发。

4__setattr__

      对象在执行添加属性操作的时候自动触发。比如:obj.变量名=变量值

5__call__

      对象被加括号调用的时候自动触发。案例:

  class A:
      def __init__(self):
          pass
      def __call__(self, *args, **kwargs):
          print('鸡哥好帅')
  obj = A()
  obj()  # 鸡哥好帅  对象() 自动触发对象从属于类(父类)的__call__方法

6__enter__ 和 exit

      enter 是在对象被执行with上下文管理语法开始自动触发,该方法返回什么as后面的变量名就会得到什么。exit 在对象被执行with上下文管理语法结束之后自动触发。并且这两个双下方法是一组的,作用为上下文管理。案例:

  class A:
      def __init__(self,name):
          self.name = name

      def __enter__(self):
          print(111)
          return self  # 必须返回self

      def __exit__(self, exc_type, exc_val, exc_tb):
          print(333)

  with A('海狗') as obj:
      print(obj.name)
      
  # 打印结果为: 111 海狗  333

7__getattribute__

      只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方法那么就不会去执行__getattr__方法。

8__new__

      对象是object类的__new__方法 产生了一个对象。实例化对象时,先触发,object 的__new__方法,此方法在内存中开辟一个对象空间。再执行__init__方法,给对象封装属性。案例:

  class A:
      __instance = None
      def __init__(self,name):
          self.name = name

      def __new__(cls, *args, **kwargs):
          if not cls.__instance:
              cls.__instance = object.__new__(cls)
          return cls.__instance
  obj = A('liky')

      让字典具备句点符查找值的功能

  # 定义一个类继承字典
  class MyDict(dict):
      def __getattr__(self, item):
          return self.get(item)

      def __setattr__(self, key, value):
          self[key] = value


  # 创建一个obj对象
  obj = MyDict({'name': 'jason', 'age': 18})

  # 具备句点符取v
  print(obj.name)  # jason
  print(obj.age)  # 18

  # 具备句点符添加k:v
  obj['gender'] = 'male'
  obj.pwd = 123  # 给字典名称空间添加名字  不是数据k:v
  print(obj)  # {'name': 'jason', 'age': 18, 'gender': 'male', 'pwd': 123}

元类

元类简介

      所有的对象都是实例化或者说是通过调用类而得到的,python中一切皆对象,通过class关键字定义的类本质也是对象,对象又是通过调用类得到的,因此通过class关键字定义的类肯定也是调用了一个类得到的,这个类就是元类。

      元类就是用来实例化产生类的类,因此我们可以得到如下的关系:

      查看元类的方式

  class MyClass(object):
      pass
  obj = MyClass()
  print(type(obj))  # <class '__main__.MyClass'>
  print(type(MyClass))  # <class 'type'>  查看MyClass类的类型

产生类的两种表现形式

      产生类的两种表现形式是class关键字和type元类实现。但其本质其实是一种。因为class关键创建类时,一定调用了元类。而这两种产生类的表现形式的写法:

   # class关键字
    class C1(object):
        pass
    print(C1)  # <class '__main__.C1'>
	
    # type元类
    type(类名,父类,类的名称空间)
    res = type('C1', (), {})
    print(res)  # <class '__main__.C1'>

      由上述代码我们发现,在调用type时会依次传入三个参数。这三个参数分别是:


  参数一:包含一系列符合python语法代码的字符串;

  参数二:字典形式的全局名称空间中的名字及所对应的值;

  参数三:字典形式的局部名称空间中的名字及所对应的值;

      学习元类的目的

      元类能够控制类的创建,也就意味着我们可以高度定制类的行为。比如:掌握了物品的生产过程,就可以在过程中做任何的额外操作。而元类可以高度制定类的行为,比如:要求类的名字必须首字母大写。思考在哪里编写定制化代码等等。

元类的基本使用

      元类是不能通过继承的方式直接指定的,需要通过关键字参数的形式修改。案例:

  class MyTypeClass(type):
      def __init__(cls, cls_name, cls_bases, cls_dict):
          # print(cls, cls_name, cls_bases, cls_dict)
          if not cls_name.istitle():
              raise Exception("类名的首字母必须大写")
          super().__init__(cls_name, cls_bases, cls_dict)

  class C1(metaclass=MyTypeClass):
      school = '清华大学'
  # 创建成功,符合条件

  class a(metaclass=MyTypeClass):
      school = '清华大学'
  # 报错,Exception: 类名的首字母必须大写

元类的进阶操作

      在之前的 call 方法中,对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么。由此可以推导出类加括号会执行元类的里面的__call__该方法返回什么其实类加括号就会得到什么。

      那么为什么类加括号就可以被调用呢?类调用之后又是如何保证先运行类中的__new__方法再运行类中的__init__方法的?其实答案就在__call__方法中。如果想让一个对象变成一个可调用对象(加括号可以调用),需要在该对象的类中定义__call__方法,调用可调用对象的返回值就是__call__方法的返回值。案例:

  class Test():

      def __init__(self):
          self.name = 'python'

      def __call__(self, *args, **kwargs):  # self是Test类的对象
          print(self)  # <__main__.Test object at 0x000001C78CE78FD0>
          print(self.name)


  t = Test()
  t()  # <__main__.Test object at 0x0000027912B19278>   python

      因此我们可以得到以下结论:

  对象加括号调用会调用该对象的类中定义的__call__方法;
  类加括号调用会调用内置元类或自定义元类中的__call__方法,取决于类的元类是什么;
  自定义元类加括号调用会内置元类中的__call__方法。

      我们可以通过一个案例验证一下上述结论:

  class MyMeta(type):
  	
      def __call__(self, *args, **kwargs): 
          print(self)
          print(args)
          print(kwargs)
          
          return 'test'

  class Test(metaclass=MyMeta):
      
      def __init__(self, name, age):
          self.name = name
          self.age = age

  # 调用Test就调用了
  t = Test()
  print(t)

  '''
    产生结果:
    <class '__main__.Test'>
    ('haha', '123')
    {}
    test
  '''

      通过上述案例我们可以推断出调用Test时,会调用自定义元类中的__call__方法,并将返回值赋值给调用类产生的对象。

      我们可以通过__call__方法自定义元类来控制类的调用,也就是产生对象。案例:

  class Mymeta(type):

      def __init__(self, class_name, class_bases, class_dict):
          
          # 实现类名首字母必须大写,否则抛出异常
          if not class_name.istitle():
              raise NameError('类名的首字母必须大写')
          # 实现创建的类必须有文档注释,否则抛出异常
          if '__doc__' not in class_dict or len(class_dict['__doc__'].strip())==0:
              raise TypeError('必须有文档注释')

      def __new__(cls, *args, **kwargs):

          return type.__new__(cls, *args, **kwargs)

      def __call__(self, *args, **kwargs):
          people_obj = self.__new__(self)
          self.__init__(people_obj,*args, **kwargs)
          return people_obj
      
  Test = MyMeta(class_name, class_bases, class_dic)  # 调用的是内置元类type的__call__方法

  class Test(metaclass=Mymeta):
      def __new__(cls, *args, **kwargs):
          # 产生空对象--真正的对象,真正造对象的是object
          return object.__new__(cls)  # 这里使用type也没有问题
      
      def __init__(self,name):
          self.name = name
     
      
  t = Test()  # 调用的是自定义元类中的__call__方法

      通过自定义元类来创建类的时候,会调用type的__call__方法,该方法内部会做三件事情:

  1、先调用自定义元类中的__new__方法,产生一个空对象
  2、在调用自定义元类中的__init__方法,为空对象添加独有属性
  3、返回一个初始化好的自定义元类的对象,就是上述的Test类

      调用Test类时则会调用自定义元类MyMeta.__call__方法,同样也会做三件事:

  1、先调用 Test类中的__new__方法产生一个空对象
  2、再调用Test 类中的__init__方法为空对象添加独有属性
  3、返回一个初始化好的Test类的对象赋值给t

      所以,我们可以得到这个道理:如果你想高度定制类的产生过程,那么编写元类里面的__init__方法。如果你想高度定制对象的产生过程,那么编写元类里面的__call__方法。

__new__方法

      创建类时先执行type的__init__方法,当一个类实例化时(创建一个对象)执行type的__call__方法,__call__方法的返回值就是实例化的对象。实例化对象是谁取决于__new__方法,__new__返回什么就是什么。而 new 方法的特性:

  __new__方法是在类准备将自身实例化时调用。
  __new__方法始终都是类的静态方法,即使没有被加上静态方法装饰器。
  

      __new__方法的作用主要有两个:

  1.为对象分配内存空间

  2.返回对象的引用

      返回对象的引用的作用是:

      我们学习过python中__init__方法,__init__方法中的第一个参数self就是实例对象的引用,也就是说,哪个对象调用该方法,self就指向哪个对象。self中对象的引用就是从__new__方法中返回的,换句话说,__new__方法返回对象的引用,__init__方法用self参数接收了。因此我们可以知道,pyhon在创建对象时,首先调用了内置__new__方法,其次再调用__init__方法。

      其实我们在代码中写了__new__方法相当于是对该__new__方法进行了一次重写,重写 __new__方法一定要 return super().new(cls),否则python解释器得不到分配的内存空间的对象引用,就不会调用对象的初始化方法,不会吧对象的引用传递到__init__方法中的self中。

      同时,不是所有的地方都可以直接调用__new__ ,如果是在元类的__new__里面,可以直接调用。案例:

  class Meta(type):
      def __new__(cls, *args, **kwargs):
          obj = type.__new__(cls,*args,**kwargs)
          return obj

      如果是在元类的__call__里面,需要间接调用:

  class Mate(type):
      def __call__(self, *args, **kwargs):
         obj = object.__new__(self) # 创建一个空对象
         self.__init__(obj,*args,**kwargs) # 让对象去初始化
         return obj
posted @ 2022-04-11 22:55  くうはくの白  阅读(49)  评论(0)    收藏  举报