资讯专栏INFORMATION COLUMN

关于Python Magic Method的若干脑洞

fizz / 3321人阅读

摘要:先从最初的脑洞开始吧。这样做的目的在于,把调用过程中的状态存储起来,借此实现带状态的调用。这种实例我们称之为函数对象。在里面也有同样的机制。对于真正的而言,肯定不会用这种的判断方式。

有一天闲着无聊的时候,脑子里突然冒出一个Magic Method的有趣用法,可以用__getattr__来实现Python版的method_missing
顺着这个脑洞想下去,我发现Python的Magic Method确实有很多妙用之处。故在此记下几种有趣(也可能有用的)Magic Method技巧,希望可以抛砖引玉,打开诸位读者的脑洞,想出更加奇妙的用法。

如果对Magic Method的了解仅仅停留在知道这个术语和若干个常用方法上(如__lt____str____len__),可以阅读下这份教程,看看Magic Method可以用来做些什么。

Python method_missing

先从最初的脑洞开始吧。曾几何时,Ruby社区的人总是夸耀Ruby的强大的元编程能力,其中method_missing更是不可或缺的特性。通过调用BaseObject上的method_missing,Ruby可以实现在调用不存在的属性时进行拦截,并动态生成对应的属性。

Ruby例子

# 来自于Ruby文档: http://ruby-doc.org/core-2.2.0/BasicObject.html#method-i-method_missing
class Roman
  def roman_to_int(str)
    # ...
  end
  def method_missing(methId)
    str = methId.id2name
    roman_to_int(str)
  end
end

r = Roman.new
r.iv      #=> 4
r.xxiii   #=> 23
r.mm      #=> 2000

method_missing的应用是如此地广泛,以至于只要是成规模的Ruby库,多多少少都会用到它。像是ActiveRecord就是靠这一特性去动态生成关联属性。

其实Python一早就内置了这一功能。Python有一个Magic Method叫__getattr__,它会在找不到属性的时候调用,正好跟Ruby的method_missing是一样的。
我们可以这样动态添加方法:

class MyClass(object):
    def __getattr__(self, name):
        """called only method missing"""
        if name == "missed_method":
            setattr(self, name, lambda : True)
            return lambda : True

myClass = MyClass()
print(dir(myClass))
print(myClass.missed_method())
print(dir(myClass))

于是乎,前面的Ruby例子可以改写成下面的Python版本:

class Roman(object):
    roman_int_map = {
            "i": 1, "v": 5, "x": 10, "l": 50,
            "c":100, "d": 500, "m": 1000
    }

    def roman_to_int(self, s):
        decimal = 0
        for i in range(len(s), 0, -1):
            if (i == len(s) or
                    self.roman_int_map[s[i-1]] >= self.roman_int_map[s[i]]):
                decimal += self.roman_int_map[s[i-1]]
            else:
                decimal -= self.roman_int_map[s[i-1]]
        return decimal

    def __getattr__(self, s):
        return self.roman_to_int(s)

r = Roman()
print(r.iv)
r.iv #=> 4
r.xxiii #=> 23
r.mm #=> 2000

很有可能你会觉得这个例子没有什么意义,你是对的!其实它就是把方法名当做一个罗马数字字符串,传入roman_to_int而已。不过正如递归不仅仅能用来计算斐波那契数列,__getattr__的这一特技实际上还是挺有用的。你可以用它来进行延时计算,或者方法分派,抑或像基于Ruby的DSL一样动态地合成方法。这里有个用__getattr__实现延时加载的例子。

函数对象

在C++里面,你可以重载掉operator (),这样就可以像调用函数一样去调用一个类的实例。这样做的目的在于,把调用过程中的状态存储起来,借此实现带状态的调用。这种实例我们称之为函数对象。

在Python里面也有同样的机制。如果想要存储的状态只有一种,你需要的是一个生成器。通过send来设置存储的状态,通过next来获取调用的结果。不过如果你需要存储多个不同的状态,生成器就不够用了,非得定义一个函数对象不可。

Python里面可以重载__call__来实现operator ()的功能。下面的例子里面,就是一个存储有两个状态value和called_times的函数对象:

class CallableCounter(object):
    def __init__(self, initial_value=0, start_times=0):
        self.value = initial_value
        self.called_times = start_times

    def __call__(self):
        print("Call the object and do something with value %d" % self.value)
        self.value += 1
        self.called_times += 1

    def reset(self):
        self.called_times = 0


cc = CallableCounter(initial_value=5)
for i in range(10):
    cc()
print(cc.called_times)
cc.reset()
伪造一个Dict

最后请允许我奉上一个大脑洞,伪造一个Dict类。(这个可就没有什么实用价值了)

首先确定下把数据存在哪里。我打算把数据存储在类的__dict__属性中。由于__dict__属性的值就是一个Dict实例,我只需把调用在FakeDict上的方法直接转发给对应的__dict__的方法。代价是只能接受字符串类型的键。

