[关闭]
@bergus 2015-12-18T03:13:37.000000Z 字数 17610 阅读 1963

pipetools类库代码研究

pipetools 代码分析


heyhapqqnqepzquj

最近研究了一下pipetools的代码,附上作者的github:https://github.com/0101/pipetools
pipetools是一个python类库,他能够让你通过管道符链式的方式给函数传递参数,比如:

  1. f = pipe | list | foreach("{0}=={0}")
  2. print f("1112342snfjfbjeryehbsa")
  3. #再简单一点的方式是:
  4. print (pipe | list | foreach("{0}=={0}"))("1112342snfjfbjeryehbsa")
  5. #又或者是:
  6. print "1112342snfjfbjeryehbsa" > pipe | list | foreach("{0}=={0}")
  7. #这种传参数方式比传统的要直观,方便,容易书写

copy一点小东西

函数对象

在python当中一切皆对象,函数也不例外,也具有属性(可用dir()查询)。作为对象,它还可以赋值给其它对象名,或者作为参数传递。
搬砖地址是:http://www.cnblogs.com/cnshen/p/3684863.html

  1. #lambda函数
  2. func = lambda x,y: x + y
  3. print func(3,4)
  4. #lambda生成一个函数对象。该函数参数为x,y,返回值为x+y。函数对象赋给func。func的调用与正常函数无异。
  5. #以上定义可以写成以下形式:
  6. def func(x, y):
  7. return x + y
  8. #函数作为参数传递
  9. #函数可以作为一个对象,进行参数传递。函数名(比如func)即该对象。比如说:
  10. def test(f, a, b):
  11. print 'test'
  12. print f(a, b)
  13. test(func, 3, 5)
  14. #test函数的第一个参数f就是一个函数对象。将func传递给f,test中的f()就拥有了func()的功能。
  15. #我们因此可以提高程序的灵活性。可以使用上面的test函数,带入不同的函数参数。比如:
  16. test((lambda x,y: x**2 + y), 6, 9)
  17. #函数作为返回结果
  18. #函数是一个对象,所以可以作为某个函数的返回结果。
  19. def line_conf():
  20. def line(x):
  21. return 2*x+1
  22. return line # return a function object
  23. my_line = line_conf()
  24. print(my_line(5))
  25. #其实,这也就是传说中的闭包
  26. #闭包与并行运算
  27. '''
  28. 闭包有效的减少了函数所需定义的参数数目。这对于并行运算来说有重要的意义。在并行运算的环境下,我们可以让每台电脑负责一个函数,然后将一台电脑的输出和下一台电脑的输入串联起来。最终,我们像流水线一样工作,从串联的电脑集群一端输入数据,从另一端输出数据。这样的情境最适合只有一个参数输入的函数。闭包就可以实现这一目的。
  29. 并行运算正称为一个热点。这也是函数式编程又热起来的一个重要原因。函数式编程早在1950年代就已经存在,但应用并不广泛。然而,我们上面描述的流水线式的工作并行集群过程,正适合函数式编程。由于函数式编程这一天然优势,越来越多的语言也开始加入对函数式编程范式的支持。
  30. '''

partial函数

  1. #局部函数应用,又称偏函数
  2. #我的简单理解是functools.partial函数通过两次传递参数的方式计算被传递函数对象的结果,比如:
  3. def hello(x,y,z,w):
  4. return 12
  5. print partial(hello)(1,2,3,4) == 12
  6. print partial(hello,1)(2,3,4) == 12
  7. print partial(hello,1,2)(3,4) == 12
  8. print partial(hello1,2,3,4)() == 12
  9. #具体的实现搬个砖:http://blog.csdn.net/largetalk/article/details/6612342
  10. def partial(func, *args, **keywords):
  11. def newfunc(*fargs, **fkeywords):
  12. newkeywords = keywords.copy()
  13. newkeywords.update(fkeywords)
  14. return func(*(args + fargs), **newkeywords)
  15. newfunc.func = func
  16. newfunc.args = args
  17. newfunc.keywords = keywords
  18. return newfunc
在pipetools文件夹中有五个主要的python文件,咱们一个一个的分析起作用

