面向对象的双下方法,元类

今日作业

  编写元类规定对象的所有数据值转大写
    	eg:
        	obj.name = 'jason'
          print(obj.name)  # JASON

   class MyTypeClass(type):
      def __call__(self, *args, **kwargs):
          args = [i.upper() for i in args]
          if kwargs:
              for i in kwargs:
                  kwargs[i] = kwargs[i].upper()
          return super().__call__(*args, **kwargs)


  class MyClass(metaclass=MyTypeClass):
      def __init__(self, name):
          self.name = name
      def __setattr__(self, key, value):
          self.__dict__[key] = value.upper()


  my = MyClass(name='aaa')
  my.age = 'abc'
  print(my.name,my.age)

面向对象的双下方法

1.__ str__, __ repr__

  __str__(***) 里边必须有return "字符串"类型  (优先级高于__repr__)
    1.打印对象的时候,会触发__str__方法
    2.直接str转化也可以触发
  class A:
      def __init__(self,name,age):
          self.name = name
          self.age =age

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

  a = A('二狗',35) 
  b = A('大狗',56)
  c = A('老狗',18)
  打印对象触发__str__方法
  print(f'{a.name}  {a.age}') # 二狗  35
  print(f'{b.name}  {b.age}')
  print(f'{c.name}  {c.age}')
  print(a) # 666
             姓名: 二狗 年龄: 35
  print(b)
  print(c)
  直接str转化也可以触发.
  print(str(a))  # 666
                   姓名: 二狗 年龄: 35

  __repr__  里边必须有return "字符串"类型
    打印对象的时候,会触发__repr__方法
  class A:
      def __init__(self,name,age):
          self.name = name
          self.age =age

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

  a = A('二狗',35)
  b = A('大狗',56)
  c = A('老狗',18)
  print(a)
  print(repr(a))


  __str__和__repr__ 一起使用时,只执行__str__的内容
  class A:

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

      def __str__(self):
          return '777'


      def __repr__(self):
          return '666'

  a = A('二狗',35)
  b = A('大狗',56)
  c = A('老狗',18)
  # print(a)
  print(a) # 777
  print(b) # 777
'''
   str()方法定义当被print()方法或者str()方法调用时的行为,repr()方法定义当被print()方法或者
repr()方法调用时的行为,一般来说,它们的功能都是实现类到字符串的转化,实现方式上也没有特别的差
异。 但实际上,str()方法的返回结果可读性应该要更强一些,repr()方法的返回结果要更加准确,也就是
说,str()方法的意义是得到便于人们阅读的信息,而__repr__()方法的目的在于开发者便于调试。
   注意:当进行print()的时候,首先被执行的是__str__()方法,如果没定义__str__()方法,repr()
方法才会被执行。
'''

2.__call__

  __call__(***)  对象()触发对象从属类(父类)的__call__方法
  class Foo:

      def __init__(self):
          pass

      def __call__(self, *args, **kwargs):
          print('__call__')

  obj = Foo()
  obj()
  当调用obj()时,call()方法会被调用,call()方法也可以接受参数
'''
  call()方法可以让类的实例像函数一样被调用
'''

3. __del__

  __del__当一个对象的生命周期结束被彻底销毁的时候被调,__del__方法会被调用,不常用,一般不需要
进行重写。
  class A:
      def __del__(self):
          print(666)

  obj = A()
  del obj

4.__getattr__,__getattribute__,__setattr__

  __getattr__表示的是当一个对象访问一个属性时,没有从它定义的属性当中查找到就会调用这个方法。
  class A(object):
      def __init__(self, value):
          self.value = value

      def __getattr__(self, item):
          print("into __getattr__")       
          return "can not find"
  a = A(10)
  print(a.value)
  # 10
  print(a.name)
  # into __getattr__
  # can not find

  __getattribute__只要对象查找名字无论名字是否存在都会执行该方法,如果类中有__getattribute__方
