Python 有什么奇技淫巧? - 知乎

2,319 阅读4分钟
原文链接: www.zhihu.com
1. 元类(metaclass)
PyPy的源码里有个pair和extendabletype
"""
Two magic tricks for classes:
    class X:
        __metaclass__ = extendabletype
        ...
    # in some other file...
    class __extend__(X):
        ...      # and here you can add new methods and class attributes to X
Mostly useful together with the second trick, which lets you build
methods whose 'self' is a pair of objects instead of just one:
    class __extend__(pairtype(X, Y)):
        attribute = 42
        def method((x, y), other, arguments):
            ...
    pair(x, y).attribute
    pair(x, y).method(other, arguments)
This finds methods and class attributes based on the actual
class of both objects that go into the pair(), with the usual
rules of method/attribute overriding in (pairs of) subclasses.
For more information, see test_pairtype.
"""

class extendabletype(type):
    """A type with a syntax trick: 'class __extend__(t)' actually extends
    the definition of 't' instead of creating a new subclass."""
    def __new__(cls, name, bases, dict):
        if name == '__extend__':
            for cls in bases:
                for key, value in dict.items():
                    if key == '__module__':
                        continue
                    # XXX do we need to provide something more for pickling?
                    setattr(cls, key, value)
            return None
        else:
            return super(extendabletype, cls).__new__(cls, name, bases, dict)


def pair(a, b):
    """Return a pair object."""
    tp = pairtype(a.__class__, b.__class__)
    return tp((a, b))   # tp is a subclass of tuple

pairtypecache = {}

def pairtype(cls1, cls2):
    """type(pair(a,b)) is pairtype(a.__class__, b.__class__)."""
    try:
        pair = pairtypecache[cls1, cls2]
    except KeyError:
        name = 'pairtype(%s, %s)' % (cls1.__name__, cls2.__name__)
        bases1 = [pairtype(base1, cls2) for base1 in cls1.__bases__]
        bases2 = [pairtype(cls1, base2) for base2 in cls2.__bases__]
        bases = tuple(bases1 + bases2) or (tuple,)  # 'tuple': ultimate base
        pair = pairtypecache[cls1, cls2] = extendabletype(name, bases, {})
    return pair

先说extendabletype。嘛 其实注释已经说得听明白了,就是一个C#里面的partial class的Python实现。
然后是pair和pairtype。pairtype就是根据两个类创建一个新的类,这个类继承自使用这两个类的基类构造的pairtype(有点绕……)或者tuple。

有啥用呢?可以拿来实现multimethod。

class __extend__(pairtype(int, int)):
    def foo((x, y)):
        print 'int-int: %s-%s' % (x, y)

class __extend__(pairtype(bool, bool)):
    def bar((x, y)):
        print 'bool-bool: %s-%s' % (x, y)

pair(False, True).foo()  # prints 'int-int: False, True'
pair(123, True).foo()  # prints 'int-int: 123, True'
pair(False, True).bar()  # prints 'bool-bool: False, True'
pair(123, True).bar()  # Oops, no such method

好像这个例子里元类只是个打辅助的角色,好玩的都在那个pair里……

再换一个。

class GameObjectMeta(type):
    def __new__(mcls, clsname, bases, _dict):
        for k, v in _dict.items():
            if isinstance(v, (list, set)):
                _dict[k] = tuple(v)  # mutable obj not allowed

        cls = type.__new__(mcls, clsname, bases, _dict)
        all_gameobjects.add(cls)
        for b in bases:
            game_objects_hierarchy.add((b, cls))

        return cls

    @staticmethod
    def _dump_gameobject_hierarchy():
        with open('/dev/shm/gomap.dot', 'w') as f:
            f.write('digraph {\nrankdir=LR;\n')
            f.write('\n'.join([
                '"%s" -> "%s";' % (a.__name__, b.__name__)
                for a, b in game_objects_hierarchy
            ]))
            f.write('}')

    def __setattr__(cls, field, v):
        type.__setattr__(cls, field, v)
        if field in ('ui_meta', ):
            return
    
        log.warning('SetAttr: %s.%s = %s' % (cls.__name__, field, repr(v)))

这个是从我写的三国杀游戏中提取的一段代码(点我签名上的链接)。大意就是把class上所有可变的容器都换成不可变的,然后记录下继承关系。
曾经被这个问题坑过,class上的值是全局共享的,逻辑代码一不小心修改了class上的值,单机测试的时候是测不出来的,然后放到线上……就悲剧了……当时绞尽脑汁没有想到是这个问题硬生生的回滚了……发现了问题之后就加上了这个东西,不允许修改class上的东西。
记录下继承关系是为了画类图。

还有就是常用的做数据注入

metadata = {}

def gen_metafunc(_for):
    def metafunc(clsname, bases, _dict):
        meta_for = getattr(_for, clsname)
        meta_for.ui_meta = UIMetaDescriptor()
        if meta_for in metadata:
            raise Exception('%s ui_meta redefinition!' % meta_for)

        metadata[meta_for] = _dict

    return metafunc

