Welcome

使用原类来改变方法属性的调用

最近在看github里边自己之前star的项目中看到了idiorm项目,这个项目是一个轻量级的数据库ORM,是php编写的,
里边有一个比较吸引我的地方是在调用方法的时候可以通过下划线或者是驼峰的方式来调用都是可以的

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
require_once "idiorm.php";
ORM::configure('mysql:host=localhost;dbname=d_blog');
ORM::configure('username', 'root');
ORM::configure('password', 'yxs');
// 驼峰的写法
$migrations = ORM::forTable('django_migrations')->where('app', 'auth')->findMany();
// 下划线的写法
$migrations = ORM::for_table('django_migrations')->where('app', 'auth')->find_many();

到源码中查看发现,这个实现是通过php的魔术方法__call来实现的,这个方式是当php对象调用到当前对象不存在的方法属性的时候触发的,触发了这个之后重写了方法的名称,从而实现了两种写法的适配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
class ORM implements ArrayAccess
{
...
public function __call($name, $arguments)
{
$method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
if (method_exists($this, $method)) {
return call_user_func_array(array($this, $method), $arguments);
} else {
throw new IdiormMethodMissingException("Method $name() does not exist in class " . get_class($this));
}
}
...
}

通过重写了method,把原来是驼峰写法的转换成了下划线的写法,然后通过calll_user_func_array来重新进行一次调用。

现在来尝试一下用python来实现这样子的适配,当然可以通过定义__getattribute这个魔术方法来实现也是可以的,不过现在打算使用到了metaclass和new的魔术方法来尝试解决这个问题,如果是对metaclass不熟悉的话可以看一下这个问答中最高票的回答What is a metaclass in Python?

python中的所有东西都是对象,学过面向对象的同学都知道,对象是由类实例化而来的,但是python比较特殊,因为在python中,类是被定义为可以生成对象的对象,而类这种对象就是由原类来生成的。
当我们创建一个类的时候,会先找是否定义了metaclass,如果是没有的话就直接按照type(cls, base, attr)来生成类,如果有的话就通过metaclass这个钩子来生成类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/env python
# encoding: utf-8
import re
class Adapt(type):
def __new__(cls, clsname, bases, dct):
new_attr = {}
for name, val in dct.items():
new_name = re.sub(r'([a-z])_([a-z])', lambda match: match.groups()[0] + match.groups()[1].upper(), name)
if name is not new_name:
new_attr[new_name] = val
new_attr[name] = val
return super(Adapt, cls).__new__(cls, clsname, bases, new_attr)
class Idiorm(object):
__metaclass__ = Adapt
def say_hi(self):
print('say hi')
idiorm = Idiorm()
idiorm.sayHi()
idiorm.say_hi()
# output:
# say hi
# say hi

上面的代码是通过在创建对象的时候对属性重新进行绑定,分别生成驼峰和下划线的两种key,两种key引用同一个对象。

写了这么多,你看完了吗,其实没什么卵用,因为在很多新的项目中都遵循了pep8,类的属性(方法和变量)命名都全部使用了小写的方式OMZ。