法 那么就不会去执行__getattr__方法
  class A(object):
      def __init__(self, value):
          self.value = value

      def __getattribute__(self, item):
          print("into __getattribute__")
          return "__getattribute__"
  a = A(10)
  print(a.value)
  into __getattribute__
  __getattribute__
  print(a.name)
  into __getattribute__
  __getattribute__


  class A(object):
      def __init__(self, value):
          self.value = value

      def __getattribute__(self, item):
          print("into __getattribute__")
          return "__getattribute__"

      def __getattr__(self, item):
          print('into __getattr__')
          return '__getattr__'


  a = A(10)
  print(a.value)
  # into __getattribute__
  # __getattribute__
  print(a.name)
  # into __getattribute__
  # __getattribute__
'''
  使用归纳:

  1.__getattribute__方法优先级比__getattr__高

  2.当同时定义__getattribute__和__getattr__时,__getattr__方法不会再被调用,除非显示调用
__getattr__方法或引发AttributeError异常。

  3.如果是对不存在的属性做处理,尽量把逻辑写在__getattr__方法中

  4.如果非得重写__getattribute__方法,需要注意两点:第一是避免.操作带来的死循环;第二是不要遗
忘父类的__getattribute__方法在子类中起的作用
'''

 __setattr__对象在执行添加属性操作的时候自动触发	>>>	obj.变量名=变量值
  class A(object):
      def __init__(self, value):
          print("into __init__")
          self.value = value

      def __setattr__(self, name, value):
          print("into __setattr__")
          if value == 10:
              print("from __init__")
          object.__setattr__(self, name, value)


  a = A(10)
  # into __init__
  # into __setattr__
  # from __init__
  print(a.value)
  # 10
  a.value = 100
  # into __setattr__
  print(a.value)
  # 100

5.__enter__,__exit__

  我们知道在操作文件对象的时候可以这么写
  with open('a.txt') as f:
       '代码块'
  上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

  __exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

  如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

  # coding=utf-8
  class Open:
      def __init__(self, name):
          self.name = name

      def __enter__(self):
          print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')
          return self

      def __exit__(self, exc_type, exc_val, exc_tb):
          print('with中代码块执行完毕时执行我啊')
          print(exc_type)
          print(exc_val)
          print(exc_tb)
          return True


  with Open('aa.txt') as f:
      print(f)
      print(f.name)
      print(sdsd)  # 触发 __exit__ ,之后的代码就不会运行
      print('-------')

  print('End....')

  '''
  运行结果:
  出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量
  <__main__.Open object at 0x0000016D7FA95BE0>
  aa.txt
  with中代码块执行完毕时执行我啊
  <class 'NameError'>
  name 'sdsd' is not defined
  <traceback object at 0x0000016D7FA925C8>
  End....
  '''

  '''
  总结:
  __enter__在with语句后自动调用,可以给as后的变量赋值
  __exit__用于捕获异常,返回boolean对象,如果为True异常被忽略,如果为False异常被抛出
  三个参数分别是:
  1.exc_type 异常类型
  2.exc_value异常值
  3.traceback
  with obj as f:
      代码块
  1. with obj ----> 触发obj.__enter__(),拿到返回值
  2. as f --> f = 返回值
  3. with obj as f 等同于  f = obj.__enter__()
  4. 执行代码块
   1> 没有异常,整个代码块运行完毕后去触发__exit__(),它的三个参数都返回None
   2> 有异常的情况下,从异常出现的位置直接触发__exit))
      a:如果__exit__返回值为True,代表吞掉了异常(不报异常)
      b:如果__exit__返回值不为True,代表吐出了异常(异常报错)
      c:__exit__运行完毕,就代表整个with语句运行完毕
  '''

