开发者

How Awful is My Decorator?

I recently created a @sequenceable decorator, that can be applied to any function that takes one argument, and causes it to automatically be applicable to any sequence. This is the code (Python 2.5):

def sequenceable(func):
    def newfunc(arg):
        if hasattr(arg, '__iter__'):
            if isinstance(arg, dict):
                return dict((k, func(v)) for k, v in arg.iteritems())
            else:
                return map(func, arg)
        else:
            return func(a开发者_如何学编程rg)
    return newfunc

In use:

@sequenceable
def unixtime(dt):
    return int(dt.strftime('%s'))

>>> unixtime(datetime.now())
1291318284
>>> unixtime([datetime.now(), datetime(2010, 12, 3)])
[1291318291, 1291352400]
>>> unixtime({'start': datetime.now(), 'end': datetime(2010, 12, 3)})
{'start': 1291318301, 'end': 1291352400}

My questions are:

  • Is this a terrible idea, and why?
  • Is this a possibly good idea, but has significant drawbacks as implemented?
  • What are the potential pitfalls of using this code?
  • Is there a builtin or library that already does what I'm doing?


This is a terrible idea. This is essentially loose typing. Duck-typing is as far as this stuff should be taken, IMO.

Consider this:

def pluralize(f):
    def on_seq(seq):
        return [f(x) for x in seq]
    def on_dict(d):
         return dict((k, f(v)) for k, v in d.iteritems())
    f.on_dict = on_dict
    f.on_seq = on_seq
    return f

Your example then becomes

@pluralize
def unixtime(dt):
    return int(dt.strftime('%s'))


unixtime.on_seq([datetime.now(), datetime(2010, 12, 3)])
unixtime.on_dict({'start': datetime.now(), 'end': datetime(2010, 12, 3)})

Doing it this way still requires the caller to know (to within duck-typing accuracy) what is being passed in and doesn't add any typechecking overhead to the actual function. It will also work with any dict-like object whereas your original solution depends on it being an actual subclass of dict.


In my opinion, you seem to be building logic into the wrong place. Why should unixtime know anything about sequencing? In some cases it would be a good idea (for performance or even semantics) but here it seems like you're adding extra features to unixtime that don't make sense in that context.

Better is just to use (say) a list comprehension:

[unixtime(x) for x in [datetime.now(), datetime(2010, 12, 3)]]

that way, you're using the proper Pythonic construct for applying the same thing to a sequence, and you're not polluting unixtime with ideas about sequences. You end up coupling logic (about sequencing) in places where the implementation should be free of that knowledge.

EDIT: It basically comes down to coding style, reusability and maintainability. You want well partitioned code, so that when you're coding unixtime (say), you're concerned exclusively with converting to a unixtime. Then, if you're interested in sequences, you design functionality (or use built-in syntax) that is exclusively concerned with sequences. That makes it easier to think clearly about each operation, test, debug and reuse code. Think about it even in terms of the name: the original function is appropriately called unixtime, but your sequenced version might more appropriately be called unixtime_sequence, which is weird and suggests an unusual function.

Sometimes, of course, you break that rule. If (but only when) performance is an issue, you might combine functionality. But in general, partitioning things at first into clear parts leads to clear thinking, clear coding and easy reuse.


I am not a huge fan of trying to help out callers too much. Python is expressive enough that it's not a big deal for the caller to handle the "listification". It's easy enough for a caller to write out the dict comprehension or map call.

As a Python programmer that's what I would expect to have to do since the Python standard library functions don't help me out this way. This idiom actually makes me a little more confused because now I have to try to remember what methods are "helpful" and which aren't.

Being too flexible is a minor gripe I have with the Python-based build tool SCons. Its methods are all very accommodating. If you want to set some preprocessor defines you can give it a string, a list of strings, tuples, a dict, etc. It's very convenient but a bit overwhelming.

env = Environment(CPPDEFINES='xyz')                # -Dxyz
env = Environment(CPPDEFINES=[('B', 2), 'A'])      # -DB=2 -DA
env = Environment(CPPDEFINES={'B':2, 'A':None})    # -DA -DB=2


Not pythonic because: - in Python explicit is considered better than implicit - it is not the standard idiom like using builtin map or a list comprehension


@sequence
def distance(points):
    return sqrt(reduce(
        lambda a, b: a + b, 
        (a**2 for a in points),
        0))

And your decorator becomes useless. Your decorator can be applied only for a special cases, and if you'll have using it, you'll break one of Python Zen's rule: "There should be one-- and preferably only one --obvious way to do it".

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