元编程(二)

之前的文章提到,使用type关键字可以用来动态地生成一个类,但是这样写实在是太麻烦了,需要创建一大堆的参数。大部分的动态创建,都可以简单地使用以下方法来实现。


    # Python 3
    def gen_class(type_):
        if type_ == 'Person':
            class Foo():
                def __init__(self):
                    print('I am a Person object')
        else:
            class Foo():
                def __init__(self):
                    print('I am not a Person object')
        return Foo

    Person = gen_class('person')
    p = Person()

但在一般的实践场景下,上面的代码都应该被尽量的避免,因为就单单地创建对象而言,不如直接定义两个类方便。(但也有意外,例如上层程序希望传递不同的值而获得相同类但不同的属性时)

更多的情况下,我们希望类实例创建时准守某个准则,比如对类的属性做检查,并自动将驼峰、大写命名的属性全部转换为小写。先来看看一般地做法是怎样的吧:


    # Python 3
    import re

    class Foo():

        def __init__(self):
            self.__get_standard_attr()

        def __get_standard_attr(self):
            attrs:list = dir(self)
            for attr in attrs:
                if hasattr(self, attr):
                    # 大写属性转小写
                    if attr.startswith('_'):
                        continue
                    attr_char_list = re.split(r'([A-Z])', attr)
                    setattr(self, self.__camel_to_underline(attr_char_list), getattr(self, attr))

        def __camel_to_underline(self, char_list = [], str_=''):
            if not char_list:
                return str_

            if not char_list[0]:
                char_list.pop(0)
                return self.__camel_to_underline(char_list, str_)
            str_ += char_list[0].lower()

            if len(char_list) > 1 and not char_list[1]:
                str_ += '_'
            elif len(char_list) > 1 and char_list[1].isupper():
                str_ += '_'
            else:
                pass

            char_list.pop(0)
            return self.__camel_to_underline(char_list, str_)



    class Test(Foo):

        camelAttr = 1


    t = Test()
    print(t.__dict__)

通过上述方法,我们悄悄地将类变量camelAttr悄悄地变成了camel_attr.(实际上是更改了对象t的成员变量名)。但是上述的方法有个显而易见的弊端,一旦我们给子类(Test)定义了初始函数(__ init __()), 其父类的属性转换就不会对子类起作用。(没错,Python的子类的构造方法默认不会执行父类的构造方法。子类的构造相当于重写父类的构造。)除非你每次都手动将父类的构造执行一次:


    class Test(Foo):

        def __init__(self):
            ...
            Foo.__init__(self)

但是,有没有更加简单的方案呢?

Python世界里的ORM映射

如果你使用Python做过Web应用开发,你应该对ORM比较熟悉,它将一个Model映射到数据库中的表中,并且封装了大量的SQL语句。有经验的你一定发现了,在一个Model中定义的属性,居然和最后取出来的不一样。例如:

    # Python3 flask-sqlalchemy
    db = SQLAlchemy(app)

    class Person(db.Model):
            name = db.Column(db.String(32))
            gender = db.Column(db.Enum(
                'male',
                'female'
            ))
            tel = db.Column(db.String(16)

    evink = Person(
        name='EvinK',
        gender='male',
        tel='131****2503'
    )

    ...

    # db.Column指向到这里
    class Column(SchemaItem, ColumnClause):
        """Represents a column in a database table."""
        pass

    evink.name # 这里居然是一个str,而不是一个db.Colum对象

SQLAlchemy中的这种实现,就得益于metaclass这个东西。在我的理解里,它可以被叫做类的模板,让实例的对象用程序员的构造模式被生成出来。一般在我们的实践中,很少会使用到这个东西,但是又由于它强大的特性,所以能帮助我们创造出很强大的框架类工具。又或者说,这个东西,是被程序员来使用并创造出可以让程序员来提高效率的东西,这样介绍,似乎真的有了’元’的味道。

再回到我们之前的问题,利用metaclass,似乎这道题有了一个完美的解决方案。

    # Python3

    def camel_to_underline(char_list = [], str_=''):
            if not char_list:
                return str_

            if not char_list[0]:
                char_list.pop(0)
                return camel_to_underline(char_list, str_)
            str_ += char_list[0].lower()

            if len(char_list) > 1 and not char_list[1]:
                str_ += '_'
            elif len(char_list) > 1 and char_list[1].isupper():
                str_ += '_'
            else:
                pass

            char_list.pop(0)
            return camel_to_underline(char_list, str_)

    def get_standard_attr(class_name, class_parent, class_attrs):
        new_attrs = {}
        for name,value in class_attrs.items():
            if not name.startswith('__'):
                attr_char_list = re.split(r'([A-Z])', name)
                name = camel_to_underline(attr_char_list)
                new_attrs[name] = value

        # 使用type创建一个新的对象
        return type(class_name, class_parent, new_attrs)

    class Foo(metaclass=get_standard_attr):
        camelAttr = []

    f = Foo()
    f.camelAttr # 'Foo' object has no attribute 'camelAttr'
    f.camel_attr # []

值得注意的是,这里继承的metaclass应该真的是一个类,而这里使用了一个type来模拟它。(可以看出Python中对象之间的转换是多么灵活……)

    # Python3
    # 一个真正的元类
    class StandardAttr(type):

        # 这里我们希望返回创建的对象,也就是类的模板
        # 所以使用了__new__方法
        def __new__(cls, class_name, class_parent, class_attrs):
            ...
            return cls

未完待续……

原文地址:>> https://code.evink.me/2018/11/post/Meta-Programming-Part-2/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值