面向对象之: 反射和双下方法

Posted zyyhxbs

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面向对象之: 反射和双下方法相关的知识,希望对你有一定的参考价值。

一, 反射

  1. 反射:程序可以访问,检测和修改它本身状态或行为的一种能力(自省)

  2. python面向对象中的反射:通过字符串的形式操作对象相关的属性

  3. python中的一切事物都是对象(都可以使用反射)

  4. 四个可以实现自省的函数,下列方法适用于类和对象(一切对象,类本身也是对象)

    # 对实例化对象的示例
    class Foo:
        f = '类的静态变量'
        def __init__(self, name, age):
            self.name = name
            self.age = age
        def say_hi(self):
            print(f'Hi,self.name')
    obj = Foo('小马', 18)
    
    # 检测是否含有某属性
    print(hasattr(obj, 'name'))  # True
    print(hasattr(obj, 'say_hi'))
    
    # 获取属性
    n = getattr(obj, 'name')
    print(n)
    func = getattr(obj, 'say_hi')
    func()
    print(getattr(obj, 'aaa', '不存在啊')) # 没有会返回设定好的信息,不设定则报错
    
    # 设置属性
    setattr(obj, 'hello', True) # 设置字段 hello = True
    setattr(obj, 'show_name', lambda self: self.name + '美女') # 设置方法
    print(obj.__dict__)
    print(obj.show_name(obj)) # 设置的函数也在对象空间中
    
    # 删除属性
    delattr(obj, 'age')
    delattr(obj, 'show_name')
    delattr(obj, 'show_name111') # 不存在就会报错
    
    print(obj.__dict__)
    -----------------------------------------------------
    # 对类的反射
    class Foo:
        Class_name = '天上人间'
        def __init__(self, name):
            self.name = name
        def func(self):
            return 'func'
        @staticmethod
        def bar():
            return 'bar'
    
    print(getattr(Foo, 'Class_name'))
    print(getattr(Foo, 'func'))
    print(getattr(Foo, 'bar'))
    -----------------------------------------------------
    
    # 当前模块的反射
    import sys
    def s1():
        print('s1')
    def s2():
        print('s2')
    
    this_module = sys.modules[__name__] # 获取当前脚本这个对象
    print(hasattr(this_module, 's1'))
    a = getattr(this_module, 's2')
    a()
    -----------------------------------------------------
    # 其他模块的发射
    # 一个模块中代码(module_test.py)
    def test():
        print('from the test')
    
    # 本模块中的代码
    import module_test as obj
    obj.test()
    print(hasattr(obj, 'test'))
    a = getattr(obj, 'test')
    a()
    -----------------------------------------------------
    # 反射的应用
    # 模拟访问网站
    class User:
        def login(self):
            print('欢迎来到登录页面')
    
        def register(self):
            print('欢迎来到注册页面')
    
        def save(self):
            print('欢迎来到存储页面')
    
    user = User()
    while 1:
        choose = input('>>>:').strip()
        if hasattr(user, choose):
            func = getattr(user, choose)
            func()
        else:
            print('输入错误!')
    

二, 函数VS方法

  1. 判断函数与方法的途径

    • 通过打印函数(方法)名确定

      def func():
          pass
      
      print(func)  # <function func at 0x01CCF7C8>
      
      class A:
          def func(self):
              pass
      
      print(A.func)  # <function A.func at 0x03AAA588>
      obj = A()
      print(obj.func)
      # <bound method A.func of <__main__.A object at 0x01D05450>>
    • 通过types模块验证

      from types import FunctionType
      from types import MethodType
      
      def func():
          pass
      
      class A:
          def func(self):
              pass
      
      obj = A()
      print(isinstance(func, FunctionType))  # True
      print(isinstance(A.func, FunctionType))  # True
      print(isinstance(obj.func, FunctionType))  # False
      print(isinstance(obj.func, MethodType))  # True
      ---------------------------------------------------
      # 静态方法是函数
      from types import FunctionType
      class A:
          def func(self):
              pass
          @classmethod
          def func1(self):
              pass
          @staticmethod
          def func2(self):
              pass
      obj = A()
      # 静态方法其实是函数
      print(isinstance(A.func2, FunctionType))  # True
      print(isinstance(obj.func2, FunctionType))  # True
  2. 函数与方法的区别

    1. 函数是显性传参,如我们要指明为len()函数传递一些要处理的参数 !
    2. 函数跟对象无关 !
    3. 方法中存在隐式传参 !
    4. 方法可以操作类内部的数据
    5. 方法跟对象是关联的.如我们在用strip()方法是,是不是都是要通过str对象调用,比如我们有字符串s,然后s.strip()这样调用.是的,strip()方法属于str对象