debug.py文件

  1. # encoding=utf-8
  2. from itertools import imap, chain
  3. def set_name(name, f):
  4. '''
  5. 给f函数对象增加一个名字为__pipetools__name__值为name的属性
  6. :param name:
  7. :param f:
  8. :return:
  9. >>> set_name("hello",lambda x:x).__pipetools__name__
  10. 'hello'
  11. '''
  12. try:
  13. f.__pipetools__name__ = name
  14. except (AttributeError, UnicodeEncodeError):
  15. pass
  16. return f
  17. def get_name(f):
  18. '''
  19. 返回f函数对象的标识名
  20. 返回值是字符串
  21. :param f:
  22. :return:
  23. '''
  24. from pipetools.main import Pipe
  25. pipetools_name = getattr(f, '__pipetools__name__', None)
  26. if pipetools_name: #如果__pipetools__name__属性存在,那么f是函数对象或者函数
  27. return pipetools_name() if callable(pipetools_name) else pipetools_name
  28. if isinstance(f, Pipe):
  29. return repr(f)
  30. return f.__name__ if hasattr(f, '__name__') else repr(f)
  31. def repr_args(*args, **kwargs):
  32. '''
  33. 返回列表和字典参数的字符串形式
  34. :param args:
  35. :param kwargs:
  36. :return:
  37. >>> repr_args(2, 3, 21, 4, 5, name=2234, ii='ffff')
  38. "2, 3, 21, 4, 5, ii='ffff', name=2234"
  39. '''
  40. return ', '.join(chain( #chain连接多个迭代器为一个
  41. imap('{0!r}'.format, args),
  42. imap('{0[0]}={0[1]!r}'.format, kwargs.iteritems())))

ds_builder.py

  1. # encoding=utf-8
  2. from functools import partial
  3. from pipetools.main import XObject, StringFormatter
  4. class NoBuilder(ValueError):
  5. pass
  6. def DSBuilder(definition):
  7. '''
  8. definition是一个可转换迭代器
  9. 可转换迭代器是让一个序列或者字典经过其他函数处理后可迭代
  10. :param definition:
  11. :return:
  12. '''
  13. builder = select_builder(definition)
  14. if builder:
  15. return builder(definition)
  16. raise NoBuilder("Don't know how to build %s" % type(definition))
  17. def SequenceBuilder(cls, definition):
  18. '''
  19. 把definition里面的可求值元素转换成其对应的函数
  20. 可求值元素是能够让里面的元素经过转换,变成一个能够传递参数的函数
  21. 而ds_item返回的是一个值,故而整体返回值是一个lambda包含着的cls类型的序列
  22. :param cls:
  23. :param definition:
  24. :return:
  25. '''
  26. return lambda x: cls(ds_item(d, x) for d in definition)
  27. def DictBuilder(definition):
  28. return lambda x: dict(
  29. (ds_item(key_def, x), ds_item(val_def, x))
  30. for key_def, val_def in definition.iteritems())
  31. '''
  32. 定义了三种构造器,列表,元组,字典构造器
  33. 定义构造器的目的是让列表,元组,字典里面的特殊元素转化成正常的元素
  34. 根据前面partial的介绍简单理解一下下面字典的作用,SequenceBuilder是一个有两个参数的函数,partial(SequenceBuilder, tuple)(data)得到计算结果,相当于tuple(data),也就是说partial(SequenceBuilder, tuple)是对tuple的封装,作用和tuple是一样的,封装的目的是处理data中复杂的数据结构
  35. '''
  36. builders = {
  37. tuple: partial(SequenceBuilder, tuple),
  38. list: partial(SequenceBuilder, list),
  39. dict: DictBuilder,
  40. }
  41. def select_builder(definition):
  42. '''
  43. 根据definition的类型,返回其对应的构造转换器,也就是上面builders中的value
  44. :param definition:
  45. :return:
  46. '''
  47. for cls, builder in builders.iteritems():
  48. if isinstance(definition, cls):
  49. return builder
  50. def ds_item(definition, data):
  51. '''
  52. 根据definition的类型转换成其对应的函数,然后根据data参数值返回处理后的结果
  53. 简而言之,该函数的结果是值,不是函数对象
  54. :param definition:
  55. :param data:
  56. :return:
  57. '''
  58. if isinstance(definition, XObject):
  59. return (~definition)(data)
  60. if isinstance(definition, basestring):
  61. return StringFormatter(definition)(data)
  62. if callable(definition):
  63. return definition(data)
  64. try:
  65. return DSBuilder(definition)(data)
  66. except NoBuilder:
  67. # static item
  68. return definition

