├── .gitignore ├── .travis.yml ├── HowItWorks.md ├── LICENSE ├── README.md ├── README.rst ├── lambdazen ├── __init__.py ├── lambdazen.py └── test │ ├── __init__.py │ └── test_given_a_zen_decorator.py ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | ### Python template 2 | # Byte-compiled / optimized / DLL files 3 | __pycache__/ 4 | *.py[cod] 5 | *$py.class 6 | .idea 7 | .DS_STORE 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask instance folder 60 | instance/ 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # IPython Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # dotenv 81 | .env 82 | 83 | # virtualenv 84 | venv/ 85 | ENV/ 86 | 87 | # Spyder project settings 88 | .spyderproject 89 | 90 | # Rope project settings 91 | .ropeproject 92 | 93 | # Created by .ignore support plugin (hsz.mobi) 94 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.6" 4 | - "2.7" 5 | - "3.2" 6 | - "3.3" 7 | - "3.4" 8 | - "3.5" 9 | script: nosetests -------------------------------------------------------------------------------- /HowItWorks.md: -------------------------------------------------------------------------------- 1 | ### A Better Lambda 2 | 3 | For a long time, I've had a distaste for the syntax of lambda expressions in python. 4 | 5 | ```python 6 | add1 = lambda value: value + 1 7 | print add1(1) 8 | >>> 2 9 | ``` 10 | 11 | Compared to syntaxes for the same effect in other languages (like c#), it seems clunky. 12 | ```c# 13 | var add1 = (x) => x+1; 14 | Console.WriteLine(add1(1)); 15 | >>> 2 16 | ``` 17 | 18 | I struck out to find a way to get a cleaner syntax in python. After a long wrestle, I've found a marginal but satisfying success. 19 | 20 | ```python 21 | @zen 22 | def _(): 23 | _.add1 = (x) > x + 1 24 | 25 | _.add1(1) 26 | >>> 2 27 | ``` 28 | 29 | You might be wondering, what could the `_` function and `zen` attribute possibly be doing to make this work. I'll admit, `_` being needed is not my favorite thing, but it's necessary. 30 | 31 | But wait, before we dig into why it's necessary... let's look at a couple of the interesting options that looked promising for this, but ultimately failed. 32 | 33 | ### Infix Operators 34 | 35 | My first thoughts went back to c++ days and operator overloads. Research into these in python yielded some interesting results. There's not any native support for operator overloads, but somebody discovered a clever solution taking advantage of hooks into the existing operators `|, <<, >>` to create what they call [infix operators](http://code.activestate.com/recipes/384122-infix-operators/). 36 | 37 | ```python 38 | x=Infix(lambda x,y: x*y) 39 | print 2 |x| 4 40 | >>> 8 41 | ``` 42 | 43 | While infix operators are pretty cool, there's no way to get the unevaluated expression on either side of the operator. This leaves us without options for creating any kind of function object with regular looking syntax. 44 | 45 | ### Compiled Bytecode rewriting 46 | 47 | Python functions all have a [code object](https://late.am/post/2012/03/26/exploring-python-code-objects.html). The code object contains the python compiled bytecode that the interpreter executes. You can see the raw bytecode string using `func.__code__.co_code` or you can use `dis` for a human readable version. 48 | 49 | ```python 50 | def func(): 51 | return 1 52 | 53 | print repr(func.__code__.co_code) 54 | >>> 'd\x01\x00S' 55 | 56 | import dis 57 | dis.dis(func) 58 | >>> 2 0 LOAD_CONST 1 (1) 59 | >>> 3 RETURN_VALUE 60 | ``` 61 | 62 | Examining this I thought what if the code object could be changed, would the function then execute differently. As it turns out, code objects are immutable... 63 | 64 | ```python 65 | def func(): 66 | return 1 67 | 68 | func.__code__.co_code = [c if index != 1 else chr(5) for index,c in enumerate(func.__code__.co_code)] 69 | >>> Traceback (most recent call last): 70 | >>> File "", line 1, in 71 | >>> TypeError: readonly attribute 72 | ``` 73 | 74 | But the whole code object can be replaced, which changes the behavior of the function. 75 | 76 | ```python 77 | def func(): 78 | return 1 79 | 80 | def func2(): 81 | return 2 82 | 83 | func.__code__ = func2.__code__ 84 | print func() 85 | >>> 2 86 | ``` 87 | 88 | At this point I realized it was possible to find patterns in the bytecode and replace them with different patterns, so the basic effect of changing python syntax can be achieved during python runtime. I realized though that this would take a deep understanding of the bytecode, similar to what a compiler might have for source code. 89 | 90 | While replacing the bytecode by [generating code objects](http://stackoverflow.com/questions/16064409/how-to-create-a-code-object-in-python) remains a viable option, I thought exploring uncompiled source rewriting might take less effort being able to take advantage of the python [compiler](https://docs.python.org/2/library/compiler.html) module. 91 | 92 | ### Zen (Source Rewriting) 93 | 94 | Python provides a utility for fetching the raw source code of a function, by reading from the source file (so it doesn't work in the repl unfortunately). 95 | 96 | ```python 97 | import inspect 98 | 99 | def func(): 100 | return 1 101 | 102 | print inspect.getsource(func) 103 | >>> 'def func():\n return 1\n' 104 | ``` 105 | 106 | Using the [compiler](https://docs.python.org/2/library/compiler.html) module you can take advantage of the `parse` function to get an ast. However, for the initial version, I went the easier route of using a regular expression to find and replace the target syntactic pattern. It was enough to get this job done, and in the future we can use an ast to do more complicated source rewrites. 107 | 108 | Using a regular expression the solution is surprisingly just a few lines of python... 109 | 110 | ```python 111 | def _replace_match(match): 112 | vars = match.groups(1)[0] 113 | return "= lambda {0}:".format(vars) 114 | 115 | def zen(func): 116 | # Look for (x, y, ..., z) > 117 | lambda_syntax_regex = r'=\s*\(([^\)]*)\)\s*>' 118 | 119 | # Get source and replace our lambda syntax with official python lambdas 120 | source = inspect.getsource(func) 121 | source = re.sub(lambda_syntax_regex, _replace_match, source) 122 | 123 | # Remove the decorator from the function source, so we don't loop endlessly 124 | source = re.sub('@zen', '', source) 125 | 126 | # Execute recompiled source in the original scope of the function 127 | # locals retains the original scope of the function 128 | locals = inspect.currentframe().f_back.f_locals 129 | a = compile(source, '', 'exec') 130 | exec(a, locals) 131 | new_function = locals[func.__name__] 132 | 133 | # Call the new function to bind the lambdas to the intended members on the new_function object 134 | new_function() 135 | 136 | return new_function 137 | ``` 138 | 139 | Now you can easily write code like this... 140 | 141 | ```python 142 | def otherFunc(*args): 143 | print sum(args) 144 | 145 | @zen 146 | def lambdaContainer(): 147 | lambdaContainer.func = (x, y, z) > otherFunc(x, y, z) 148 | 149 | lambdaContainer.func(1,2,3) 150 | >>> 6 151 | ``` 152 | 153 | Having to hide the new lambda expression under a function is non-ideal but necessary for the moment. Perhaps future research will show more usable patterns. 154 | 155 | Until then, enjoy... 156 | 157 | *EDIT: The code in the [repository](https://github.com/brthornbury/lambdazen) has been updated to use AST transformations. Benefits include multiline lambda syntax and lists of single line lambdas.* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Bryan Thornbury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### A Better Lambda 2 | [![PyPI version](https://badge.fury.io/py/lambdazen.svg)](https://badge.fury.io/py/lambdazen) 3 | [![Build Status](https://travis-ci.org/brthornbury/lambdazen.svg?branch=master)](https://travis-ci.org/brthornbury/lambdazen) 4 | *Supports Python 2.6 - 3.5* 5 | 6 | **What is this?** 7 | 8 | A better python lambda syntax for your anonymous function needs. 9 | 10 | Write `a = (x) > x` instead of `a = lambda x: x`. See below for syntax caveats. 11 | 12 | Get started immediately: `pip install lambdazen` 13 | 14 | ```python 15 | from lambdazen import zen 16 | def otherfunc(*args): 17 | print sum(args) 18 | 19 | # The zen decorator allows you to define lambdas with a better syntax 20 | @zen 21 | def example(): 22 | example.epic = (x, y, z) > otherfunc(x, y, z) 23 | 24 | # Multiline lambdas are a tuple or list of statements 25 | # The assignment operator inside is << instead of = 26 | # The last statement is the return value 27 | example.multiline = (x, y, z) > ( 28 | s << otherfunc(x, y, z), 29 | s 30 | ) 31 | 32 | # Call function so the lambdas are bound to function attributes 33 | example() 34 | 35 | example.epic(1,2,3) 36 | >>> 6 37 | 38 | example.multiline(1,2,3) 39 | >>> 6 40 | ``` 41 | 42 | **Caveats** 43 | - better lambdas can only be defined in a function with the `@zen` attribute 44 | - any other code in this function will be executed, it's best to use the function as a container of lambdas 45 | 46 | **How does it work** 47 | 48 | [Read the story](https://github.com/brthornbury/lambdazen/blob/master/HowItWorks.md) 49 | 50 | TLDR; Runtime in-memory source rewriting and recompilation 51 | 52 | **Additional Examples** 53 | 54 | ```python 55 | from lambdazen import zen 56 | 57 | # Lambdas don't need to be bound to the function 58 | @zen 59 | def normalizeString(nS): 60 | transforms = [ 61 | (s) > s.strip(), 62 | (s) > s.lower(), 63 | (s) > s.replace(' ', '_')] 64 | 65 | apply_all = (transforms_list, s) > ( 66 | is_done << (len(transforms_list) == 0), 67 | current_transform << (transforms_list[0] if not is_done else None), 68 | remaining_transforms << (transforms_list[1:] if not is_done else None), 69 | current_transform(apply_all(remaining_transforms, s)) if not is_done else s) 70 | 71 | return apply_all(transforms, nS) 72 | 73 | normalizeString("Abraham Lincoln") 74 | >>> "abraham_lincoln" 75 | ``` 76 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | A Better Lambda 2 | ~~~~~~~~~~~~~~~ 3 | 4 | | |PyPI version| 5 | | |Build Status| 6 | | *Supports Python 2.6 - 3.5* 7 | 8 | **What is this?** 9 | 10 | A better python lambda syntax for your anonymous function needs. 11 | 12 | Write ``a = (x) > x`` instead of ``a = lambda x: x``. See below for 13 | syntax caveats. 14 | 15 | Get started immediately: ``pip install lambdazen`` 16 | 17 | .. code:: python 18 | 19 | from lambdazen import zen 20 | def otherfunc(*args): 21 | print sum(args) 22 | 23 | # The zen decorator allows you to define lambdas with a better syntax 24 | @zen 25 | def example(): 26 | example.epic = (x, y, z) > otherfunc(x, y, z) 27 | 28 | # Multiline lambdas are a tuple or list of statements 29 | # The assignment operator inside is << instead of = 30 | # The last statement is the return value 31 | example.multiline = (x, y, z) > ( 32 | s << otherfunc(x, y, z), 33 | s 34 | ) 35 | 36 | # Call function so the lambdas are bound to function attributes 37 | example() 38 | 39 | example.epic(1,2,3) 40 | >>> 6 41 | 42 | example.multiline(1,2,3) 43 | >>> 6 44 | 45 | **Caveats** 46 | 47 | - better lambdas can only be defined in a function with the ``@zen`` 48 | attribute 49 | - any other code in this function will be executed, it’s best to use 50 | the function as a container of lambdas 51 | 52 | **How does it work** 53 | 54 | `Read the story`_ 55 | 56 | TLDR; Runtime in-memory source rewriting and recompilation 57 | 58 | **Additional Examples** 59 | 60 | .. code:: python 61 | 62 | from lambdazen import zen 63 | 64 | # Lambdas don't need to be bound to the function 65 | @zen 66 | def normalizeString(nS): 67 | transforms = [ 68 | (s) > s.strip(), 69 | (s) > s.lower(), 70 | (s) > s.replace(' ', '_')] 71 | 72 | apply_all = (transforms_list, s) > ( 73 | is_done << (len(transforms_list) == 0), 74 | current_transform << (transforms_list[0] if not is_done else None), 75 | remaining_transforms << (transforms_list[1:] if not is_done else None), 76 | current_transform(apply_all(remaining_transforms, s)) if not is_done else s) 77 | 78 | return apply_all(transforms, nS) 79 | 80 | normalizeString("Abraham Lincoln") 81 | >>> "abraham_lincoln" 82 | 83 | .. _Read the story: https://github.com/brthornbury/lambdazen/blob/master/HowItWorks.md 84 | 85 | .. |PyPI version| image:: https://badge.fury.io/py/lambdazen.svg 86 | :target: https://badge.fury.io/py/lambdazen 87 | .. |Build Status| image:: https://travis-ci.org/brthornbury/lambdazen.svg?branch=master 88 | :target: https://travis-ci.org/brthornbury/lambdazen -------------------------------------------------------------------------------- /lambdazen/__init__.py: -------------------------------------------------------------------------------- 1 | from .lambdazen import zen -------------------------------------------------------------------------------- /lambdazen/lambdazen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import inspect 3 | import re 4 | import ast, _ast 5 | 6 | def _replace_match(match): 7 | vars = match.groups(1)[0] 8 | return "= lambda {0}:".format(vars) 9 | 10 | def _is_multiline_lambda(function_body_node): 11 | return type(function_body_node) is _ast.Tuple or type(function_body_node) is _ast.List 12 | 13 | 14 | def _transform_multiline_assignment_statements(statements): 15 | assignment_statements = [statement for statement in statements 16 | if type(statement) is _ast.BinOp 17 | and type(statement.op) is _ast.LShift 18 | and type(statement.left) is _ast.Name] 19 | 20 | other_statements = [statement for statement in statements if statement not in assignment_statements] 21 | 22 | assignments = [ast.Assign(targets=[statement.left], value=statement.right, lineno=statement.lineno, col_offset=statement.col_offset) 23 | for statement in assignment_statements] 24 | 25 | for assignment in assignments: 26 | assignment.targets[0].ctx = ast.Store() 27 | 28 | return other_statements + assignments 29 | 30 | 31 | def _transform_multiline_return_statement(return_statement): 32 | return ast.Return(value=return_statement, lineno=return_statement.lineno, col_offset = return_statement.col_offset) 33 | 34 | 35 | def _transform_function_arguments(left): 36 | if type(left) is ast.Name: 37 | names = [left] 38 | else: 39 | names = left.elts 40 | 41 | # Python3 42 | if hasattr(_ast, 'arg'): 43 | args = [_ast.arg(annotation=None, arg=name.id, col_offset = name.col_offset, lineno=name.lineno) for name in names] 44 | return ast.arguments(args=args, defaults=[], kwonlyargs=[], kw_defaults=[]) 45 | 46 | # Python 2 47 | arguments = ast.arguments(args=names, defaults=[]) 48 | for argument in arguments.args: 49 | argument.ctx = ast.Param() 50 | 51 | return arguments 52 | 53 | class FunctionNodeVisitor(ast.NodeTransformer): 54 | 55 | def visit_FunctionDef(self, node): 56 | """ 57 | :type node: _ast.FunctionDef 58 | """ 59 | children = node.body 60 | lambda_assign_children = [child for child in children 61 | if type(child) == _ast.Assign 62 | and len(child.targets) == 1 63 | and type(child.value) == _ast.Compare 64 | and (type(child.value.left) == _ast.Tuple or type(child.value.left) == _ast.Name) 65 | and all(map(lambda t: type(t) == _ast.Name, getattr(child.value.left, 'elts', [])))] 66 | 67 | # Support single line lambdas outside of assigns 68 | other_children = [child for child in children if child not in lambda_assign_children] 69 | for child in other_children: 70 | CompareNodeVisitor().visit(child) 71 | 72 | for assign_type_child in lambda_assign_children: 73 | arguments = _transform_function_arguments(assign_type_child.value.left) 74 | function_body = assign_type_child.value.comparators[0] 75 | 76 | if _is_multiline_lambda(function_body): 77 | all_statements = function_body.elts 78 | 79 | return_statement = all_statements[-1] 80 | statements = all_statements[0:len(all_statements) - 1] 81 | 82 | statements = _transform_multiline_assignment_statements(statements) 83 | return_statement = _transform_multiline_return_statement(return_statement) 84 | 85 | assign_target = assign_type_child.targets[0] 86 | if type(assign_target) is _ast.Attribute: 87 | function_name = assign_target.attr 88 | else: 89 | function_name = assign_target.id 90 | 91 | all_transformed_statements = statements + [return_statement] 92 | functiondef_object = ast.FunctionDef(args = arguments, 93 | body=all_transformed_statements, 94 | lineno=assign_type_child.lineno, 95 | name=function_name, 96 | col_offset=assign_type_child.col_offset, 97 | decorator_list=[]) 98 | 99 | children.insert(0, functiondef_object) 100 | assign_type_child.value = ast.Name(id=functiondef_object.name, 101 | col_offset=functiondef_object.col_offset, 102 | lineno=functiondef_object.lineno, 103 | ctx=ast.Load()) 104 | else: 105 | lambda_ast_transform = ast.Lambda(args=arguments, 106 | body=function_body, 107 | lineno=assign_type_child.lineno, 108 | col_offset = assign_type_child.col_offset) 109 | assign_type_child.value = lambda_ast_transform 110 | 111 | return node 112 | 113 | class CompareNodeVisitor(ast.NodeTransformer): 114 | 115 | def visit_Compare(self, node): 116 | """ 117 | :type node: _ast.FunctionDef 118 | """ 119 | 120 | is_lambda_def = len(node.ops) == 1\ 121 | and type(node.ops[0]) is _ast.Gt \ 122 | and (type(node.left) is _ast.Tuple or type(node.left) is _ast.Name) \ 123 | and all(map(lambda t: type(t) == _ast.Name, getattr(node.left, 'elts', []))) 124 | 125 | if not is_lambda_def: 126 | return node 127 | 128 | arguments = _transform_function_arguments(node.left) 129 | function_body = node.comparators[0] 130 | 131 | lambda_ast_transform = ast.Lambda(args=arguments, 132 | body=function_body, 133 | lineno=node.lineno, 134 | col_offset=node.col_offset) 135 | return lambda_ast_transform 136 | 137 | def _transform_ast(code_ast): 138 | code_ast = FunctionNodeVisitor().visit(code_ast) 139 | return code_ast 140 | 141 | def _zen_decorator(func): 142 | source = inspect.getsource(func) 143 | 144 | # remove leading whitespace 145 | leading_whitespace_length = len(re.match('\s*', source).group()) 146 | source = '\n'.join( 147 | [line[leading_whitespace_length:] if len(line) > leading_whitespace_length else line 148 | for line in source.split('\n')]) 149 | 150 | # remove attribute to prevent looping endlessly 151 | source = re.sub('\s*@zen\s*\n', '', source) 152 | 153 | code_ast = ast.parse(source) 154 | code_ast = _transform_ast(code_ast) 155 | 156 | # Gather the original scope of the function 157 | frame = inspect.currentframe().f_back.f_back 158 | globals, locals = frame.f_globals, frame.f_locals 159 | globals.update(locals) 160 | 161 | # Run the newly compiled source to define the new function 162 | recompiled_source = compile(code_ast, '', 'exec') 163 | exec(recompiled_source, globals) 164 | 165 | new_function = globals[func.__name__] 166 | return new_function 167 | 168 | def zen(func): 169 | return _zen_decorator(func) 170 | -------------------------------------------------------------------------------- /lambdazen/test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brthor/lambdazen/9383abf7348a2cae7672dc34d6fd7510b611a0af/lambdazen/test/__init__.py -------------------------------------------------------------------------------- /lambdazen/test/test_given_a_zen_decorator.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from lambdazen import zen 3 | 4 | module_var = "module_var" 5 | 6 | class GivenAZenDecorator(unittest.TestCase): 7 | def test_It_creates_functions_from_alternate_lambda_syntax(self): 8 | 9 | @zen 10 | def lambdaContainer(): 11 | lambdaContainer.func = (x) > x + 1 12 | 13 | lambdaContainer.x = lambda y: y+1 14 | 15 | lambdaContainer() 16 | 17 | def emptyFunction(): 18 | pass 19 | 20 | self.assertTrue(type(lambdaContainer.func) == type(emptyFunction)) 21 | self.assertTrue(lambdaContainer.func(1) == 2) 22 | self.assertTrue(lambdaContainer.func(2) == 3) 23 | self.assertTrue(lambdaContainer.func(3) == 4) 24 | 25 | def test_It_creates_lists_of_single_line_lambdas(self): 26 | 27 | @zen 28 | def normalizeString(nS): 29 | transforms = [ 30 | (s) > s.strip(), 31 | (s) > s.lower(), 32 | (s) > s.replace(' ', '_')] 33 | 34 | apply_all = (transforms_list, s) > ( 35 | is_done << (len(transforms_list) == 0), 36 | current_transform << (transforms_list[0] if not is_done else None), 37 | remaining_transforms << (transforms_list[1:] if not is_done else None), 38 | current_transform(apply_all(remaining_transforms, s)) if not is_done else s) 39 | 40 | return apply_all(transforms, nS) 41 | 42 | self.assertTrue(normalizeString("Abraham Lincoln") == "abraham_lincoln") 43 | 44 | 45 | 46 | def test_It_creates_functions_from_nonbound_lambda_assigns(self): 47 | 48 | @zen 49 | def lambdaContainer(): 50 | nonBound = (x) > x + 1 51 | self.assertTrue(nonBound(1) == 2) 52 | 53 | lambdaContainer() 54 | 55 | def test_It_creates_functions_that_can_use_local_scope(self): 56 | @zen 57 | def lambdaContainer(local_var): 58 | lambdaContainer.func = (y) > y + local_var 59 | 60 | lambdaContainer(2) 61 | self.assertTrue(lambdaContainer.func(2) == 4) 62 | 63 | def test_It_creates_functions_out_of_multiline_syntax(self): 64 | outer_var = 's' 65 | 66 | @zen 67 | def lambdaContainer(): 68 | lambdaContainer.func = (x, y, z) > ( 69 | s << x + y + z, 70 | s 71 | ) 72 | 73 | lambdaContainer.func2 = (x, y, z) > [ 74 | s << x + y + z, 75 | s 76 | ] 77 | 78 | lambdaContainer.outerFunc = () > ( 79 | s << outer_var, 80 | s 81 | ) 82 | 83 | lambdaContainer.moduleFunc = () > ( 84 | s << module_var, 85 | s 86 | ) 87 | 88 | lambdaContainer.combinedScopeFunc = (x) > ( 89 | s << module_var + outer_var + x, 90 | s 91 | ) 92 | 93 | lambdaContainer.singleLineTest = () > ( 94 | 1 95 | ) 96 | 97 | nonBoundLambdaTest = () > ( 98 | s << "yes", 99 | s) 100 | 101 | self.assertTrue(nonBoundLambdaTest() == "yes") 102 | 103 | lambdaContainer() 104 | 105 | self.assertTrue(lambdaContainer.func(1,2,3) == 6) 106 | self.assertTrue(lambdaContainer.func2(1, 2, 3) == 6) 107 | self.assertTrue(lambdaContainer.outerFunc() == outer_var) 108 | self.assertTrue(lambdaContainer.moduleFunc() == module_var) 109 | self.assertTrue(lambdaContainer.combinedScopeFunc("t") == (module_var + outer_var + "t")) 110 | self.assertTrue(lambdaContainer.singleLineTest() == 1) 111 | 112 | def test_It_creates_functions_with_multiple_arguments(self): 113 | 114 | @zen 115 | def lambdaContainer(): 116 | lambdaContainer.func = (x, y, z) > x + y + z 117 | 118 | lambdaContainer() 119 | 120 | self.assertTrue(lambdaContainer.func(1,3,5) == 9) 121 | 122 | def test_It_creates_functions_with_no_arguments(self): 123 | @zen 124 | def lambdaContainer(): 125 | lambdaContainer.func = () > "hello" 126 | 127 | lambdaContainer() 128 | 129 | self.assertTrue(lambdaContainer.func() == "hello") 130 | 131 | def test_It_creates_functions_that_call_other_functions(self): 132 | def otherfunc(*args): 133 | return sum(args) 134 | 135 | @zen 136 | def lambdaContainer(): 137 | lambdaContainer.func = (x, y, z) > otherfunc(x,y,z) 138 | 139 | lambdaContainer() 140 | 141 | self.assertTrue(lambdaContainer.func(1,5,7) == 13) 142 | 143 | def test_It_creates_functions_that_call_other_functions_when_nested_in_another_function(self): 144 | 145 | def outer(): 146 | def otherfunc(*args): 147 | return sum(args) 148 | 149 | @zen 150 | def lambdaContainer(): 151 | lambdaContainer.func = (x, y, z) > otherfunc(x,y,z) 152 | 153 | lambdaContainer() 154 | 155 | self.assertTrue(lambdaContainer.func(1,5,7) == 13) 156 | 157 | outer() 158 | 159 | def test_It_creates_multiple_functions(self): 160 | @zen 161 | def lambdaContainer(): 162 | lambdaContainer.func = () > "hello" 163 | lambdaContainer.func2 = (x) > x + 1 164 | lambdaContainer.func3 = (x) > x * 2 165 | 166 | lambdaContainer() 167 | 168 | self.assertTrue(lambdaContainer.func() == "hello") 169 | self.assertTrue(lambdaContainer.func2(1) == 2) 170 | self.assertTrue(lambdaContainer.func3(5) == 10) 171 | 172 | def test_It_creates_functions_that_use_values_outside_function_scope(self): 173 | outer_var = 5 174 | 175 | @zen 176 | def lambdaContainer(): 177 | lambdaContainer.func = () > outer_var 178 | lambdaContainer.func2 = (x) > outer_var + x 179 | 180 | lambdaContainer() 181 | 182 | self.assertTrue(lambdaContainer.func() == 5) 183 | self.assertTrue(lambdaContainer.func2(5) == 10) 184 | 185 | def test_It_creates_functions_that_use_values_in_module_scope(self): 186 | outer_var = "s" 187 | 188 | @zen 189 | def lambdaContainer(): 190 | lambdaContainer.func = () > module_var 191 | lambdaContainer.func2 = (x) > module_var + outer_var + x 192 | 193 | lambdaContainer() 194 | 195 | self.assertTrue(lambdaContainer.func() == "module_var") 196 | self.assertTrue(lambdaContainer.func2("t") == "module_varst") 197 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup(name='lambdazen', 4 | version='0.1.7', 5 | description='Syntax changes for python lambdas.', 6 | url='http://github.com/brthornbury/lambdazen', 7 | author='Bryan Thornbury', 8 | author_email='author@example.com', 9 | license='MIT', 10 | packages=['lambdazen'], 11 | zip_safe=False, 12 | 13 | # See https://pypi.python.org/pypi?%3Aaction=list_classifiers 14 | classifiers=[ 15 | # Specify the Python versions you support here. In particular, ensure 16 | # that you indicate whether you support Python 2, Python 3 or both. 17 | 'Programming Language :: Python :: 2', 18 | 'Programming Language :: Python :: 2.6', 19 | 'Programming Language :: Python :: 2.7', 20 | 'Programming Language :: Python :: 3', 21 | 'Programming Language :: Python :: 3.3', 22 | 'Programming Language :: Python :: 3.4', 23 | 'Programming Language :: Python :: 3.5', 24 | ]) --------------------------------------------------------------------------------