三, 双下方法

  1. 定义: 双下方法是特殊方法,他是解释器提供的,由双下划线加方法名加双下划线(__方法名__)组成具有特殊意义的方法,双下方法主要是python源码程序员使用的

  2. 调用: 不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:__init__

  3. __init__

    class A:
        def __init__(self, name):
            print('in __init__')
            self.name = name
    obj = A('小马')
    # 实例化对象时自动运行,在__new__之后
  4. __len__

    # 一个对象可以使用len()函数,根本原因是这个对象从属于的类中有__len__方法
    class B:
        def __len__(self):
            print(666)
            return 10  # 必须要有一个int型的返回值
    
    b = B()
    len(b) # len()一个对象就会触发__len__方法  # 666
    
    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
        def __len__(self):
            return len(self.__dict__)
    
    a = A()
    print(len(a))  # 2
  5. __hash__

    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
        def __hash__(self):
            return hash(str(self.a) + str(self.b))
    a = A()
    print(hash(a))  # hash()一个对象就会触发__hash__方法
  6. __str__

    # 如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值
    class A:
        def __init__(self, name):
            self.name = name
        def __str__(self):
            return self.name
    a = A('小马')
    str(a) # str()一个对象就会触发__hash__方法
    print(str(a))
    print(a) # 打印对象时,默认输出__str__方法的返回值
    print(f'a') # 格式化输出也会触发__str__方法
  7. __repr____str__相似,但是优先级比__str__

    class A:
        def __init__(self):
            pass
        def __repr__(self):
            return '小马'
    a = A()
    print(repr(a))
    print(a) # 打印对象时,也可以调用__repr__方法
    print(f'a') # 格式化输出,也可以调用__repr__方法
    print('%r' % a) # 指定调用__repr__方法
  8. __call__

    # 对象后面加括号,触发执行
    # 注:构造方法__new__的执行是由创建对象触发的,即:对象 = 类名(),而对于__call__方法的执行是由对象后加括号触发的,即:对象(),或者类()()
    class Foo:
        def __init__(self):
            pass
        def __call__(self, *args, **kwargs):
            print('__call__')
    obj = Foo()  # 执行 __init__
    obj()  # 执行 __call__
  9. __eq__

    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
        def __eq__(self,obj):
            if self.a == obj.a and self.b == obj.b:
                return True
    a = A()
    b = A()
    print(a == b) # 对一个类的两个对象进行比较操作就会触发__eq__方法
  10. __del__

    • 析构方法,当对象在内存中被释放时,自动触发执行.
    • 注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的.
  11. __new__ 构造方法,创造并返回一个新对象

    class A:
        def __init__(self):
            self.x = 1
            print('in init function')
        def __new__(cls, *args, **kwargs):
            print('in new function')
            return object.__new__(cls)
            # 调用object或者父类(最终调用object)的__new__来开辟对象空间
    
    a = A() # 类名加()先触发__new__,并且将类名自动传给cls
    print(a.x)
    -------------------------------------------------------
    # 单例模式
    # 一个类只能实例化一个对象,无论实例化多少次,内存中只有一个对象;这个类的对象不是个性化的,主要是实例化对象之后去执行类中的方法
    class A:
        __instance = None
        def __new__(cls, *args, **kwargs):
            if cls.__instance is None:
                obj = object.__new__(cls)
                cls.__instance = obj
            return cls.__instance
        def func(self):
            print(self.__instance)
    a = A()
    a.func() # <__main__.A object at 0x002F5F30>
    
    b = A() # b还是a对象
    print(b is a) # True
    • 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案.

    • 采用单例模式动机,原因: 对于系统中的某些类来说,只有一个实例很重要.例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器.如在Windows中就只能打开一个任务管理器.如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象.浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态.因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要.

    • 如何保证一个类只有一个实例并且这个实例易于被访问呢? 定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象.一个更好的解决办法是让类自身负责保存它的唯一实例.这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法.这就是单例模式的模式动机.

    • 单例模式优缺点

      # 单例模式优缺点
      # 优点
      # 一,实例控制
      # 单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例.
      # 二,灵活性
      # 因为类控制了实例化过程,所以类可以灵活更改实例化过程.
      
      # 缺点
      # 一,开销
      # 虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销.可以通过使用静态初始化解决此问题.
      # 二,可能的开发混淆
      # 使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象.因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类.
      # 三,对象生存期
      # 不能解决删除单个对象的问题.
  12. __item__系列 对对象进行类似字典的操作

    class Foo:
        def __init__(self, name):
            self.name = name
        def __getitem__(self, item):
            print(self.__dict__[item])
            print(666)
        def __setitem__(self, key, value):
            self.__dict__[key] = value
        def __delitem__(self, key):
            self.__dict__.pop(key)
            print('del obj[key]时,我执行')
        def __delattr__(self, item):
            print('del obj.key时,我执行')
            self.__dict__.pop(item)
    f1 = Foo('小马')
    f1['age'] = 18 # 执行__setitem__方法
    f1['sex'] = '男'
    print(f1['age']) # 执行__getitem__方法
    del f1['age']  # 执行__delitem__方法
    del f1.sex     # 执行__delattr__方法
    f1['name'] = '大马'
    print(f1.__dict__)
  13. __enter__ __exit__ 上下文管理器相关

    # 如果想要对一个类的对象进行with  as 的操作 不行。
    class A:
        def __init__(self, text):
            self.text = text
    
    with A('大爷') as f1:
        print(f1.text)
    ------------------------------------------------------
    # 解决上述问题
    class A:
        def __init__(self, text):
            self.text = text
        def __enter__(self):  # 开启上下文管理器(with语句)对象时触发此方法
            self.text = self.text + '来啦'
            return self  # 将实例化的对象返回f1
        def __exit__(self, exc_type, exc_val, exc_tb):  # 执行完上下文管理器对象f1时触发此方法
            self.text = self.text + '这就走啦'
    with A('小马') as f1:
        print(f1.text)
    print(f1.text)
    
    ------------------------------------------------------
    # 用于文件管理
    class Diycontextor:
        def __init__(self, name, mode):
            self.name = name
            self.mode = mode
    
        def __enter__(self):
            print('in enter')
            self.filehander = open(self.name, self.mode)
            return self.filehander
    
        def __exit__(self, *para):
            print('in exit')
            self.filehander.close()
    
    with Diycontextor('module_test.py', 'r') as f:
        for i in f:
            print(i)

以上是关于面向对象之: 反射和双下方法的主要内容,如果未能解决你的问题,请参考以下文章

面向对象的反射和双下方法(魔术方法)

Python面向对象之反射,双下方法

面向对象之 元类 , 反射 , 双下方法

面向对象之:元类,反射, 双下方法

Python面向对象反射,双下方法

python 面向对象专题:元类type反射函数与类的区别特殊的双下方法