decorators.py

  1. # encoding=utf-8
  2. import re
  3. from functools import partial, wraps
  4. from pipetools.debug import repr_args, set_name, get_name
  5. from pipetools.ds_builder import DSBuilder, NoBuilder
  6. from pipetools.main import pipe, XObject, StringFormatter, xpartial
  7. def pipe_util(func):
  8. """
  9. 装饰器装饰函数func之后,然后让其返回一个函数对象pipe_util_wrapper
  10. pipe_util_wrapper函数的首参数为函数,返回值是一个Pipe对象
  11. 被装饰者的首参数就是pipe_util_wrapper的首参数
  12. """
  13. @wraps(func)
  14. def pipe_util_wrapper(function, *args, **kwargs):
  15. if isinstance(function, XObject):
  16. function = ~function
  17. original_function = function
  18. if args or kwargs:
  19. function = xpartial(function, *args, **kwargs)
  20. name = lambda: '%s(%s)' % (get_name(func), ', '.join(
  21. filter(None, (get_name(original_function), repr_args(*args, **kwargs)))))
  22. f = func(function) #被装饰者的返回值
  23. result = pipe | set_name(name, f) #把被装饰者的返回值封装成一个Pipe对象
  24. # if the util defines an 'attrs' mapping, copy it as attributes
  25. # to the result
  26. attrs = getattr(f, 'attrs', {})
  27. for k, v in attrs.iteritems():
  28. setattr(result, k, v)
  29. # result是一个拥有f函数的Pipe对象
  30. return result
  31. return pipe_util_wrapper
  32. def auto_string_formatter(func):
  33. """
  34. 把含有格式化的字符串转化成该字符串的格式化函数形式
  35. """
  36. @wraps(func)
  37. def auto_string_formatter_wrapper(function, *args, **kwargs):
  38. if isinstance(function, basestring):
  39. function = StringFormatter(function) #StringFormatter的返回值类似于"{0}".format
  40. return func(function, *args, **kwargs)
  41. return auto_string_formatter_wrapper
  42. def data_structure_builder(func):
  43. """
  44. 装饰器处理元素中包含特殊数据结构的元组,列表,字典等
  45. """
  46. @wraps(func)
  47. def ds_builder_wrapper(function, *args, **kwargs):
  48. try:
  49. function = DSBuilder(function)
  50. except NoBuilder:
  51. pass
  52. return func(function, *args, **kwargs)
  53. return ds_builder_wrapper
  54. def regex_condition(func):
  55. """
  56. If a condition is given as string instead of a function, it is turned
  57. into a regex-matching function.
  58. """
  59. @wraps(func)
  60. def regex_condition_wrapper(condition, *args, **kwargs):
  61. if isinstance(condition, basestring):
  62. condition = partial(re.match, condition)
  63. return func(condition, *args, **kwargs)
  64. return regex_condition_wrapper

