OOP--元类

type()动态创建类

Python创建类:运行时动态创建

(内部创建)方法:type()

   type()接收参数:(calss名称,

           class继承的父类集合(如只有一个父类,参照tuple单元素写法(xxx,))

           class的方法名称与函数绑定)

Eg:

>>> def fn(self, name='world'): # 先定义函数
...     print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<class 'type'>
>>> print(type(h))
<class '__main__.Hello'>

一般使用时class Hello(object):......就可以创建

另外type还可用于检测对象类型

metaclass元类  又一创建类的魔法类,控制类的创建行为

扫描二维码关注公众号,回复: 10248569 查看本文章

一般顺序:先定义类,然后创建实例。

但如果想创建出类呢?:先定义metaclass,然后创建类。metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”

最终结论:先定义metaclass,就可以创建类,最后创建实例。

metaclass使用步骤(创建xxx类):

1. 定义XXXMetaclass()类

# metaclass是类的模板,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

__new__()方法接收参数:

    (当前准备创建的类的对象,

       类的名字,

        类继承的父类集合,

          类的方法集合)

2.创建XXX类

使用关键字metaclass,传入metaclass

作用:它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

class MyList(list, metaclass=ListMetaclass):
    pass

3.创建实例调用

>>> L = MyList()
>>> L.add(1)
>> L
[1]

所以问题来了,为什么不直接在Mylist中定义add方法呢?额一般来说就是这么做的,metaclass有更特殊的应用场景

metaclass应用场景之 —— ORM框架

定义:“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。

要编写一个ORM框架,所有的类都只能动态定义,方便添加(属性)改动表

假设使用者想要这样定义表-对象

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# 创建一个实例:
u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
# 保存到数据库:
u.save()
  • 直接在类中定义属性,调用相关类型函数传入属性名称,得到具有指定类型的属性
  • 父类Model和属性类型StringFieldIntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。

StringFiled,IntergerField等由单独类实现,步骤如下

1.首先来定义Field类,它负责保存数据库表的字段名和字段类型:

class Field(object):

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

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)

2.在Field的基础上,进一步定义各种类型的Field

class StringField(Field):

    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class IntegerField(Field):

    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

编写Model类的元类——ModelMetaclass

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        
     #当然这两句不是什么重点-------------
     print('Found model: %s' % name) mappings = dict()
     #---------------------
     #attrs是传入类的属性方法集合
     #k是属性或方法的类型
     #v是属性或方法的值
     #筛选出类型是Field(当然子类也算比如
StringField)的对象(比如id)
     for k, v in attrs.items():
        if isinstance(v, Field):#如果v是Field的实例对象
          print('Found mapping: %s ==> %s' % (k, v))
          mappings[k]
= v #将其添加到mapping
  
     for k in mappings.keys():#遍历mapping,将其从attr中pop
        attrs.pop(k)
     #最后为正在创建的对象/类,添加两个属性
        attrs['__mappings__'] = mappings # 保存属性和列的映射关系
        attrs['__table__'] = name # 假设表名和类名一致
        return type.__new__(cls, name, bases, attrs)

代码解释:

  1.排除掉对Model类的修改;

  2.当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);

  3.把表名保存到__table__中,这里简化为表名默认为类名。

!1:第一个for循环有点难搞,贴一个小test

(出自:https://www.liaoxuefeng.com/discuss/969955749132672/1092153449118144

class ModelMetaclass(type):
    def __new__(cls, name, bases, attrs):
        for k, v in attrs.items():
            print('key:%s,value:%s' % (k, v))
        return type.__new__(cls, name, bases, attrs)

class Test(object, metaclass=ModelMetaclass):
    name = "dlj"
    age = 123

    def aaa(self):
        pass
a = Test() 打印结果: key:name,value:dlj key:__module__,value:__main__ key:aaa,value:<function Test.aaa at 0x109fa10d0> key:age,value:123 key:__qualname__,value:Test

按照后续调用(在Model子类中)比如

class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

得到的key: password , value:<StringField:password>,这个可以翻到最后看结果,value格式在Field中定义过

!2:Python类的__new__()方法和metaclass的__new__()是有区别滴

(出自:https://blog.csdn.net/oqqZhe1234/article/details/95944314

区别1:作用不同

  类的__new()__ 方法是返回一个对象;

  (解说见https://blog.csdn.net/qq_41020281/article/details/79638370

  而metaclass是返回一个类(其实类也是type的一个对象,万物皆为对象啊

区别2:时间不同:

  前者在生成对象的时候调用,后者在定义类的时候调用。

类中定义__new()__的优点:

  通过在父类中定义__new()__,子类在生成对象的时候可以临时修改该子类的类属性(注意这里是临时! 因为每定义一个子类对象都会运行___new()__);而使用metaclass需要定义一个父类和一个metaclass,比较麻烦,代码也不简洁。

使用metaclass的优点:

  在定义子类的时候就执行了metaclass的__new()__,不用在每生成一个对象的时候分别再调用,比上者效率要高。虽然还得多写一个metaclass,代码也不简洁,但是metaclass还是优先提倡的。

编写基类Model:

class Model(dict, metaclass=ModelMetaclass):

    def __init__(self, **kw):
        super(Model, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

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

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            params.append('?')
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

代码解释

1.super(Model, self).__init__(**kw),初始化时设置指定属性-值

首先找到Model的父类(dict),然后把类Model的对象self转换为类dict的对象,然后转换后的dict对象调用__init__,传入关键字参数**kw

2.__getattr__和__setattr__实现动态存取属性-值

3.在Model类中,定义各种操作数据库的方法,比如save()delete()find()update

代码中实现了save函数--Insert操作

for循环:

  首先遍历属性列表,将属性放入fields

  占位符?放入params

  参数放入args,不存在返回None

创建sql语句:

  '分隔符'.join(要连接的序列)  将序列中的元素以指定的字符连接生成一个新的字符串。(','.join(['a','b','c','d']->'a,b,c,d'))

我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

Q:当用户定义一个class User(Model)时,Python解释器做了什么?

首先在当前类User的定义中查找metaclass

  如果没有找到,就继续在父类Model中查找metaclass

  找到了,就使用Model中定义的metaclassModelMetaclass来创建User类,

也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

最后是测试代码:

u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
u.save()

输出如下:

Found model: User
Found mapping: email ==> <StringField:email>
Found mapping: password ==> <StringField:password>
Found mapping: id ==> <IntegerField:uid>
Found mapping: name ==> <StringField:username>
SQL: insert into User (password,email,username,id) values (?,?,?,?)
ARGS: ['my-pwd', '[email protected]', 'Michael', 12345]

Over...这分析真是让人头秃

猜你喜欢

转载自www.cnblogs.com/jpga/p/12586805.html
OOP