IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [Python]动态修改Python对象

    忆先发表于 2017-03-30 22:30:27
    love 0

    背景

    在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的对象本来就是对外暴露所有属性的。



沪ICP备19023445号-2号
友情链接