23.python元类-元编程

发布时间 2023-12-05 21:22:50作者: 贝壳里的星海

python元类

python中的一切都是对象,类本身也是对象,元类则是创建类对象的类

元类(metaclass)可以干预类的创建,控制类的实例化。

通俗地理解就是,普通的类用来定义一般对象的属性和行为,元类用来定义普通的类及其实例对象的属性和行为

type|object|class

  • object: object类是所有类(class)的父类,包括type类,object类的父类为空
  • type: type类是所有类的类型,即为所有类(class)都可由type实例化而来,包括type类和父类object
  • class: 继承自object,同时,由type进行实例化。其中,type就是我们所要讲的元类(metaclass)
class MyClass(object):
    pass

print(type(MyClass()))  # 类的实例对象属于 MyClass
print(type(MyClass))    # 发现类的对象属于 type
print(type(int))        # 发现int类也属于 type
print(type.__base__)    # 发现其父类是 object

类创建的两种方式

传统方式创建

# 我们传统的实现方式
class MyClass(object):
    def get(self) -> None:
        print("类获取了一个信息", self.info)
    info = "hello"

c = MyClass()
c.get() 
# 类获取了一个信息 hello
# 动态创建方式

def get(self) -> None:
    """定义类内部需要运行的函数"""
    print("类获取了一个信息", self.info)

MyClass = type("MyClass", (object, ), {"get": get, "info": "hello"})

c = MyClass()
if hasattr(c, "get"):  # 判断是否有get函数
    getattr(c, "get", None)()  # 获取get函数,并运行函数
else:
    print("该类内部没有get函数")
    
# 类获取了一个信息 hello

动态创建类

type 两种使用方式:

  • type(object) 获取对象类型。
  • type(name, bases, dict) 创建类对象。
Foo = type(
    "Foo",     #类的名字
    (object,), #继承的类
    
    #字典封装的成员
    {
        "v1": 123,     
        #通过lamba实现构造函数
        "__init__": lambda self: print("创建Foo"),
        "print": lambda self: print("调用实例函数"),
    }
)

obj = Foo()
print(obj.v1)
obj.print()
 
# 创建Foo
# 123
# 调用实例函数
class A(object):
     pass
 
print(A)
# <class '__main__.A'>                              类
print(A()) 
# <__main__.A object at 0x000002044DDC24C0>         对象

B=type("B", (object, ), dict())
print(B)

oB=type("B", (object, ), dict())()
print(oB)

# <class '__main__.B'>                               类
# <__main__.B object at 0x0000026356A8F0A0>          对象
import abc
import types


def __init__(self, name, shares, price):
    self.name = name
    self.shares = shares
    self.price = price


def cost(self):
    return self.shares * self.price


cls_dict = {
    '__init__': __init__,
    'cost': cost,
}

Stock = types.new_class(
    'Stock',  # 类名
    (),  	  # 父类
    {'metaclass': abc.ABCMeta},  # 元类及类定义参数
    lambda ns: ns.update(cls_dict)  # ns 为 prepare 方法(见上)返回的字典对象
)
Stock.__module__ = __name__

s = Stock('abc', 200, 35)
print(s)

什么是元类

MetaClass元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改

主要目的就是为了当创建类时能够自动地改变类

创建类的流程

img

函数做为元类

# 元类会自动获取通常传给`type`的参数
def upper_attr(_class, _object, _attr):
    """
      返回一个类对象,将其属性置为大写
    """

    # 过滤出所有开头不为'__'的属性,置为大写
    uppercase_attr = {}
    for name, val in _attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # 利用'type'创建类,同时将这个类进行返回
    return type(_class, _object, uppercase_attr)


class Foo(metaclass=upper_attr):  # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化
    bar = 'bip'

print(hasattr(Foo, 'bar'))  
print(hasattr(Foo, 'BAR'))

f = Foo()
print(f.BAR)

类做为元类

在有元类的情况下,每次创建类时,会都先进入 元类的 __new__ 方法,如果你要对类进行定制,可以在这时做一些手脚。

综上,元类的__new__和普通类的不一样:

元类的__new__ 在创建类时就会进入,它可以获取到上层类的一切属性和方法,包括类名,魔法方法。
而普通类的__new__ 在实例化时就会进入,它仅能获取到实例化时外界传入的属性。

class UpperAttrMetaClass(type):  # 注意,元类必须要继承type这个类
    """
      返回一个类对象,将其属性置为大写
    """
    def __new__(cls, *args, **kwargs):
        _class, _object, _attr = args  # 分别对应 类名,继承的类,属性
        uppercase_attr = {}
        # 过滤出所有开头不为'__'的属性,置为大写
        for name, val in _attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val
        return super(UpperAttrMetaClass, cls).__new__(cls, _class, _object, uppercase_attr)  
    # 使用type的new方法,相当于使用type创建类


class Foo(metaclass=UpperAttrMetaClass):  # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化
    bar = 'bip'

