Ruby 魔幻的语法对于写抽象轮子(如 ORM、Web 框架)似乎如鱼得水。
很多魔幻特性对于 Python 这样一个简约语言好像难以实现,那么各位在造轮子时如何模拟实现这些魔幻特性的呢?
(我很同意 RoR 的观点,外部接口要简单、内部实现要魔幻 ~)
或者,你贴出一段高度抽象、玄机、黑魔法、充分利用 Python 自身语言特点的代码?
(如:利用 yield 、利用 Magic method、利用元类、利用函数也是一个对象、扩展语言自带对象、利用装饰圈等等等)
另外,我想请教一下我遇到的这两个问题要如何解决:
1
bingwenshi 2015-08-16 14:08:09 +08:00
> 或者,你贴出一段高度抽象、玄机、黑魔法、充分利用 Python 自身语言特点的代码?
除了装逼,还有什么意义么? 真正好的代码是可读的代码 |
2
GPU 2015-08-16 14:09:56 +08:00
先收藏 。看能不能养肥。
|
3
Feiox OP @bingwenshi 比如,这样做是不是装?
用装饰器将一个函数标记,初始化模块的时候遍历该模块中这部分被标记的函数,并对其做进一步操作 形如: @ my_decorate def func(): pass 这功能利用了 function 也是对象这一特点,动态添加其属性 func.__dict__['be_add'] = True |
4
AlexaZhou 2015-08-16 14:13:56 +08:00
个人认为实现要优雅,顺带着可以魔幻一把,然而为了魔幻而魔幻并没有意义
|
5
justahappy 2015-08-16 14:14:16 +08:00
|
6
Feiox OP @AlexaZhou
动态语言大多有自省这一神奇的语言特性,很久看过一个帖子 http://www.cnblogs.com/huxi/archive/2011/01/02/1924317.html 将 Python 自省的,感觉不错,现在工作了用的也多。 我们团队在写各种抽象工具的时候,代码确实魔幻了一些,但那些库对外的接口一般的经过多轮讨论确定规范、无歧义、完全解耦、简单才会放给开发组用。(尤其是上了 DSL 之后,内部实现更魔幻了哈哈)所以,我们经常维护的业务代码确实可读性很有必要,但工具库的实现,咱 Python 到底可以完成什么样子呢? @justahappy 这帖子 ~ 额 ~ 咱是好孩子不吵架的 :P |
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__等函数,简化了使用库的成本。 |
8
gamexg 2015-08-16 14:27:16 +08:00
并不魔幻的一个例子
socket5代理实例 提供的接口 = socket module。也就是完全把代理实例当作 socket module 使用,多重代理嵌套也不需要特殊操作。 如果一只鸟走起来像鸭子,叫起来像鸭子,那我们就可以把它当作是鸭子了 |
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 |
10
Feiox OP @mengzhuo 额,您所说的问题我也听说过、见过。最原始的做法,是不是就是指的仅用基本的语句,高级特性(如自省、迭代器、生成器、装饰器)都不用呢?
不过我个人认为,可读性并不一定是指这样的。我们在工作中,规范上明确讲业务实现要代码、命名明确,抽象工具库的接口要简单易懂,抽象工具库的实现要多注释和文档、多测试。这样最容易出错和更改的业务部分保持代码的易读,接口调用明确。这样的代码可读性也是非常好的。 额,我从业时间短不敢多说,但,感觉还是和团队风格是不是有一些关系? |
11
neoblackcap 2015-08-16 15:47:00 +08:00
同理,用Python写DSL肯定不如ruby好看(语法上,ruby解析器可忽略括号导致DSL看起来可以更接近自然语言),若是要像ruby一样看起,那么久要用ast包。
|
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 } } |
13
virusdefender 2015-08-16 16:51:44 +08:00
meta
|
15
xiazi 2015-08-16 18:58:12 +08:00
不要用继承来当接口,不要有很多的抽象层
内部实现尽量扁平化,方便debug,方便测试 |
16
Feiox OP @xiazi 抽象层的问题,我看到有很多人批评《重构》那本书中的观点,同样的有很多人反对设计模式。
但,至少我看到过的很多代码,将业务逻辑放给新员工,抽象层放给优秀的员工,这样可以更好的解决团队代码质量的问题。并且,很多包含技巧、重复的代码出现在业务层面,难道不是更容易犯错吗。对于 debug 和 测试,良好的构架和抽象可以分离不同层级的代码,降低耦合性,低耦合之后不是反而更容易测试吗? 不过,Python 的函数调用开销很多,这也是我一直头疼的。 |
17
chengzhoukun 2015-08-16 19:56:49 +08:00
|
18
lcqtdwj 2015-08-16 20:22:15 +08:00
1.感觉要分使用的场景
2.类方法第一个参数就是class,在decorate里应该能轻易捕获吧 我跟LZ想法一样,内部的魔幻是为了使用接口的简便。python之所以这么容易上手,就是因为有那么多现成的接口简单的库。库作者都很伟大呢 |
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就可以算出来了( ̄▽ ̄) |
20
nightv2 2015-08-16 23:05:10 +08:00
过两天看看有没有什么不认识的东西
|
21
xierch 2015-08-17 00:07:46 +08:00
monkey patch 算魔幻么
|
22
wyxfcy 2015-08-17 00:52:02 +08:00
难道不是hylang?: http://docs.hylang.org/en/latest/
|
23
xiaket 2015-08-17 09:19:10 +08:00
顺着这个主题推荐Pro Python和Fluent Python这两本书
|
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 |
25
poke707 2015-08-17 13:43:17 +08:00
解答 LZ 的最后提到的问题
1.在 function 定义内调用 inspect.getargspec (<function_name>), 2.在类定义时,类方法还只是普通 function ,类定义完成后才加工成类方法(有 im_class,im_self 等属性),所以在装饰器拿到的并不包含类信息。个人认为无法,若有望告之。 |
26
msg7086 2015-08-17 15:10:49 +08:00 via Android
rubyu 和 python 的理念相差太远。
写 py 就应该代码干净清晰易懂,写 ruby 就应该写得魔幻然后让测试代码来保证正确性。 |
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 函数。 |
28
invite 2015-08-18 07:28:44 +08:00 via Android
看完回复,果断学 python
|
29
Feiox OP @xiazi 哈哈,感谢你提供高质量讨论。
在我有限的职业生涯中高手菜鸟都见过,对于 DRY 、 KISS 这些编程箴言我个人觉得更多的是指导菜鸟,因为他们的代码往往重复、混杂。对于合格工程师,自然不必太强调这些,而对于他们,优雅的接口设计和稳健的代码实现才是他们关注的重点。对于抽象的层次,我们目前采用的方式是对非业务的通用功能抽象、对业务代码少抽象。比如,在目前项目中,我们的业务逻辑部分大多是直白的代码描述和接口调用,但对在权限管理、处理缓存、数据处理、异步操作(请求其他接口)、计数器等操作往往都是抽象出来。但同时,为了避免过度抽象,我们还要求在模型定义、业务接口调用等方面禁止使用继承。(对于王垠,在我学识尚浅的时候还是很崇拜他的,但后来玩的多了,发现他虽然还是正确的,但他讨论的层次(除编译优化)往往并不深入) 对于 with 等语言元素,用不用我想是属于个人风格的问题吧。大家喊了 lambda 这么多年 GvR 就是不改,他也算偏执的追求简洁吧,但 with enum 这些关键词、标准库,我想他们一定是经过设计者深思熟虑的存在的非常有意义的。对于 explicit is better than implicit 这个,我看过有些人写的代码,使用 value[n] 引用的方式修改不该修改的内容,在业务代码里写多层嵌套,滥用装饰器、闭包等特性,我也是反对的。 reactjs 所倡导的组件化,我也是支持的。多用接口,少用继承。接口可以保证调用明确,继承往往存在隐式转换(这也是我不喜欢 C++ Java 的原因) 我不喜欢 Ruby 的魔幻,热爱 Python 的简洁。 P.S 我是支持 Python 3 的。 |