class FakeDict:
    def __init__(self, iterable=None, **kwarg):
        if iterable is not None:
            if isinstance(iterable, dict):
                self.__dict__ = iterable
            else:
                for i in iterable:
                    self[i] = None
        self.__dict__.update(kwarg)

    def __len__(self):
        """len(self)"""
        return len(self.__dict__)

    def __str__(self):
        """it looks like a dict"""
        return self.__dict__.__str__()
    __repr__ = __str__

接下来开始做点实事。Dict最基本的功能是给一个键设置值和返回一个键对应的值。通过定义__setitem____getitem__方法,我们可以重载掉[]=[]

    def __setitem__(self, k, v):
        """self[k] = v"""
        self.__dict__[k] = v

    def __getitem__(self, k):
        """self[k]"""
        return self.__dict__[k]

别忘了del方法:

    def __delitem__(self, k):
        """del self[k]"""
        del self.__dict__[k]

Dict的一个常用用途是允许我们迭代里面所有的键。这个可以通过定义__iter__实现。

    def __iter__(self):
        """it iterates like a dict"""
        return iter(self.__dict__)

Dict的另一个常用用途是允许我们查找一个键是否存在。其实只要定义了__iter__,Python就能判断if x in y,不过这个过程中会遍历对象的所有值。对于真正的Dict而言,肯定不会用这种O(n)的判断方式。定义了__contains__之后,Python会优先使用它来判断if x in y

    def __contains__(self, k):
        """key in self"""
        return k in self.__dict__

接下要实现==的重载,不但要让FakeDict和FakeDict之间可以进行比较,而且要让FakeDict和正牌的Dict也能进行比较。

    def __eq__(self, other):
        """
        implement self == other FakeDict,
        also implement self == other dict
        """
        if isinstance(other, dict):
            return self.__dict__ == other
        return self.__dict__ == other.__dict__

要是继续实现了__subclass____class__,那么我们的伪Dict就更完备了。这个就交给感兴趣的读者自己动手了。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/37635.html

相关文章

  • Python中Mock和MagicMock区别

    摘要:也就是说,如果不需要,两者使用起来并没有什么分别。来看个例子,先定义个类,里面只有一个成员方法,返回倍的数值使用类来掉这个成员方法使用类来两者没有任何区别,都成功了了成员方法。再看下两者的区别因为使用类时,默认不会创建这个的,所以报错。 Python的unittest.mock模块中提供了两个主要的mock类,分别是Mock和MagicMock. 先看一下官方文档的定义: MagicM...

    TigerChain 评论0 收藏0
  • Python单例模式(Singleton)N种实现

    摘要:本篇文章总结了目前主流的实现单例模式的方法供读者参考。使用实现单例模式同样,我们在类的创建时进行干预,从而达到实现单例的目的。 很多初学者喜欢用 全局变量 ,因为这比函数的参数传来传去更容易让人理解。确实在很多场景下用全局变量很方便。不过如果代码规模增大,并且有多个文件的时候,全局变量就会变得比较混乱。你可能不知道在哪个文件中定义了相同类型甚至重名的全局变量,也不知道这个变量在程序的某...

    Maxiye 评论0 收藏0
  • 介绍Python魔术方法 - Magic Method

    摘要:所以准确来说是和共同构成了构造函数是用来创建类并返回这个类的实例而只是将传入的参数来初始化该实例在创建一个实例的过程中必定会被调用但就不一定,比如通过的方式反序列化一个实例时就不会调用。 前言 在Python中,所有以__双下划线包起来的方法,都统称为魔术方法。比如我们接触最多的__init__. 有些魔术方法,我们可能以后一辈子都不会再遇到了,这里也就只是简单介绍下; 而有些魔术方法...

    animabear 评论0 收藏0
  • php magic method 具体应用和 phpdoc 结合

    摘要:关于的介绍自行查阅官方文档,这里不再赘述。使用的同学注意了,如果在我们的代码中使用到了中相关的魔术方法,需要在文件中指明告诉应该如何来跟踪变量属性。下面我们来具体实践分析。确实这个样子可以实现,但没有利用到这一魔术方法的特性。 关于 Magic Methods 的介绍自行查阅官方文档,这里不再赘述。http://php.net/manual/en/lang... 使用 phpstorm...

    csRyan 评论0 收藏0
  • Python Tricks 若干

    摘要:在代码中可以看到一些常见的,在这里做一个简单的小结。的妙用在某些场景下我们需要判断我们是否是从一个循环中跳出来的,并且只针对跳出的情况做相应的处理。这时候我们通常的做法是使用一个变量来标识是否是从循环中跳出的。 在 python 代码中可以看到一些常见的 trick,在这里做一个简单的小结。 json 字符串格式化 在开发 web 应用的时候经常会用到 json 字符串,但是一段比...

    lovXin 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<