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
Feiox
V2EX  ›  Python

如何为 Python 添加魔幻语言特性?

  •  
  •   Feiox ·
    feiox · 2015-08-16 13:59:01 +08:00 · 5159 次点击
    这是一个创建于 3420 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Ruby 魔幻的语法对于写抽象轮子(如 ORM、Web 框架)似乎如鱼得水。
    很多魔幻特性对于 Python 这样一个简约语言好像难以实现,那么各位在造轮子时如何模拟实现这些魔幻特性的呢?
    (我很同意 RoR 的观点,外部接口要简单、内部实现要魔幻 ~)

    或者,你贴出一段高度抽象、玄机、黑魔法、充分利用 Python 自身语言特点的代码?
    (如:利用 yield 、利用 Magic method、利用元类、利用函数也是一个对象、扩展语言自带对象、利用装饰圈等等等)


    另外,我想请教一下我遇到的这两个问题要如何解决:

    1. 如何实现:读取函数定义时的形式参数名?不使用关键词参数时获取传入的参数名?
    2. 当用装饰器装饰一个类方法时,如何获取该类方法所属的类呢?
    30 条回复    2015-08-18 09:33:57 +08:00
    bingwenshi
        1
    bingwenshi  
       2015-08-16 14:08:09 +08:00
    > 或者,你贴出一段高度抽象、玄机、黑魔法、充分利用 Python 自身语言特点的代码?

    除了装逼,还有什么意义么? 真正好的代码是可读的代码
    GPU
        2
    GPU  
       2015-08-16 14:09:56 +08:00
    先收藏 。看能不能养肥。
    Feiox
        3
    Feiox  
    OP
       2015-08-16 14:12:46 +08:00
    @bingwenshi 比如,这样做是不是装?

    用装饰器将一个函数标记,初始化模块的时候遍历该模块中这部分被标记的函数,并对其做进一步操作
    形如:
    @ my_decorate
    def func(): pass
    这功能利用了 function 也是对象这一特点,动态添加其属性 func.__dict__['be_add'] = True
    AlexaZhou
        4
    AlexaZhou  
       2015-08-16 14:13:56 +08:00
    个人认为实现要优雅,顺带着可以魔幻一把,然而为了魔幻而魔幻并没有意义
    justahappy
        5
    justahappy  
       2015-08-16 14:14:16 +08:00
    @bingwenshi
    > 真正好的代码是可读的代码
    搞得你是大神一样。。。。
    这种套话就跟“语言只是工具”一样,你有资格说这种话?你设计过几门语言?
    装逼?你有资格装么?装个我看看?
    Feiox
        6
    Feiox  
    OP
       2015-08-16 14:21:18 +08:00
    @AlexaZhou
    动态语言大多有自省这一神奇的语言特性,很久看过一个帖子 http://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html 将 Python 自省的,感觉不错,现在工作了用的也多。
    我们团队在写各种抽象工具的时候,代码确实魔幻了一些,但那些库对外的接口一般的经过多轮讨论确定规范、无歧义、完全解耦、简单才会放给开发组用。(尤其是上了 DSL 之后,内部实现更魔幻了哈哈)所以,我们经常维护的业务代码确实可读性很有必要,但工具库的实现,咱 Python 到底可以完成什么样子呢?
    @justahappy 这帖子 ~ 额 ~ 咱是好孩子不吵架的 :P
    nkssai
        7
    nkssai  
       2015-08-16 14:25:00 +08:00
    其实你的意思就是用Python写DSL嘛。Python写DSL的能力应该和Ruby相当,不过有些地方写起来不那么漂亮而已。简短的例子的话,前两天正好用了https://github.com/michaelliao/sinaweibopy/blob/master/weibo.py
    这个挺简单的,依靠__getattr__等函数,简化了使用库的成本。
    gamexg
        8
    gamexg  
       2015-08-16 14:27:16 +08:00
    并不魔幻的一个例子

    socket5代理实例 提供的接口 = socket module。也就是完全把代理实例当作 socket module 使用,多重代理嵌套也不需要特殊操作。

    如果一只鸟走起来像鸭子,叫起来像鸭子,那我们就可以把它当作是鸭子了
    mengzhuo
        9
    mengzhuo  
       2015-08-16 15:10:15 +08:00   ❤️ 1
    至今见过最魔幻的应该算pony ORM:
    select(c for c in Customer if sum(c.orders.price) > 1000)
    直接转成SQL

    从业三年表示楼主算典型的做太少,想太多
    现实团队中web、game server所有用Python的,都是趋向于原始的做法
    因为连列表推导都会debug困难,没办法trace,虽然不牛,但是朴实,明确,
    也正是this中的:Readability counts.

    每当团队里有人写了不明确的推导,不合理地使用metaclass,我都会苦口婆心地劝他多读读this
    Feiox
        10
    Feiox  
    OP
       2015-08-16 15:22:45 +08:00
    @mengzhuo 额,您所说的问题我也听说过、见过。最原始的做法,是不是就是指的仅用基本的语句,高级特性(如自省、迭代器、生成器、装饰器)都不用呢?
    不过我个人认为,可读性并不一定是指这样的。我们在工作中,规范上明确讲业务实现要代码、命名明确,抽象工具库的接口要简单易懂,抽象工具库的实现要多注释和文档、多测试。这样最容易出错和更改的业务部分保持代码的易读,接口调用明确。这样的代码可读性也是非常好的。
    额,我从业时间短不敢多说,但,感觉还是和团队风格是不是有一些关系?
    neoblackcap
        11
    neoblackcap  
       2015-08-16 15:47:00 +08:00
    同理,用Python写DSL肯定不如ruby好看(语法上,ruby解析器可忽略括号导致DSL看起来可以更接近自然语言),若是要像ruby一样看起,那么久要用ast包。
    yakczh
        12
    yakczh  
       2015-08-16 16:51:43 +08:00
    求1-4之间的整数加起来之和等于5的全排列
    ----------------------
    for a in (0..4) do
    for b in (0..4) do
    for c in (0..4) do
    for d in (0..4) do

    if a+b+c+d ==5 then
    print a,b,c,d
    puts
    end

    end
    end
    end
    end

    vs
    ------------------------
    (0..4).each{|i| (0..4).to_a.permutation(i).map{|x| print x if x.inject(&:+)==5 ;puts } }
    virusdefender
        13
    virusdefender  
       2015-08-16 16:51:44 +08:00
    meta
    9hills
        14
    9hills  
       2015-08-16 17:56:46 +08:00 via iPad
    @Feiox 不是不用,而是不要刻意用。

    Pythonic
    xiazi
        15
    xiazi  
       2015-08-16 18:58:12 +08:00
    不要用继承来当接口,不要有很多的抽象层

    内部实现尽量扁平化,方便debug,方便测试
    Feiox
        16
    Feiox  
    OP
       2015-08-16 19:56:25 +08:00
    @xiazi 抽象层的问题,我看到有很多人批评《重构》那本书中的观点,同样的有很多人反对设计模式。
    但,至少我看到过的很多代码,将业务逻辑放给新员工,抽象层放给优秀的员工,这样可以更好的解决团队代码质量的问题。并且,很多包含技巧、重复的代码出现在业务层面,难道不是更容易犯错吗。对于 debug 和 测试,良好的构架和抽象可以分离不同层级的代码,降低耦合性,低耦合之后不是反而更容易测试吗?
    不过,Python 的函数调用开销很多,这也是我一直头疼的。
    chengzhoukun
        17
    chengzhoukun  
       2015-08-16 19:56:49 +08:00
    http://pycon.b0.upaiyun.com/ppt/shell909090-meta-class.html

    这是一篇介绍Python元编程讲得比较好的
    lcqtdwj
        18
    lcqtdwj  
       2015-08-16 20:22:15 +08:00
    1.感觉要分使用的场景
    2.类方法第一个参数就是class,在decorate里应该能轻易捕获吧

    我跟LZ想法一样,内部的魔幻是为了使用接口的简便。python之所以这么容易上手,就是因为有那么多现成的接口简单的库。库作者都很伟大呢
    mengzhuo
        19
    mengzhuo  
       2015-08-16 21:42:24 +08:00   ❤️ 1
    @9hills 对的呢

    @Feiox

    刻意在不需要的场合用这些“高级货”,有点像内功不够,偏学最高级的武功,会走火入魔的

    但是不是说现实编程中不需要
    比如:
    1. 一个实例中某个属性,必须通过计算得出,但又不是每次调用实例都需要,那么你就需要lazy object
    2. 某个函数接口需要调权限校验,那么你就需要decorator
    3. web中最常见的,session的读写、函数执行计时,需要middleware、singleton、local.local
    4. 如果要编写ORM,那么metaclass是必须要会的
    5. 如果做网络编程,那么协程是必须要要会的

    最重要的其实还是算法,数据结构( ´ ▽ ` )ノ(算法苦手……)

    比如,最近工作中我在实现一个函数:抽奖N次(>200),每次抽奖大于指定概率就发奖。
    有同事就用了yield做生成器循环并用gevent.sleep(0)切出协程,看起来节省时间,并且了解了gevent协程的特性
    但是实际上……
    因为是二项分布,所以只需要python的一次random.gauss就可以算出来了( ̄▽ ̄)
    nightv2
        20
    nightv2  
       2015-08-16 23:05:10 +08:00
    过两天看看有没有什么不认识的东西
    xierch
        21
    xierch  
       2015-08-17 00:07:46 +08:00
    monkey patch 算魔幻么
    wyxfcy
        22
    wyxfcy  
       2015-08-17 00:52:02 +08:00
    难道不是hylang?: http://docs.hylang.org/en/latest/
    xiaket
        23
    xiaket  
       2015-08-17 09:19:10 +08:00
    顺着这个主题推荐Pro Python和Fluent Python这两本书
    saber000
        24
    saber000  
       2015-08-17 13:14:42 +08:00
    > 如何实现:读取函数定义时的形式参数名?不使用关键词参数时获取传入的参数名?
    [n for i, n in zip (
    range (func.func_code.co_argcount ),
    func.func_code.co_varnames
    )]
    # func 是个函数

    > 当用装饰器装饰一个类方法时,如何获取该类方法所属的类呢?
    装饰器模式不是能拿到 self 吗?self.__class__不就是了?或者改成 descriptor
    poke707
        25
    poke707  
       2015-08-17 13:43:17 +08:00
    解答 LZ 的最后提到的问题
    1.在 function 定义内调用 inspect.getargspec (<function_name>),
    2.在类定义时,类方法还只是普通 function ,类定义完成后才加工成类方法(有 im_class,im_self 等属性),所以在装饰器拿到的并不包含类信息。个人认为无法,若有望告之。
    msg7086
        26
    msg7086  
       2015-08-17 15:10:49 +08:00 via Android
    rubyu 和 python 的理念相差太远。
    写 py 就应该代码干净清晰易懂,写 ruby 就应该写得魔幻然后让测试代码来保证正确性。
    xiazi
        27
    xiazi  
       2015-08-17 20:13:50 +08:00
    @Feiox
    我觉得抽象层会让以后添加新功能很麻烦(因为新功能一般需要改动底层的东西),还有就是抽象层多了 call stack 会很深,对于代码的非原作者了解代码很费时间(因为大部分情况除了看 API 的文档外,还要看源代码才能理解这个函数的具体作用)。我觉得这篇 blog 的观念很好: http://www.yinwang.org/blog-cn/2015/06/14/dry-principle/

    对于代码的复用我觉得 reactjs 里面的 component specs 的方式就比 subclass 的好, component 定义的任何变量或方法都不会跟父类冲突,父类决定了那些方法能被子 component override ,那些是被 chained call 等.

    一般魔幻的东西会隐藏很多内部的 call ,这一点我觉得有背于 explicit is better than implicit 。比如 with 的作用大部分相当于 try finally ,我宁愿直接用 try finally 。大部分 OOP 语言的 this 也是,而 python 里的 self 就比 this 灵活得多。

    问题 1. 可以参考: https://github.com/geertj/gruvi/blob/master/lib/gruvi/util.py#L52 其中的 wrap 函数。
    invite
        28
    invite  
       2015-08-18 07:28:44 +08:00 via Android
    看完回复,果断学 python
    Feiox
        29
    Feiox  
    OP
       2015-08-18 09:22:05 +08:00
    @xiazi 哈哈,感谢你提供高质量讨论。
    在我有限的职业生涯中高手菜鸟都见过,对于 DRY 、 KISS 这些编程箴言我个人觉得更多的是指导菜鸟,因为他们的代码往往重复、混杂。对于合格工程师,自然不必太强调这些,而对于他们,优雅的接口设计和稳健的代码实现才是他们关注的重点。对于抽象的层次,我们目前采用的方式是对非业务的通用功能抽象、对业务代码少抽象。比如,在目前项目中,我们的业务逻辑部分大多是直白的代码描述和接口调用,但对在权限管理、处理缓存、数据处理、异步操作(请求其他接口)、计数器等操作往往都是抽象出来。但同时,为了避免过度抽象,我们还要求在模型定义、业务接口调用等方面禁止使用继承。(对于王垠,在我学识尚浅的时候还是很崇拜他的,但后来玩的多了,发现他虽然还是正确的,但他讨论的层次(除编译优化)往往并不深入)

    对于 with 等语言元素,用不用我想是属于个人风格的问题吧。大家喊了 lambda 这么多年 GvR 就是不改,他也算偏执的追求简洁吧,但 with enum 这些关键词、标准库,我想他们一定是经过设计者深思熟虑的存在的非常有意义的。对于 explicit is better than implicit 这个,我看过有些人写的代码,使用 value[n] 引用的方式修改不该修改的内容,在业务代码里写多层嵌套,滥用装饰器、闭包等特性,我也是反对的。

    reactjs 所倡导的组件化,我也是支持的。多用接口,少用继承。接口可以保证调用明确,继承往往存在隐式转换(这也是我不喜欢 C++ Java 的原因)

    我不喜欢 Ruby 的魔幻,热爱 Python 的简洁。 P.S 我是支持 Python 3 的。
    Feiox
        30
    Feiox  
    OP
       2015-08-18 09:33:57 +08:00
    @poke707 我发现了,取到的都是函数对象 ~
    @lcqtdwj 并不是这样的,你可以自己试一试。原因是 @poke707 所说的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5649 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 03:10 · PVG 11:10 · LAX 19:10 · JFK 22:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.