print(hasattr(Foo, 'bar'))  
print(hasattr(Foo, 'BAR'))

f = Foo()
print(f.BAR)

自定义元类

自定义元类主要是指,通过定义一个继承于 type 的元类,并重写 __new__ 方法来对类的创建过程实现定制。

其具体步骤分为:

  • 定义阶段:定义一个继承于 type 的元类,并编写 __new__ 方法
  • 使用阶段:在需要被定制的类中添加 metaclass=<your metaclass> 关键字参数
可以通过继承  type 类来定义元类。元类应该定义一个 __new__ 方法。这个方法负责接收类定义的参数,并返回一个类对象

class MyMeta(type):
    def __new__(cls, name, bases, dct):
        x = super().__new__(cls, name, bases, dct)
        x.attr = 100
        return x

class MyClass(metaclass=MyMeta):
    pass

print(MyClass.attr)
# 输出: 100

元类的应用场景

  • 自动添加属性或方法,拦截类的创建

  • 类初始化和注册,修改类的定义

  • 强制API一致性

单实例-元类

"""
  使用元类实现单例模式
  注意: 这种写法非线程安全!
"""

class Singleton(type):

    def __init__(cls, *args, **kwargs):
        cls._instance = None
        super().__init__(*args, **kwargs)

    def __call__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__call__(*args, **kwargs)
        return cls._instance
    
class Test(metaclass=Singleton):
    def test(self):
        print("test")

test1 = Test()
test2 = Test()

print(id(test1), id(test2)) 

# 4473306472 4473306472

对象缓存

对象创建代价比较大的时候,做对象缓存是个不错的选择,很多缓存是用专门的缓存池来完成,这里也可以直接用元类来做缓存。

import weakref


class CacheMeta(type):
    """
    使用元类做对象缓存处理
    """

    def __init__(cls, name, bases, namespace, **kwargs):
        super().__init__(name, bases, namespace)
        cls._cache = weakref.WeakValueDictionary()

    def __call__(cls, *args):
        # 传入的参数相同的,就给同一个对象,如果对应参数的对象还没创建就先创建对象
        if args in cls._cache:
            return cls._cache[args]
        obj = super().__call__(*args)
        cls._cache[args] = obj
        return obj


class CacheClass(metaclass=CacheMeta):
    def __init__(self, name):
        print('Creating instance({!r})'.format(name))
        self.name = name


if __name__ == '__main__':
    o1 = CacheClass('hello')
    o2 = CacheClass('world')
    o3 = CacheClass('hello')
    print(o1 is o2)
    print(o1 is o3)
    
# Creating instance('hello')
# Creating instance('world')
# False
# True

抽象类

抽象类是一种特殊的类,它只能有抽象方法,不能被实例化,

在子类继承抽象类时,不能通过实例化使用其抽象方法,必须实现该方法。

抽象类的作用

  • 一种规范 规定子类应该具备的功能

  • 抽象类可以实现多个子类中共用的部分,而不需要重复写到实现类中。

  • 目的让别的类继承它以及实现特定的方法

  • 从设计角度去看,抽象类是基于类抽象而来的,是实现类的基类。

  • 从实现角度来看,抽象类与普通类的不同之处在于:抽象类中有抽象方法,该类不能被实例化,只能被继承,且实现子类必须继承并实现抽象类的方法。

# abc.ABCMeta
import abc
class Base(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def read(self):
        pass

    @abc.abstractmethod
    def write(self):
        pass

class Http(Base, abc.ABC):
    pass
from abc import ABC, ABCMeta, abstractmethod

class Person(ABC):

    @staticmethod
    @abstractmethod
    def age_fudging(age):
        pass

    @classmethod
    @abstractmethod
    def weight_fudging(cls, weight):
        pass

    @property
    @abstractmethod
    def age(self):
        pass

    @age.setter
    @abstractmethod
    def age(self, val):
        pass

    @property
    @abstractmethod
    def weight(self):
        pass

    @weight.setter
    @abstractmethod
    def weight(self, val):
        pass
    
    
class BeiKe(Person):

    def __init__(self,age,weight):
        self._age = age
        self._weight = weight


    @staticmethod
    def age_fudging(age):
        return age - 10


    @classmethod
    def weight_fudging(cls, weight):
        return weight - 20

    @property
    def age(self):
        return BeiKe.age_fudging(self._age)

    
    @age.setter
    def age(self, age):
        print('设置age属性')
        self._age = age

    @property
    def weight(self):
        return self.weight_fudging(self._weight)

    @weight.setter
    def weight(self):
        return

y = BeiKe(31,100)
print("名字:贝壳 年龄:{0} 体重:{1}".format(y.age, y.weight))
setattr(y, "age", 49)
print("获取年龄:",y.age)

# 名字:贝壳 年龄:21 体重:80
# 设置age属性
# 获取年龄: 39

参考资料

https://blog.csdn.net/qq_62789540/article/details/127469186

https://www.cnblogs.com/tracydzf/p/15687310.html