针对双下方法的笔试题

  1.让字典具备句点符查找值的功能
  	# 1.定义一个类继承字典
    class MyDict(dict):
        def __getattr__(self, item):
            return self.get(item)

        def __setattr__(self, key, value):
            self[key] = value
    '''要区别是名称空间的名字还是数据k:v键值对'''
    obj = MyDict({'name':'jason','age':18})
    # 1.具备句点符取v
    # print(obj.name)
    # print(obj.age)
    # 2.具备句点符设k:v
    # obj['gender'] = 'male'
    obj.pwd = 123  # 给字典名称空间添加名字  不是数据k:v
    print(obj)

  2.补全下列代码 使其运行不报错
  	"""
  	class Context:
  			pass
  	with Context() as ctx:
  			ctx.do_something()
  	"""
    class Context:
      def __enter__(self):
          return self
      def __exit__(self, exc_type, exc_val, exc_tb):
          pass
      def do_something(self):
          pass
  	with Context() as ctx:
      ctx.do_something()

元类

元类就是用于创建类的“东西”。

在理解元类之前,我们必须先掌握Python中的类(class)。

和大多数语言一样,Python中的类是用来描述如何“生成一个对象”:
class A(object):
    pass

a = A()
print(a)
# <__main__.A object at 0x01DFFDD8>

但是,在Python中,类不仅能用来描述如何生成一个对象,类本身也是对象。

在你使用关键词 class 的时候,Python就会执行它,并创建一个对象。
class A(object):
    pass
当我们运行这两行代码时在内存中创建了一个名为“A”的对象。
这个对象(类)本身具有创建对象(实例)的能力,因此它也是一个类。你可以对它做以下操作:

1.将其分配给变量
2.复制它
3.为其添加属性
4.将其作为函数参数传递

我们可以对A添加属性等等,还可以对A使用反射
class A(object):
    pass

A.name = 1
print(A) # <class '__main__.A'>
print(hasattr(A,'name')) # True
A实际也是一个对象,但它又可以实例出对象。

使用class关键字时,Python会帮你自动创建此对象,但是,Python同样也提供了一种手动创建的方法,那就
是type函数。
type函数最经典的用法是返回对象的类型。但是很少人知道,它还能接受参数并手动创建类。
type(name, bases, attrs)
  
name: 类名
bases: 元组,父类名
attrs: 字典,类属性值
A = type('A', (), {})
print(A) # <class '__main__.A'>

class Foo(object):
    bar = True
等同于Foo = type('Foo', (), {'bar':True})

继承
FooChild = type('FooChild', (Foo,), {})
print(FooChild)
# <class '__main__.FooChild'>
print(FooChild.bar) 
# True

可见通过 type() 函数创建的类和直接写class是完全一样的。
因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用 type() 函数创建出class。

产生类的两种表现形式(本质是一种)

  1.class关键字
  	class C1(object):
      pass
  	print(C1)  # <class '__main__.C1'>
  	
  2.type元类
  	type(类名,父类,类的名称空间)
    	res = type('C1', (), {})
  		print(res)  # <class '__main__.C1'>
      
  """
  学习元类的目的
  	元类能够控制类的创建 也就意味着我们可以高度定制类的行为
  		eg:掌握了物品的生产过程 就可以在过程中做任何的额外操作
  		
  	比如:要求类的名字必须首字母大写
  		思考在哪里编写定制化代码
  			类的产生过程目前还比较懵 	  元类里面的__init__方法
  			对象的产生过程呢 			     类里面的__init__方法
  		方法:由已知推未知
  """

metaclass自定义元类

  元类一般用于创建类。在执行类定义时,解释器必须要知道这个类的正确的元类。解释器会先寻找类属性
__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类。如果此属性没有定义,它会向上查
找父类中的__metaclass__.如果还没有发现__metaclass__属性,解释器会检查名字为
__metaclass__的全局变量,如果它存在,就使用它作为元类。否则, 这个类就是一个传统类,并用 
types.ClassType 作为此类的元类。
  注意:Python3中不再有__metaclass__属性以及模块级别的__metaclass__属性。
  在执行类定义的时候,将检查此类正确的(一般是默认的)元类,元类(通常)传递三个参数(到构造器): 类名,