from gamepack.thb import characters

__metaclass__ = gen_metafunc(characters.sakuya)


class Sakuya:
    # 于是这个就不是类了, 而是作为数据存到了metadata这个dict里
    char_name = u'十六夜咲夜'
    port_image = 'thb-portrait-sakuya'
    figure_image = 'thb-figure-sakuya'
    miss_sound_effect = 'thb-cv-sakuya_miss'
    description = (
        u'|DB完全潇洒的PAD长 十六夜咲夜 体力:4|r\n\n'
        u'|G月时计|r:|B锁定技|r,准备阶段开始时,你执行一个额外的出牌阶段。\n\n'
        u'|G飞刀|r:你可以将一张装备牌当【弹幕】使用或打出。按此法使用的【弹幕】无距离限制。\n\n'
        u'|DB(画师:小D@星の妄想乡,CV:VV)|r'
    )
Ruby党不要喷,我知道你们可以做的更优雅……

2. Python沙盒逃逸
http://blog.delroth.net/2013/03/escaping-a-python-sandbox-ndh-2013-quals-writeup/
刷新三观的Python代码

3. PEP302 New Import Hook
最近在把刚才提到的纯Python游戏向Unity引擎上移植。
玩过Unity的就会知道,Unity的游戏的资源都是打包在一起的,没有单独的文件,Python解释器就不高兴了……于是写了import hook,用Unity提供的API来读py文件。

# -*- coding: utf-8 -*-

# -- stdlib --
import imp
import sys

# -- third party --
# -- own --
from clr import UnityEngine, WarpGateController

# -- code --

class UnityResourceImporter(object):
    known_builtin = (
        'sys',
        'imp',
        'cStringIO',
        'gevent_core',
        'gevent_ares',
        'gevent_util',
        'gevent_semaphore',
        'msgpack_packer',
        'msgpack_unpacker',
        'UnityEngine',
    )

    def __init__(self, bases, unity_loader):
        self.bases = bases
        self.last_fullname = ''
        self.last_text = ''
        self.last_ispkg = False
        self.unity_load = unity_loader

    def find_module(self, fullname, path=None):
        if fullname in sys.modules:
            return self

        head = fullname.split('.')[0]
        if head in self.known_builtin:
            return None

        rst = self.do_load_module(fullname)
        if rst:
            self.last_text, self.last_ispkg = rst
            self.last_fullname = fullname
            return self
        else:
            return None

    def load_module(self, fullname):
        if fullname in sys.modules:
            return sys.modules[fullname]

        if fullname != self.last_fullname:
            self.find_module(fullname)

        try:
            code = self.last_text
            ispkg = self.last_ispkg

            mod = sys.modules.setdefault(fullname, imp.new_module(fullname))
            mod.__file__ = "%s>" % fullname
            mod.__loader__ = self

            if ispkg:
                mod.__path__ = []
                mod.__package__ = fullname
            else:
                mod.__package__ = fullname.rpartition('.')[0]

            co = compile(code, mod.__file__, 'exec')
            exec(co, mod.__dict__)

            return mod
        except Exception as e:
            UnityEngine.Debug.LogError('Error importing %s%s' % (fullname, e))
            raise ImportError(e)

    def do_load_module(self, fullname):
        fn = fullname.replace('.', '/')

        asset = self.try_load(fn + '.py')
        if asset is not None:
            return asset, False

        asset = self.try_load(fn + '/__init__.py')
        if asset is not None:
            return asset, True

    def try_load(self, filename):
        for b in self.bases:
            asset = self.unity_load(b + filename)
            if asset is not None:
                return asset

        return None


sys.meta_path.append(UnityResourceImporter([
    'Python/THBattle/',
    'Python/Site/',
    'Python/Stdlib/',
], WarpGateController.GetTextAsset))
需要的extension module都静态编译到解释器里了,所以没考虑。

4. 可以批量执行操作的list

class BatchList(list):
    def __getattribute__(self, name):
        try:
            list_attr = list.__getattribute__(self, name)
            return list_attr
        except AttributeError:
            pass

        return list.__getattribute__(self, '__class__')(
            getattr(i, name) for i in self
        )

    def __call__(self, *a, **k):
        return list.__getattribute__(self, '__class__')(
            f(*a, **k) for f in self
        )

class Foo(object):
    def __init__(self, v):
        self.value = v

    def foo(self):
        print 'Foo!', self.value

foo = Foo(1)
foo.foo()  # Foo! 1

foos = BatchList(Foo(i) for i in xrange(10))
foos.value  # BatchList([0, 1, 2, 3, ..., 9])
foos.foo()  # 你能猜到的
这个其实不算很黑魔法了,只是感觉很好用也有些危险,所以放上来。

暂时就想到这么多了,以后发现了再补。