美文网首页
元编程(二)

元编程(二)

作者: EvinK | 来源:发表于2018-11-22 22:13 被阅读0次

    之前的文章提到,使用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__)
    
    

    通过上述方法,我们悄悄地将类变量<e>camelAttr</e>悄悄地变成了camel_attr.(实际上是更改了对象<e>t</e>的成员变量名)。但是上述的方法有个显而易见的弊端,一旦我们给子类(<e>Test</e>)定义了初始函数(<e>__ init __()</e>), 其父类的属性转换就不会对子类起作用。(没错,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中的这种实现,就得益于<e>metaclass</e>这个东西。在我的理解里,它可以被叫做类的模板,让实例的对象用程序员的构造模式被生成出来。一般在我们的实践中,很少会使用到这个东西,但是又由于它强大的特性,所以能帮助我们创造出很强大的框架类工具。又或者说,这个东西,是被程序员来使用并创造出可以让程序员来提高效率的东西,这样介绍,似乎真的有了'元'的味道。

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

        # 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/

    相关文章

      网友评论

          本文标题:元编程(二)

          本文链接:https://www.haomeiwen.com/subject/mxgrqqtx.html