背景
在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的对象本来就是对外暴露所有属性的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。