utils.py

  1. from functools import partial
  2. from itertools import imap, ifilter, islice, takewhile, dropwhile
  3. import operator
  4. from pipetools.debug import set_name, repr_args, get_name
  5. from pipetools.decorators import data_structure_builder, regex_condition
  6. from pipetools.decorators import pipe_util, auto_string_formatter
  7. from pipetools.main import pipe, X, _iterable
  8. KEY, VALUE = X[0], X[1]
  9. @pipe_util
  10. @auto_string_formatter
  11. @data_structure_builder
  12. def foreach(function):
  13. """
  14. Returns a function that takes an iterable and returns an iterator over the
  15. results of calling `function` on each item of the iterable.
  16. >>> xrange(5) > foreach(factorial) | list
  17. [1, 1, 2, 6, 24]
  18. >>> "1234567assfsd" > foreach("{0}") | list
  19. [u'1', u'2', u'3', u'4', u'5', u'6', u'7', u'a', u's', u's', u'f', u's', u'd']
  20. >>> "123455dHHJBHJgg" > foreach(X.lower()) | list
  21. ['1', '2', '3', '4', '5', '5', 'd', 'h', 'h', 'j', 'b', 'h', 'j', 'g', 'g']
  22. >>> "12BHJgg" > foreach(("{0}","{0}==")) | list
  23. [(u'1', u'1=='), (u'2', u'2=='), (u'B', u'B=='), (u'H', u'H=='), (u'J', u'J=='), (u'g', u'g=='), (u'g', u'g==')]
  24. >>> "12BHJgg" > foreach([X,X.upper()]) | list
  25. [['1', '1'], ['2', '2'], ['B', 'B'], ['H', 'H'], ['J', 'J'], ['g', 'G'], ['g', 'G']]
  26. >>> "12BHJgg" > foreach({X:X.upper()}) | list
  27. [{'1': '1'}, {'2': '2'}, {'B': 'B'}, {'H': 'H'}, {'J': 'J'}, {'g': 'G'}, {'g': 'G'}]
  28. """
  29. return partial(imap, function)
  30. @pipe_util
  31. def foreach_do(function):
  32. """
  33. foreach除了返回一个迭代器什么都不会执行
  34. foreach_do马上就会被执行,如果你只是想马上看到结果,不考虑性能等副作用的话,它很适合你
  35. >>> xrange(5) > foreach_do(factorial) | foreach_do(X+3)
  36. [4, 4, 5, 9, 27]
  37. >>> "12332dffrrr" > foreach_do(X.upper())
  38. ['1', '2', '3', '3', '2', 'D', 'F', 'F', 'R', 'R', 'R']
  39. """
  40. return lambda iterable: [function(item) for item in iterable]
  41. @pipe_util
  42. @regex_condition
  43. def where(condition):
  44. """
  45. Pipe-able lazy filter.
  46. >>> odd_range = xrange | where(X % 2) | list
  47. >>> odd_range(10)
  48. [1, 3, 5, 7, 9]
  49. """
  50. return partial(ifilter, condition)
  51. @pipe_util
  52. @regex_condition
  53. def where_not(condition):
  54. """
  55. Inverted :func:`where`.
  56. """
  57. return partial(ifilter, pipe | condition | operator.not_)
  58. @pipe_util
  59. @data_structure_builder
  60. def sort_by(function):
  61. """
  62. Sorts an incoming sequence by using the given `function` as key.
  63. >>> xrange(10) > sort_by(-X)
  64. [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
  65. Supports automatic data-structure creation::
  66. users > sort_by([X.last_name, X.first_name])
  67. There is also a shortcut for ``sort_by(X)`` called ``sort``:
  68. >>> [4, 5, 8, -3, 0] > sort
  69. [-3, 0, 4, 5, 8]
  70. And (as of ``0.2.3``) a shortcut for reversing the sort:
  71. >>> 'asdfaSfa' > sort_by(X.lower()).descending
  72. ['s', 'S', 'f', 'f', 'd', 'a', 'a', 'a']
  73. """
  74. f = partial(sorted, key=function)
  75. f.attrs = {'descending': _descending_sort_by(function)}
  76. return f
  77. @pipe_util
  78. def _descending_sort_by(function):
  79. return partial(sorted, key=function, reverse=True)
  80. sort = sort_by(X)
  81. @pipe_util
  82. @auto_string_formatter
  83. @data_structure_builder
  84. def debug_print(function):
  85. """
  86. Prints function applied on input and returns the input.
  87. ::
  88. foo = (pipe
  89. | something
  90. | debug_print(X.get_status())
  91. | something_else
  92. | foreach(debug_print("attr is: {0.attr}"))
  93. | etc)
  94. """
  95. def debug(thing):
  96. print function(thing)
  97. return thing
  98. return debug
  99. @pipe_util
  100. def as_args(function):
  101. """
  102. Applies the sequence in the input as positional arguments to `function`.
  103. ::
  104. some_lists > as_args(izip)
  105. """
  106. return lambda x: function(*x)
  107. @pipe_util
  108. def as_kwargs(function):
  109. """
  110. Applies the dictionary in the input as keyword arguments to `function`.
  111. """
  112. return lambda x: function(**x)
  113. def take_first(count):
  114. """
  115. Assumes an iterable on the input, returns an iterable with first `count`
  116. items from the input (or possibly less, if there isn't that many).
  117. >>> xrange(9000) > where(X % 100 == 0) | take_first(5) | tuple
  118. (0, 100, 200, 300, 400)
  119. """
  120. def _take_first(iterable):
  121. return islice(iterable, count)
  122. return pipe | set_name('take_first(%s)' % count, _take_first)
  123. def drop_first(count):
  124. """
  125. Assumes an iterable on the input, returns an iterable with identical items
  126. except for the first `count`.
  127. >>> xrange(10) > drop_first(5) | tuple
  128. (5, 6, 7, 8, 9)
  129. """
  130. def _drop_first(iterable):
  131. g = (x for x in xrange(1, count + 1))
  132. return dropwhile(lambda i: unless(StopIteration, g.next)(), iterable)
  133. return pipe | set_name('drop_first(%s)' % count, _drop_first)
  134. def unless(exception_class_or_tuple, func, *args, **kwargs):
  135. """
  136. When `exception_class_or_tuple` occurs while executing `func`, it will
  137. be caught and ``None`` will be returned.
  138. >>> f = where(X > 10) | list | unless(IndexError, X[0])
  139. >>> f([5, 8, 12, 4])
  140. 12
  141. >>> f([1, 2, 3])
  142. None
  143. """
  144. @pipe_util
  145. @auto_string_formatter
  146. @data_structure_builder
  147. def construct_unless(function):
  148. # a wrapper so we can re-use the decorators
  149. def _unless(*args, **kwargs):
  150. try:
  151. return function(*args, **kwargs)
  152. except exception_class_or_tuple:
  153. pass
  154. return _unless
  155. name = lambda: 'unless(%s, %s)' % (exception_class_or_tuple, ', '.join(
  156. filter(None, (get_name(func), repr_args(*args, **kwargs)))))
  157. return set_name(name, construct_unless(func, *args, **kwargs))
  158. @pipe_util
  159. @regex_condition
  160. def select_first(condition):
  161. """
  162. Returns first item from input sequence that satisfies `condition`. Or
  163. ``None`` if none does.
  164. >>> ['py', 'pie', 'pi'] > select_first(X.startswith('pi'))
  165. 'pie'
  166. As of ``0.2.1`` you can also
  167. :ref:`directly use regular expressions <auto-regex>` and write the above
  168. as:
  169. >>> ['py', 'pie', 'pi'] > select_first('^pi')
  170. 'pie'
  171. There is also a shortcut for ``select_first(X)`` called ``first_of``:
  172. >>> first_of(['', None, 0, 3, 'something'])
  173. 3
  174. >>> first_of([])
  175. None
  176. """
  177. return where(condition) | unless(StopIteration, X.next())
  178. first_of = select_first(X)
  179. @pipe_util
  180. @auto_string_formatter
  181. @data_structure_builder
  182. def group_by(function):
  183. """
  184. Groups input sequence by `function`.
  185. Returns an iterator over a sequence of tuples where the first item is a
  186. result of `function` and the second one a list of items matching this
  187. result.
  188. Ordering of the resulting iterator is undefined, but ordering of the items
  189. in the groups is preserved.
  190. >>> [1, 2, 3, 4, 5, 6] > group_by(X % 2) | list
  191. [(0, [2, 4, 6]), (1, [1, 3, 5])]
  192. """
  193. def _group_by(seq):
  194. result = {}
  195. for item in seq:
  196. result.setdefault(function(item), []).append(item)
  197. return result.iteritems()
  198. return _group_by
  199. def _flatten(x):
  200. if not _iterable(x):
  201. yield x
  202. else:
  203. for y in x:
  204. for z in _flatten(y):
  205. yield z
  206. def flatten(*args):
  207. """
  208. Flattens an arbitrarily deep nested iterable(s).
  209. """
  210. return _flatten(args)
  211. flatten = pipe | flatten
  212. def count(iterable):
  213. """
  214. Returns the number of items in `iterable`.
  215. """
  216. return sum(1 for whatever in iterable)
  217. count = pipe | count
  218. @pipe_util
  219. @regex_condition
  220. def take_until(condition):
  221. """
  222. >>> [1, 4, 6, 4, 1] > take_until(X > 5) | list
  223. [1, 4]
  224. """
  225. return partial(takewhile, pipe | condition | operator.not_)