从基类继承数据的元组,和(类的)属性字典。

  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("类名的首字母必须大写 你个SD")
          super().__init__(cls_name, cls_bases, cls_dict)

  class C1(metaclass=MyTypeClass):
      school = '清华大学'


  class a(metaclass=MyTypeClass):
      school = '清华大学'

元类进阶操作

1.回想__call__方法
	对象加括号会自动执行产生该对象的类里面的__call__,并且该方法返回什么对象加括号就会得到什么
  推导:类加括号会执行元类的里面的__call__该方法返回什么其实类加括号就会得到什么
  """类里面的__init__方法和元类里面的__call__方法执行的先后顺序"""
  class MyTypeClass(type):
      def __call__(self, *args, **kwargs):
          print('__call__ run')
          super().__call__(*args, **kwargs)
  class MyClass(metaclass=MyTypeClass):
      def __init__(self, name):
          print('__init__ run')
          self.name = name
  obj = MyClass('jason')
  
# 定制对象的产生过程
  class MyTypeClass(type):
      def __call__(self, *args, **kwargs):
          # print('__call__ run')
          # print(args,kwargs)
          if args:
              raise Exception('必须全部采用关键字参数')
          super().__call__(*args, **kwargs)

  class MyClass(metaclass=MyTypeClass):
      def __init__(self, name):
          # print('__init__ run')
          self.name = name

	"""强制规定:类在实例化产生对象的时候 对象的独有数据必须采用关键字参数"""
  # obj1 = MyClass('jason')
  obj2 = MyClass(name='jason')
"""
如果你想高度定制类的产生过程
	那么编写元类里面的__init__方法
如果你想高度定制对象的产生过程
	那么编写元类里面的__call__方法
"""

__new__

  new()方法是在新式类(新式类和经典类的区别)中的方法。object为所有新式类的基类,在object中,
new()方法被定义为静态方法,并且至少需要传递一个参数cls,cls表示需要实例化的类。

  在创建一个类对象实例的过程中,new()方法作用在构造方法__init__()之前。new()函数执行后会返回实
例对象(self),然后将self作为第一个参数传给该类的初始化方法__init__()方法。

  可以这么理解,如果设计一个A类,A类中__new__()方法负责返回一个实例对象(不一定是A类对象),而
__init__()方法负责将类对象初始化(设置各种属性之类)。在init()调用之前,new()可以决定是否要使
用init()方法,因为new()可以调用其他类的构造方法或者直接返回别的对象来作为本类(A类)的实例。
  #类A
  class A(object):
      def __init__(self):
          self.name = 'A' #设置name属性

      def __new__(cls, *args, **kwargs):
          # 执行object的__new__()函数,传入cls说明返回当前类(A类)的实例对象
          return object.__new__(cls)

  #类B
  class B(object):
      def __init__(self):
          self.name = 'B' #设置name属性

      def __new__(cls, *args, **kwargs):
          #执行object的__new__()函数,传入参数A说明返回(A类)的实例对象
          return object.__new__(A)

  a = A() 	#创建A类实例, 相当于隐式执行了A类的__new__()和__init__()
  b = B() 	#创建B类实例, 只执行了B类的__new__()
  print(a.name) 		#  A
  #print(b.name)    # 出错:AttributeError: 'A' object has no attribute 'name'
  b.__init__( )        #显示调用__init(),完成b的初始化
  print(b.name)     #  A  正常输出
  print(type(a))     # <class '__main__.A'>
  print(type(b))     # <class '__main__.A'>


  #第一个print(b.name)报错的原因是,想要创建B类实例的时候,new方法却直接返回了A类实例对象
  #因此,没有执行B类的init方法,也没有执行A类的init方法,也就导致b对象没有name属性
  #在显示调用init方法之后,b才有了name属性,所以第二个的print(b.name)正常输出

  """
  注意:并不是所有的地方都可以直接调用__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 23:17  春游去动物园  阅读(41)  评论(0编辑  收藏  举报