V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
wh0syourda66y
V2EX  ›  Python

用闭包和元编程技巧给 Python 对象加上索引

  •  
  •   wh0syourda66y · 2015-04-07 15:47:44 +08:00 · 2462 次点击
    这是一个创建于 3549 天前的主题,其中的信息可能已经有所发展或是发生改变。

    0x00.引子

    在看《Expert Python Programming》看到Meta-programming这章,一下子玩high了,为了度过漫长的节后综合症恢复期,这里记录一下玩乐的过程。

    0x01 初始化之前的代码执行

    new方法是一个‘元构建器’(meta-constructor),每当一个对象被实例化时都会调用之。

    class MyClass(object):
        def __new__(cls):
            print('{0}.__new__()'.format(cls.__name__))
            obj = object.__new__(cls)
            return obj
    
        def __init__(self):
            print('{0}.__init__()'.format(self.__class__))
    
    In [7]: mclass = MyClass()
    MyClass.__new__()
    <class '__main__.MyClass'>.__init__()
    

    继承自该类的子类自然也有相应的表现:

    class SubClass(MyClass):
        pass
    
    In [9]: sclass = SubClass()
    SubClass.__new__()
    <class '__main__.SubClass'>.__init__()
    

    这里可以看到new是比init更早调用的,也就是说newinit更加底层,甚至在对象还没有创建的时候就可以工作,这对于需要隐式地初始化对象状态是很好的解决方案。

    0x02 动态定义的方法

    在Python中定义一个方法可以是这样:

    def MyFunc():
        print('calling Myfunc')
    
    In [12]: MyFunc()
    calling Myfunc
    

    也可以是这样

    def gen_MyFunc():
        print('defining Myfunc')
        def inner_func():
            print('calling Myfunc')
        return inner_func
    
    In [16]: MyFunc = gen_MyFunc()
    defining Myfunc
    
    In [17]: MyFunc()
    calling Myfunc
    

    0x03 动态定义的类

    在Python中,类是一种type,type本身也是一个特殊的类,而类本身就是对象。

    In [46]: MyClass.__class__
    Out[46]: type
    
    In [44]: "a".__class__
    Out[44]: str
    
    In [45]: "a".__class__.__class__
    Out[45]: type
    
    In [37]: type.__bases__
    Out[37]: (object,)
    
    In [42]: isinstance(type,object)
    Out[42]: True
    

    所以我们可以这么定义一个类:

    MyClass = type('MyClass',(object,),{'__init__':lambda self:None'})
    

    等价于

    class MyClass(object):
        def __init__(self):
            pass
    

    0x04 闭包

    闭包是一组函数和存储上下文的组合。这个上下文(context)是这个函数被定义时的所处作用域中包含的引用集合。在Python中,这个环境被存储在一个cell的tuple中。你能够通过funcclosure或Python 3中的closure_属性访问它。这里就不详细展开了。

    def func_closure():
        inner_var = 'inner var'
        def inner_func():
            print('accessing '+inner_var)
        return inner_func
    
    In [71]: func_closure()()
    accessing inner var
    

    0x05 《let over lambda》

    接下来我们要在上面这些功能的基础上,实现一个类似 ‘计划生育’ 的功能。借助这个功能,我们可以统计每个类实例化出的所有实例引用,并且可以通过这个引用操作所有类的实例。这样是不是很令人心动呢?

    简单的思路如下:

    • 闭包+动态类+动态方法+new操作符+weakref

    不多说了,看代码:

    # -*- coding=utf8 -*-
    import weakref
    
    def gen_class():
        # closure variables
        birth_hash = []
    
        def __new__(cls):
            #cls.saybefore_create()
            obj = object.__new__(cls)
            cls.save_birth_hash(obj)
            return obj
    
        def __init__(self):
            pass
    
        def method(self):
            print(self.__class__)
    
        @classmethod
        def saybefore_create(cls):
            print('hi,',cls)
    
        @classmethod
        def save_birth_hash(cls,obj):
            obj_ref = weakref.ref(obj)
            birth_hash.append(obj_ref)
    
        @classmethod
        def get_birth_hash(cls):
            return birth_hash
    
        return type('MyClass',(object,),{'__new__':__new__,'__init__':__init__,'method':method,'saybefore_create':saybefore_create,'save_birth_hash':save_birth_hash,'get_birth_hash':get_birth_hash})
    

    And we play with it!

    In [86]: MyClass = gen_class()
    
    In [87]: a = MyClass()
    
    In [88]: b = MyClass()
    
    In [89]: c = MyClass()
    
    In [90]: a.get_birth_hash()
    Out[90]:
    [<weakref at 0000000003579278; to 'MyClass' at 0000000003570CF8>,
     <weakref at 0000000003579368; to 'MyClass' at 00000000035707F0>,
     <weakref at 0000000003579458; to 'MyClass' at 0000000003570F28>]
    
    In [91]: a.get_birth_hash()[1]() == b
    Out[91]: True
    

    到这里其实碰到了一个不大不小的问题,就是不能及时的清除失效的对象引用,如果直接重写 del()会破坏gc,如果有什么好的方法,请不吝告知。

    EOF

    2 条回复    2015-04-13 09:06:03 +08:00
    origingodoh
        1
    origingodoh  
       2015-04-08 09:37:31 +08:00
    没弄清楚,难道不能直接用类变量来记录么?类变量也是所有实例共享的。
    sing1ee
        2
    sing1ee  
       2015-04-13 09:06:03 +08:00
    也一直是这样用的,不过感觉还是没有理清楚。这块儿值得深入讨论
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1687 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 22ms · UTC 16:35 · PVG 00:35 · LAX 08:35 · JFK 11:35
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.