main.py

  1. # encoding=utf-8
  2. from collections import Iterable
  3. from functools import partial, wraps
  4. from pipetools.debug import get_name, set_name, repr_args
  5. class Pipe(object):
  6. """
  7. Pipe-style combinator.
  8. Example::
  9. p = pipe | F | G | H
  10. p(x) == H(G(F(x)))
  11. """
  12. def __init__(self, func=None):
  13. self.func = func
  14. self.__name__ = 'Pipe'
  15. def __str__(self):
  16. return get_name(self.func)
  17. __repr__ = __str__
  18. @staticmethod
  19. def compose(first, second):
  20. name = lambda: '{0} | {1}'.format(get_name(first), get_name(second))
  21. def composite(*args, **kwargs):
  22. return second(first(*args, **kwargs))
  23. return set_name(name, composite)
  24. @classmethod
  25. def bind(cls, first, second):
  26. return cls(cls.compose(first, second) if all([first, second]) else first or second)
  27. def __or__(self, next_func):
  28. return self.bind(self.func, prepare_function_for_pipe(next_func))
  29. def __ror__(self, prev_func):
  30. return self.bind(prepare_function_for_pipe(prev_func), self.func)
  31. def __lt__(self, thing):
  32. return self.func(thing) if self.func else thing
  33. def __call__(self, *args, **kwargs):
  34. return self.func(*args, **kwargs)
  35. def __get__(self, instance, owner):
  36. return partial(self, instance) if instance else self
  37. pipe = Pipe()
  38. class Maybe(Pipe):
  39. @staticmethod
  40. def compose(first, second):
  41. name = lambda: '{0} ?| {1}'.format(get_name(first), get_name(second))
  42. def composite(*args, **kwargs):
  43. result = first(*args, **kwargs)
  44. return None if result is None else second(result)
  45. return set_name(name, composite)
  46. def __call__(self, *args, **kwargs):
  47. if len(args) == 1 and args[0] is None and not kwargs:
  48. return None
  49. return self.func(*args, **kwargs)
  50. def __lt__(self, thing):
  51. return (
  52. None if thing is None else
  53. self.func(thing) if self.func else
  54. thing)
  55. maybe = Maybe()
  56. def prepare_function_for_pipe(thing):
  57. if isinstance(thing, XObject):
  58. return ~thing
  59. if isinstance(thing, tuple):
  60. return xpartial(*thing)
  61. if isinstance(thing, basestring):
  62. return StringFormatter(thing)
  63. if callable(thing):
  64. return thing
  65. raise ValueError('Cannot pipe %s' % thing)
  66. def StringFormatter(template):
  67. '''
  68. 把一个字符串转化为一个带有format格式的函数
  69. content是格式化字符串的参数
  70. :param template:
  71. :return:
  72. '''
  73. f = unicode(template).format
  74. def _format(content):
  75. if isinstance(content, dict):
  76. return f(**content)
  77. if _iterable(content):
  78. return f(*content)
  79. return f(content)
  80. return set_name(lambda: "format('%s')" % template[:20], _format)
  81. def _iterable(obj):
  82. "Iterable but not a string"
  83. return isinstance(obj, Iterable) and not isinstance(obj, basestring)
  84. class XObject(object):
  85. def __init__(self, func=None):
  86. self._func = func
  87. set_name(lambda: get_name(func) if func else 'X', self)
  88. def __repr__(self):
  89. return get_name(self)
  90. def __invert__(self):
  91. return self._func or set_name('X', lambda x: x)
  92. def bind(self, name, func):
  93. set_name(name, func)
  94. return XObject((self._func | func) if self._func else (pipe | func))
  95. def __call__(self, *args, **kwargs):
  96. name = lambda: 'X(%s)' % repr_args(*args, **kwargs)
  97. return self.bind(name, lambda x: x(*args, **kwargs))
  98. def __eq__(self, other):
  99. return self.bind(lambda: 'X == {0!r}'.format(other), lambda x: x == other)
  100. def __getattr__(self, name):
  101. return self.bind(lambda: 'X.{0}'.format(name), lambda x: getattr(x, name))
  102. def __getitem__(self, item):
  103. return self.bind(lambda: 'X[{0!r}]'.format(item), lambda x: x[item])
  104. def __gt__(self, other):
  105. return self.bind(lambda: 'X > {0!r}'.format(other), lambda x: x > other)
  106. def __ge__(self, other):
  107. return self.bind(lambda: 'X >= {0!r}'.format(other), lambda x: x >= other)
  108. def __lt__(self, other):
  109. return self.bind(lambda: 'X < {0!r}'.format(other), lambda x: x < other)
  110. def __le__(self, other):
  111. return self.bind(lambda: 'X <= {0!r}'.format(other), lambda x: x <= other)
  112. def __mod__(self, y):
  113. return self.bind(lambda: 'X % {0!r}'.format(y), lambda x: x % y)
  114. def __ne__(self, other):
  115. return self.bind(lambda: 'X != {0!r}'.format(other), lambda x: x != other)
  116. def __neg__(self):
  117. return self.bind(lambda: '-X', lambda x: -x)
  118. def __mul__(self, other):
  119. return self.bind(lambda: 'X * {0!r}'.format(other), lambda x: x * other)
  120. def __div__(self, other):
  121. return self.bind(lambda: 'X / {0!r}'.format(other), lambda x: x / other)
  122. def __add__(self, other):
  123. return self.bind(lambda: 'X + {0!r}'.format(other), lambda x: x + other)
  124. def __sub__(self, other):
  125. return self.bind(lambda: 'X - {0!r}'.format(other), lambda x: x - other)
  126. def __pow__(self, other):
  127. return self.bind(lambda: 'X ** {0!r}'.format(other), lambda x: x ** other)
  128. def __ror__(self, func):
  129. return pipe | func | self
  130. def __or__(self, func):
  131. if isinstance(func, Pipe):
  132. return func.__ror__(self)
  133. return pipe | self | func
  134. def _in_(self, y):
  135. return self.bind(lambda: 'X._in_({0!r})'.format(y), lambda x: x in y)
  136. X = XObject()
  137. def xpartial(func, *xargs, **xkwargs):
  138. """
  139. Like :func:`functools.partial`, but can take an :class:`XObject`
  140. placeholder that will be replaced with the first positional argument
  141. when the partially applied function is called.
  142. Useful when the function's positional arguments' order doesn't fit your
  143. situation, e.g.:
  144. >>> reverse_range = xpartial(range, X, 0, -1)
  145. >>> reverse_range(5)
  146. [5, 4, 3, 2, 1]
  147. It can also be used to transform the positional argument to a keyword
  148. argument, which can come in handy inside a *pipe*::
  149. xpartial(objects.get, id=X)
  150. Also the XObjects are evaluated, which can be used for some sort of
  151. destructuring of the argument::
  152. xpartial(somefunc, name=X.name, number=X.contacts['number'])
  153. Lastly, unlike :func:`functools.partial`, this creates a regular function
  154. which will bind to classes (like the ``curry`` function from
  155. ``django.utils.functional``).
  156. """
  157. any_x = any(isinstance(a, XObject)
  158. for a in xargs + tuple(xkwargs.values()))
  159. use = lambda x, value: (~x)(value) if isinstance(x, XObject) else x
  160. @wraps(func)
  161. def xpartially_applied(*func_args, **func_kwargs):
  162. if any_x:
  163. if not func_args:
  164. raise ValueError('Function "%s" partially applied with an '
  165. 'X placeholder but called with no positional arguments.'
  166. % get_name(func))
  167. first = func_args[0]
  168. rest = func_args[1:]
  169. args = tuple(use(x, first) for x in xargs) + rest
  170. kwargs = dict((k, use(x, first)) for k, x in xkwargs.iteritems())
  171. kwargs.update(func_kwargs)
  172. else:
  173. args = xargs + func_args
  174. kwargs = dict(xkwargs, **func_kwargs)
  175. return func(*args, **kwargs)
  176. name = lambda: '%s(%s)' % (get_name(func), repr_args(*xargs, **xkwargs))
  177. return set_name(name, xpartially_applied)
  178. if __name__ == '__main__':
  179. f = 'ggggcdwecfwefhweejhjwq' > pipe | list | ','.join
  180. print f
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注