背景

在Codewars上碰到一个很有趣的问题,和算法关系不大,主要是考究对Python对象的动态修改,题目描述是这样子的。

Description:

For this kata you will be using some meta-programming magic to create a new Thing object. This object will allow you to define things in a descriptive sentence like format.

This challenge attempts to build on itself in an increasingly complex manner.

jane = Thing('Jane')
jane.name # => 'Jane'

# can define boolean methods on an instance
jane.is_a.person
jane.is_a.woman
jane.is_not_a.man

jane.is_a_person # => True
jane.is_a_man # => False

# can define properties on a per instance level
jane.is_the.parent_of.joe
jane.parent_of # => 'joe'

# can define number of child things
# when more than 1, a tuple subclass is created
jane.has(2).legs
len(jane.legs) # => 2
isinstance(jane.legs[0], Thing) # => True

# can define single items
jane.has(1).head
isinstance(jane.head, Thing) # => True

# can define number of things in a chainable and natural format
>> Note: Python, unlike Ruby and Javascript, doesn't have a pretty syntax for blocks of expressions and a forEach method for iterables. So you should implement this behaviour yourself.
jane.has(2).arms.each.having(1).hand.having(5).fingers
len(jane.arms[0].hand.fingers) # => 5

# can define properties on nested items
jane.has(1).head.having(2).eyes.each.being_the.color.blue.having(1).pupil.being_the.color.black

# can define methods: thing.can.verb(method, past='')
method = lambda phrase: "%s says: %s" % (name, phrase)
# or 
def method(phrase):
  return "%s says: %s" % (name, phrase)
jane.can.speak(method, "spoke")

jane.speak("hello") # => "Jane says: hello"

# if past tense was provided then method calls are tracked
jane.spoke # => ["Jane says: hello"]

解析

这个问题的关键是给现有对象添加新的属性,而属性的名字是不确定的,这时候一般要用到__dict__和重写__getattr__方法。

代码

class ExtendTuple(tuple):
""" 用来解决each的问题,因为要求访问tuple对象的each属性,所以写了一个tuple的拓展 """
    def __init__(self, *args, **kwargs):
        super().__init__()
        self.each = self[0]


class Thing(object):

    def __init__(self, name, _type='Thing', parent=None, number=0):
        self.name = name
        self.type = _type
        self.parent = parent
        self.list = []
        self.number = number
        self.functions = {}
        self.functions_called = {}
        self.property = {}
        if self.type == 'Thing':
            self.being_a = self.is_a = Thing('is_a', _type='is_a', parent=self)
            self.being_not_a = self.is_not_a = Thing(
                'is_not_a', _type='is_not_a', parent=self)
            self.and_the = self.being_the = self.is_the = Thing(
                'is_the', _type='is_the', parent=self)
            self.having = self.has
            self.can = Thing('can', _type='can', parent=self)

    def __getattr__(self, attr):
        if self.type in ('is_a', 'is_not_a', 'being_a', 'being_not_a'):
            self.list.append(attr)
        elif self.type in ('is_the', 'being_the', 'and_the'):
            self.property[attr] = Thing(attr, _type='property', parent=self)
            return self.property[attr]
        elif self.type == 'property':
            self.parent.parent.property[self.name] = attr
            return self.parent.parent
        elif self.type == 'can':
            def func(func, name=''):
                self.parent.functions[attr] = (func, name)
                self.parent.functions_called[name] = []
            return func
        elif self.type == 'child':
            if self.number == 1:
                self.parent.__dict__[attr] = Thing(attr)
                self.parent.__dict__[attr].__dict__['is_' + attr] = True
                return self.parent.__dict__[attr]
            else:
                one = Thing(attr[:-1])
                one.__dict__['is_' + attr[:-1]] = True
                self.parent.__dict__[attr] = ExtendTuple([one] * self.number)
                return self.parent.__dict__[attr]
        elif self.type == 'Thing':
            if attr in self.property:
                return self.property[attr]
            if attr in self.functions:
                def actualfunc(*args, **kwargs):
                    global name
                    name = self.name
                    self.functions_called[self.functions[attr][1]].append(
                        self.functions[attr][0](*args, **kwargs))
                    return self.functions_called[self.functions[attr][1]][::-1][0]
                return actualfunc
            elif attr in self.functions_called:
                return self.functions_called[attr]
            elif attr.startswith('is_a_') or attr.startswith('being_a_'):
                name = attr.split('a_')[1]
                if name in self.being_a.list:
                    return True
                elif name in self.being_not_a.list:
                    return False

    def has(self, number):
        return Thing('has', _type='child', parent=self, number=number)

    def __repr__(self):
        return 'Thing (name = {name})'.format(name=self.name)

感想

对Python对象来说,大胆地使用猴子补丁是没有什么问题的,只要能解决实际问题就可以,毕竟Python的对象本来就是对外暴露所有属性的。


忆先
4.2k 声望45 粉丝

引用和评论

0 条评论