diff --git a/.gitignore b/.gitignore index 27b56e02d..776f6d90d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ .DS_Store answers .hg +.idea \ No newline at end of file diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 000000000..d6122b7f2 --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,11 @@ +# Install custom tools, runtimes, etc. +# For example "bastet", a command-line tetris clone: +# RUN brew install bastet +# +# More information: https://www.gitpod.io/docs/config-docker/ + +FROM gitpod/workspace-full:latest + +USER gitpod + +RUN pip3 install pytest==4.4.2 pytest-testdox mock \ No newline at end of file diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000..94253242b --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,14 @@ +image: + file: .gitpod.Dockerfile + +tasks: + - command: python contemplate_koans.py + +github: + prebuilds: + # enable for the master/default branch (defaults to true) + master: true + # enable for pull requests coming from this repo (defaults to true) + pullRequests: false + # add a "Review in Gitpod" button as a comment to pull requests (defaults to true) + addComment: false diff --git a/.hgignore b/.hgignore index cf9029f6d..f1035d222 100644 --- a/.hgignore +++ b/.hgignore @@ -4,3 +4,4 @@ syntax: glob .DS_Store answers .git +.idea \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..e8f7d64ac --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: python + +python: + - 3.9 + +script: + - python _runner_tests.py +# - python contemplate_koans.py # Run all the koans +# - python contemplate_koans.py about_asserts about_none # Run a subset of +# # koans +# +# Working through Python Koans in a fork? Want to use Travis CI to show which +# koans you've passed? Then comment out one of the above "contemplate_koans" +# lines above! +# +notifications: + email: true + +# Some other koans (see runner/sensei.py or "ls koans" to see the light): +# +# about_none about_lists about_list_assignments about_dictionaries +# about_strings about_tuples about_methods about_control_statements +# about_true_and_false about_sets ... diff --git a/Contributor Notes.txt b/Contributor Notes.txt index c3cd6b256..c14bb947a 100644 --- a/Contributor Notes.txt +++ b/Contributor Notes.txt @@ -1,15 +1,12 @@ - Testing a specific koan =================================== -This will help when adding/modifing koans +This will help when adding/modifying koans Running a whole test case: - $ python contemplate_koans.py about_strings - or $ python3 contemplate_koans.py about_strings - + Running a single test: - $ python contemplate_koans.py about_strings.AboutStrings.test_triple_quoted_strings_need_less_escaping + $ python3 contemplate_koans.py about_strings.AboutStrings.test_triple_quoted_strings_need_less_escaping diff --git a/MIT-LICENSE b/MIT-LICENSE index 128401ac9..5e57f89fd 100644 --- a/MIT-LICENSE +++ b/MIT-LICENSE @@ -1,22 +1,19 @@ -Copyright (c) 2010 Greg Malcolm and The Status Is Not Quo +Copyright 2021 Greg Malcolm and The Status Is Not Quo -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following -conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.rst b/README.rst new file mode 100644 index 000000000..e2f303fe9 --- /dev/null +++ b/README.rst @@ -0,0 +1,242 @@ +============ +Python Koans +============ + +.. image:: https://travis-ci.org/gregmalcolm/python_koans.png?branch=master + :target: http://travis-ci.org/gregmalcolm/python_koans + +.. image:: https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod + :target: https://gitpod.io/#https://github.com/gregmalcolm/python_koans + +.. image:: https://www.eclipse.org/che/contribute.svg + :target: https://workspaces.openshift.com/f?url=https://gitpod.io/#https://github.com/gregmalcolm/python_koans + +One click installation: +----------------------- + +.. image:: https://www.eclipse.org/che/contribute.svg + :target: https://workspaces.openshift.com/f?url=https://gitpod.io/#https://github.com/gregmalcolm/python_koans +| or +.. image:: https://gitpod.io/button/open-in-gitpod.svg + :target: https://gitpod.io/#https://gitpod.io/#https://github.com/gregmalcolm/python_koans + +| + +Python Koans is a port of Edgecase's "Ruby Koans" which can be found +at http://rubykoans.com/. + +.. image:: https://user-images.githubusercontent.com/2614930/28401740-ec6214b2-6cd0-11e7-8afd-30ed3102bfd6.png + +Python Koans is an interactive tutorial for learning the Python programming +language by making tests pass. + +Most tests are *fixed* by filling the missing parts of assert functions. Eg: + +.. code-block:: python + + self.assertEqual(__, 1+2) + +which can be fixed by replacing the __ part with the appropriate code: + +.. code-block:: python + + self.assertEqual(3, 1+2) + +Occasionally you will encounter some failing tests that are already filled out. +In these cases you will need to finish implementing some code to progress. For +example, there is an exercise for writing some code that will tell you if a +triangle is equilateral, isosceles or scalene. + +As well as being a great way to learn some Python, it is also a good way to get +a taste of Test Driven Development (TDD). + + +Downloading Python Koans +------------------------ + +Python Koans is available on GitHub: + +* https://github.com/gregmalcolm/python_koans + +You can clone with Git or download the source as a zip/gz/bz2. + + +Installing Python Koans +----------------------- + +Aside from downloading or checking out the latest version of Python Koans, you +need to install the Python interpreter. + +At this time of writing, we support Python 3. The policy is to try to keep +current with the latest production version. + +You should be able to work with newer Python versions, but older ones will +likely give you problems. + +You can download Python from here: + +* https://www.python.org/downloads/ + +After installing Python make sure the folder containing the python executable +is in the system path. In other words, you need to be able to run Python from a +command console. It will either be ``python3`` or for Windows it will be ``python.exe``. + +If you have problems, this may help: + +* https://www.python.org/about/gettingstarted/ + +Windows users may also want to update the line in the batch file ``run.bat`` to +set the python path:: + + SET PYTHON_PATH=C:\Python39 + + +Getting Started +--------------- + +Jake Hebbert has created a couple of screencasts available here: + +https://www.youtube.com/watch?v=e2WXgXEjbHY&list=PL5Up_u-XkWgNcunP_UrTJG_3EXgbK2BQJ&index=1 + +Or if you prefer to read: + +From a \*nix terminal or Windows command prompt run:: + +.. code-block:: sh + + python contemplate_koans.py + +or: + +.. code-block:: sh + + python3 contemplate_koans.py + +In my case I'm using Python 3 with Windows, so I fire up my command +shell (cmd.exe) and run this: + +.. image:: https://user-images.githubusercontent.com/2614930/28401747-f723ff00-6cd0-11e7-9b9a-a6993b753cf6.png + +Apparently a test failed:: + + AssertionError: False is not True + +It also tells me exactly where the problem is, it's an assert on line 12 +of ``.\\koans\\about_asserts.py``. This one is easy, just change ``False`` to ``True`` to +make the test pass. + +Sooner or later you will likely encounter tests where you are not sure what the +expected value should be. For example: + +.. code-block:: python + + class Dog: + pass + + def test_objects_are_objects(self): + fido = self.Dog() + self.assertEqual(__, isinstance(fido, object)) + +This is where the Python Command Line can come in handy. In this case I can +fire up the command line, recreate the scenario and run queries: + +.. image:: https://user-images.githubusercontent.com/2614930/28401750-f9dcb296-6cd0-11e7-98eb-c20318eada33.png + +Sniffer Support +--------------- + +Sniffer allows you to run the tests continuously. If you modify any files files +in the koans directory, it will rerun the tests. + +To set this up, you need to install sniffer: + +.. code-block:: sh + + python3 -m pip install sniffer + +You should also run one of these libraries depending on your system. This will +automatically trigger sniffer when a file changes, otherwise sniffer will have +to poll to see if the files have changed. + +On Linux: + +.. code-block:: sh + + python3 -m pip install pyinotify + +On Windows: + +.. code-block:: sh + + python3 -m pip install pywin32 + + Also available here: + + https://github.com/mhammond/pywin32/releases + +On macOS: + +.. code-block:: sh + + python3 -m pip install MacFSEvents + +Once it is set up, you just run: + +.. code-block:: sh + + sniffer + +Just modify one of the koans files and you'll see that the tests are triggered +automatically. Sniffer is controlled by ``scent.py``. + +Getting the Most From the Koans +------------------------------- + +Quoting the Ruby Koans instructions: + + "In test-driven development the mantra has always been, red, green, + refactor. Write a failing test and run it (red), make the test pass + (green), then refactor it (that is look at the code and see if you + can make it any better). In this case you will need to run the koan + and see it fail (red), make the test pass (green), then take a + moment and reflect upon the test to see what it is teaching you + and improve the code to better communicate its intent (refactor)." + + + +Finding More Koan Projects +-------------------------- + +There are number of other great Koan projects out there for various languages +and frameworks. Most of them can be found in GitHub. Also there is a little +koans activity on Bitbucket. + +* GitHub koan projects: + https://github.com/search?q=koans&ref=cmdform + +* Bitbucket koan projects: + https://bitbucket.org/repo/all?name=koans + +Translations +------------ + +Translations are always welcome! Feel free to add one to this README +if you happen to work on one: + +https://github.com/mswell/python_koans_br + +Acknowledgments +--------------- + +Thanks go to Jim Weirich and Joe O'Brien for the original Ruby Koans that the +Python Koans is based on! Also the Ruby Koans in turn borrows from Metakoans +so thanks also go to Ara Howard for that! + +Also thanks to everyone who has contributed to Python Koans! I got a great +headstart by taking over a code base initiated by the combined Mikes of +FPIP. So here's a little plug for their very cool Python podcast: + +* https://www.frompythonimportpodcast.com/ + +A big thanks also to Mike Pirnat @pirnat and Kevin Chase @kjc have pitched in +as co-maintainers at various times diff --git a/README.txt b/README.txt deleted file mode 100644 index cac80571f..000000000 --- a/README.txt +++ /dev/null @@ -1,145 +0,0 @@ -============ -Python Koans -============ - -Python Koans is a port of Edgecase's "Ruby Koans". - -Python Koans is an interactive tutorial for learning Python by making tests pass. - -Most tests are 'fixed' by filling the missing parts of assert functions. Eg: - - self.assertEqual(__, 1+2) - -which can be fixed by replacing the __ part with the appropriate code: - - self.assertEqual(3, 1+2) - -Occasionally you will encounter some failing tests that are already filled out. In these cases you will need to finish implementing some code to progress. For example, there is an exercise for writing some code that will tell you if a triangle is equilateral, isosceles or scalene. - -As well as being a great way to learn some Python, it is also a good way to get a taste of Test Driven Development (TDD). - - -Downloading Python Koans ------------------------- - -Python Koans is available through Mercurial on bitbucket: - - http://bitbucket.org/gregmalcolm/python_koans - -It is also mirrored on github for Git users : - - http://wiki.github.com/gregmalcolm/python_koans - -Either site will allow you to download the source as a zip/gz/bz2. - - -Installing Python Koans ------------------------ - -Aside from downloading or checking out the latest version of Python Koans, all you need to install is Python. - -At this time of writing there are two versions of the koans, one for use with Python 2.6 and one for Python 3.1. You should be able to work with newer Python versions, but older ones will likely give you problems. - -You can download Python from here: - - http://www.python.org/download - -On installing Python make sure the folder containing the python executable is in the system path. In other words, you need to be able to be able to run Python from a command console. With Python 2 it will be called 'python' or 'python.exe' depending on the operating system. For Python 3 it will either be 'python3' or for windows it will be 'python.exe'. - -If you have problems, this may help: - - http://www.python.org/about/gettingstarted - - -Getting Started ---------------- - -From a *nix terminal or windows command prompt go to the python koans\python_VERSION folder and run: - - python contemplate_koans.py - -or - - python3 contemplate_koans.py - -In my case I'm using Python 3 with windows, so I fire up my command shell (cmd.exe) and run this: - - C:\>cd "c:\hg\python_koans\python 3" - C:\hg\python_koans\python 3_1>python contemplate_koans.py - - Thinking AboutAsserts - test_assert_truth has damaged your karma. - - You have not yet reached enlightenment ... - AssertionError: False is not True - - Please meditate on the following code: - File "C:\hg\python_koans\python 3\koans\about_asserts.py", line 12, in test_ - assert_truth - self.assertTrue(False) # This should be true - - - Beautiful is better than ugly. - C:\hg\python_koans\python 3> - -Apparently a test failed: - - AssertionError: False is not True - -It also tells me exactly where the problem in, its an assert on line 12 of .\koans\about_asserts.py. This one is easy, just change False to True to make the test pass. - -Sooner or later you will likely encounter tests where you are not sure what the expected value should be. For example: - - class Dog: - pass - - def test_objects_are_objects(self): - fido = self.Dog() - self.assertEqual(__, isinstance(fido, object)) - -This is where the Python Command Line can come in handy. in this case I can fire up the command line, recreate the scenario and run queries: - - C:\hg\python_koans\python 3>python - Python 3.1.2 (r312:79149, Mar 21 2010, 00:41:52) [MSC v.1500 32 bit (Intel)] on - win32 - Type "help", "copyright", "credits" or "license" for more information. - >>> class Dog: pass - ... - >>> fido = Dog() - >>> isinstance(fido, object) - True - >>> - -Getting the Most From the Koans -------------------------------- - -Quoting the Ruby Koans instructions: - - "In test-driven development the mantra has always been, red, green, refactor. Write a failing test and run it (red), make the test pass (green), then refactor it (that is look at the code and see if you can make it any better. In this case you will need to run the koan and see it fail (red), make the test pass (green), then take a moment and reflect upon the test to see what it is teaching you and improve the code to better communicate its intent (refactor)." - - -Content -------- - -Python is a made up of about 2/3 Ruby Koans ported material and 1/3 Python specific tests. The content ported from Ruby Koans includes all the assignment projects. - -Content for Python 3 is a little different to the Python 2 flavor due to big changes between the 2 different languages. For example in the Python 2 variant the differences between old and new style classes are covered. This loses relevance in in the Python 3 version, but there are some extra tests covering new functionality. - - -Finding More Koan Projects --------------------------- - -Right now there are a lot of spinoff Koan projects out there for a great number of languages and frameworks. Many of them do not have that much content, but contributing to them is a great way to learn. At the moment most of them can be found by searching for 'koans' on github. - -A couple of promising projects include DotNetKoans and TestMongoKoans. - - -Acknowledgments ---------------- - -Thanks go to Jim Weirich and Joe O'Brien for the original Ruby Koans that Python Koans is based on! Also the Ruby Koans in turn borrows from Metakoans so thanks also go to Ara Howard for that! - - -Also thanks to everyone who helped with the Python Koans conversion! In particular I got a great headstart on the project by forking from this Python Koans startup project: - - http://bitbucket.org/mcrute/python_koans/ diff --git a/python 2/_runner_tests.py b/_runner_tests.py similarity index 57% rename from python 2/_runner_tests.py rename to _runner_tests.py index 55ef5a12a..26fec382d 100644 --- a/python 2/_runner_tests.py +++ b/_runner_tests.py @@ -7,14 +7,20 @@ from runner.runner_tests.test_mountain import TestMountain from runner.runner_tests.test_sensei import TestSensei from runner.runner_tests.test_helper import TestHelper +from runner.runner_tests.test_path_to_enlightenment import TestFilterKoanNames +from runner.runner_tests.test_path_to_enlightenment import TestKoansSuite + def suite(): suite = unittest.TestSuite() suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMountain)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestSensei)) suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestHelper)) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFilterKoanNames)) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestKoansSuite)) return suite + if __name__ == '__main__': - unittest.TextTestRunner(verbosity=2).run(suite()) - \ No newline at end of file + res = unittest.TextTestRunner(verbosity=2).run(suite()) + sys.exit(not res.wasSuccessful()) diff --git a/contemplate_koans.py b/contemplate_koans.py new file mode 100644 index 000000000..ddf992388 --- /dev/null +++ b/contemplate_koans.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +# +# Acknowledgment: +# +# Python Koans is a port of Ruby Koans originally written by Jim Weirich +# and Joe O'brien of Edgecase. There are some differences and tweaks specific +# to the Python language, but a great deal of it has been copied wholesale. +# So thanks guys! +# + +import sys + +if __name__ == '__main__': + if sys.version_info < (3, 0): + print("\nThis is the Python 3 version of Python Koans, but you are " + + "running it with Python 2!\n\n" + "Did you accidentally use the wrong Python script? \nTry:\n\n" + + " python3 contemplate_koans.py\n") + else: + if sys.version_info < (3, 7): + print("\n" + + "********************************************************\n" + + "WARNING:\n" + + "This version of Python Koans was designed for " + + "Python 3.7 or greater.\n" + + "Your version of Python is older, so you may run into " + + "problems!\n\n" + + "But let's see how far we get...\n" + + "********************************************************\n") + + from runner.mountain import Mountain + + Mountain().walk_the_path(sys.argv) diff --git a/python 2/example_file.txt b/example_file.txt similarity index 100% rename from python 2/example_file.txt rename to example_file.txt diff --git a/koans.txt b/koans.txt new file mode 100644 index 000000000..8e2788028 --- /dev/null +++ b/koans.txt @@ -0,0 +1,40 @@ +# Lines starting with # are ignored. +koans.about_asserts.AboutAsserts +koans.about_strings.AboutStrings +koans.about_none.AboutNone +koans.about_lists.AboutLists +koans.about_list_assignments.AboutListAssignments +koans.about_dictionaries.AboutDictionaries +koans.about_string_manipulation.AboutStringManipulation +koans.about_tuples.AboutTuples +koans.about_methods.AboutMethods +koans.about_control_statements.AboutControlStatements +koans.about_true_and_false.AboutTrueAndFalse +koans.about_sets.AboutSets +koans.about_triangle_project.AboutTriangleProject +koans.about_exceptions.AboutExceptions +koans.about_triangle_project2.AboutTriangleProject2 +koans.about_iteration.AboutIteration +koans.about_comprehension.AboutComprehension +koans.about_generators.AboutGenerators +koans.about_lambdas.AboutLambdas +koans.about_scoring_project.AboutScoringProject +koans.about_classes.AboutClasses +koans.about_with_statements.AboutWithStatements +koans.about_monkey_patching.AboutMonkeyPatching +koans.about_dice_project.AboutDiceProject +koans.about_method_bindings.AboutMethodBindings +koans.about_decorating_with_functions.AboutDecoratingWithFunctions +koans.about_decorating_with_classes.AboutDecoratingWithClasses +koans.about_inheritance.AboutInheritance +koans.about_multiple_inheritance.AboutMultipleInheritance +koans.about_scope.AboutScope +koans.about_modules.AboutModules +koans.about_packages.AboutPackages +koans.about_class_attributes.AboutClassAttributes +koans.about_attribute_access.AboutAttributeAccess +koans.about_deleting_objects.AboutDeletingObjects +koans.about_proxy_object_project.AboutProxyObjectProject +koans.about_proxy_object_project.TelevisionTest +koans.about_extra_credit.AboutExtraCredit +koans.about_regex.AboutRegex diff --git a/python 2/koans/GREEDS_RULES.txt b/koans/GREEDS_RULES.txt similarity index 100% rename from python 2/koans/GREEDS_RULES.txt rename to koans/GREEDS_RULES.txt diff --git a/python 2/koans/__init__.py b/koans/__init__.py similarity index 100% rename from python 2/koans/__init__.py rename to koans/__init__.py diff --git a/python 2/koans/a_package_folder/__init__.py b/koans/a_package_folder/__init__.py similarity index 100% rename from python 2/koans/a_package_folder/__init__.py rename to koans/a_package_folder/__init__.py diff --git a/python 3/koans/a_package_folder/a_module.py b/koans/a_package_folder/a_module.py similarity index 100% rename from python 3/koans/a_package_folder/a_module.py rename to koans/a_package_folder/a_module.py diff --git a/python 2/koans/about_asserts.py b/koans/about_asserts.py similarity index 52% rename from python 2/koans/about_asserts.py rename to koans/about_asserts.py index 3905498d3..d17ed0cdf 100644 --- a/python 2/koans/about_asserts.py +++ b/koans/about_asserts.py @@ -9,13 +9,18 @@ def test_assert_truth(self): """ We shall contemplate truth by testing reality, via asserts. """ - self.assertTrue(False) # This should be true - + + # Confused? This video should help: + # + # http://bit.ly/about_asserts + + self.assertTrue(False) # This should be True + def test_assert_with_message(self): """ Enlightenment may be more easily achieved with appropriate messages. """ - self.assertTrue(False, "This should be true -- Please fix this") + self.assertTrue(False, "This should be True -- Please fix this") def test_fill_in_values(self): """ @@ -37,14 +42,37 @@ def test_a_better_way_of_asserting_equality(self): """ expected_value = __ actual_value = 1 + 1 - + self.assertEqual(expected_value, actual_value) - + def test_that_unittest_asserts_work_the_same_way_as_python_asserts(self): """ - Knowing how things really work is half the battle + Understand what lies within. """ - + # This throws an AssertionError exception assert False - + + def test_that_sometimes_we_need_to_know_the_class_type(self): + """ + What is in a class name? + """ + + # Sometimes we will ask you what the class type of an object is. + # + # For example, contemplate the text string "navel". What is its class type? + # The koans runner will include this feedback for this koan: + # + # AssertionError: '-=> FILL ME IN! <=-' != + # + # So "navel".__class__ is equal to ? No not quite. This + # is just what it displays. The answer is simply str. + # + # See for yourself: + + self.assertEqual(__, "navel".__class__) # It's str, not + + # Need an illustration? More reading can be found here: + # + # https://github.com/gregmalcolm/python_koans/wiki/Class-Attribute + diff --git a/python 3/koans/about_attribute_access.py b/koans/about_attribute_access.py similarity index 83% rename from python 3/koans/about_attribute_access.py rename to koans/about_attribute_access.py index 4eabb76f9..f12f61143 100644 --- a/python 3/koans/about_attribute_access.py +++ b/koans/about_attribute_access.py @@ -8,113 +8,114 @@ from runner.koan import * class AboutAttributeAccess(Koan): - + class TypicalObject: pass - + def test_calling_undefined_functions_normally_results_in_errors(self): typical = self.TypicalObject() - + with self.assertRaises(___): typical.foobar() - + def test_calling_getattribute_causes_an_attribute_error(self): typical = self.TypicalObject() with self.assertRaises(___): typical.__getattribute__('foobar') - + # THINK ABOUT IT: # # If the method __getattribute__() causes the AttributeError, then # what would happen if we redefine __getattribute__()? - + # ------------------------------------------------------------------ - + class CatchAllAttributeReads: def __getattribute__(self, attr_name): return "Someone called '" + attr_name + "' and it could not be found" - + def test_all_attribute_reads_are_caught(self): catcher = self.CatchAllAttributeReads() - - self.assertRegexpMatches(catcher.foobar, __) + + self.assertRegex(catcher.foobar, __) def test_intercepting_return_values_can_disrupt_the_call_chain(self): catcher = self.CatchAllAttributeReads() - self.assertRegexpMatches(catcher.foobaz, __) # This is fine - + self.assertRegex(catcher.foobaz, __) # This is fine + try: catcher.foobaz(1) except TypeError as ex: err_msg = ex.args[0] - - self.assertRegexpMatches(err_msg, __) - + + self.assertRegex(err_msg, __) + # foobaz returns a string. What happens to the '(1)' part? # Try entering this into a python console to reproduce the issue: # # "foobaz"(1) # - + def test_changes_to_the_getattribute_implementation_affects_getattr_function(self): catcher = self.CatchAllAttributeReads() - - self.assertRegexpMatches(getattr(catcher, 'any_attribute'), __) - + + self.assertRegex(getattr(catcher, 'any_attribute'), __) + # ------------------------------------------------------------------ - + class WellBehavedFooCatcher: def __getattribute__(self, attr_name): if attr_name[:3] == "foo": return "Foo to you too" else: return super().__getattribute__(attr_name) - + def test_foo_attributes_are_caught(self): catcher = self.WellBehavedFooCatcher() - + self.assertEqual(__, catcher.foo_bar) self.assertEqual(__, catcher.foo_baz) - + def test_non_foo_messages_are_treated_normally(self): catcher = self.WellBehavedFooCatcher() - + with self.assertRaises(___): catcher.normal_undefined_attribute # ------------------------------------------------------------------ - + global stack_depth - stack_depth = 0 + stack_depth = 0 class RecursiveCatcher: def __init__(self): global stack_depth - stack_depth = 0 + stack_depth = 0 self.no_of_getattribute_calls = 0 - - def __getattribute__(self, attr_name): - global stack_depth # We need something that is outside the scope of this class + + def __getattribute__(self, attr_name): + # We need something that is outside the scope of this class: + global stack_depth stack_depth += 1 if stack_depth<=10: # to prevent a stack overflow self.no_of_getattribute_calls += 1 # Oops! We just accessed an attribute (no_of_getattribute_calls) # Guess what happens when self.no_of_getattribute_calls is - # accessed? + # accessed? # Using 'object' directly because using super() here will also # trigger a __getattribute__() call. - return object.__getattribute__(self, attr_name) - + return object.__getattribute__(self, attr_name) + def my_method(self): pass - + def test_getattribute_is_a_bit_overzealous_sometimes(self): catcher = self.RecursiveCatcher() catcher.my_method() global stack_depth self.assertEqual(__, stack_depth) - + # ------------------------------------------------------------------ class MinimalCatcher: @@ -126,7 +127,7 @@ def __init__(self): def __getattr__(self, attr_name): self.no_of_getattr_calls += 1 return self.DuffObject - + def my_method(self): pass @@ -135,62 +136,66 @@ def test_getattr_ignores_known_attributes(self): catcher.my_method() self.assertEqual(__, catcher.no_of_getattr_calls) - + def test_getattr_only_catches_unknown_attributes(self): catcher = self.MinimalCatcher() catcher.purple_flamingos() catcher.free_pie() - + self.assertEqual(__, type(catcher.give_me_duff_or_give_me_death()).__name__) - + self.assertEqual(__, catcher.no_of_getattr_calls) - + # ------------------------------------------------------------------ class PossessiveSetter(object): def __setattr__(self, attr_name, value): new_attr_name = attr_name - + if attr_name[-5:] == 'comic': new_attr_name = "my_" + new_attr_name elif attr_name[-3:] == 'pie': - new_attr_name = "a_" + new_attr_name + new_attr_name = "a_" + new_attr_name - object.__setattr__(self, new_attr_name, value) + object.__setattr__(self, new_attr_name, value) def test_setattr_intercepts_attribute_assignments(self): fanboy = self.PossessiveSetter() - + fanboy.comic = 'The Laminator, issue #1' fanboy.pie = 'blueberry' - - self.assertEqual(__, fanboy.a_pie) + + self.assertEqual(__, fanboy.a_pie) + + # + # NOTE: Change the prefix to make this next assert pass + # prefix = '__' self.assertEqual("The Laminator, issue #1", getattr(fanboy, prefix + '_comic')) # ------------------------------------------------------------------ - class ScarySetter: + class ScarySetter: def __init__(self): self.num_of_coconuts = 9 self._num_of_private_coconuts = 2 - + def __setattr__(self, attr_name, value): new_attr_name = attr_name - + if attr_name[0] != '_': new_attr_name = "altered_" + new_attr_name - + object.__setattr__(self, new_attr_name, value) - + def test_it_modifies_external_attribute_as_expected(self): setter = self.ScarySetter() setter.e = "mc hammer" - + self.assertEqual(__, setter.altered_e) - + def test_it_mangles_some_internal_attributes(self): setter = self.ScarySetter() diff --git a/python 3/koans/about_class_attributes.py b/koans/about_class_attributes.py similarity index 88% rename from python 3/koans/about_class_attributes.py rename to koans/about_class_attributes.py index bdf0ea70a..7a5be1df4 100644 --- a/python 3/koans/about_class_attributes.py +++ b/koans/about_class_attributes.py @@ -10,38 +10,38 @@ class AboutClassAttributes(Koan): class Dog: pass - + def test_objects_are_objects(self): fido = self.Dog() self.assertEqual(__, isinstance(fido, object)) def test_classes_are_types(self): self.assertEqual(__, self.Dog.__class__ == type) - + def test_classes_are_objects_too(self): self.assertEqual(__, issubclass(self.Dog, object)) - + def test_objects_have_methods(self): fido = self.Dog() self.assertEqual(__, len(dir(fido))) - + def test_classes_have_methods(self): self.assertEqual(__, len(dir(self.Dog))) def test_creating_objects_without_defining_a_class(self): - singularity = object() + singularity = object() self.assertEqual(__, len(dir(singularity))) def test_defining_attributes_on_individual_objects(self): fido = self.Dog() fido.legs = 4 - + self.assertEqual(__, fido.legs) - + def test_defining_functions_on_individual_objects(self): fido = self.Dog() fido.wag = lambda : 'fidos wag' - + self.assertEqual(__, fido.wag()) def test_other_objects_are_not_affected_by_these_singleton_functions(self): @@ -49,13 +49,13 @@ def test_other_objects_are_not_affected_by_these_singleton_functions(self): rover = self.Dog() def wag(): - return 'fidos wag' + return 'fidos wag' fido.wag = wag - + with self.assertRaises(___): rover.wag() - + # ------------------------------------------------------------------ - + class Dog2: def wag(self): return 'instance wag' @@ -69,28 +69,28 @@ def growl(self): @staticmethod def bark(): return "staticmethod bark, arg: None" - + @classmethod def growl(cls): - return "classmethod growl, arg: cls=" + cls.__name__ - + return "classmethod growl, arg: cls=" + cls.__name__ + def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too(self): - self.assertRegexpMatches(self.Dog2.growl(), __) - + self.assertRegex(self.Dog2.growl(), __) + def test_classmethods_are_not_independent_of_instance_methods(self): fido = self.Dog2() - self.assertRegexpMatches(fido.growl(), __) - self.assertRegexpMatches(self.Dog2.growl(), __) + self.assertRegex(fido.growl(), __) + self.assertRegex(self.Dog2.growl(), __) def test_staticmethods_are_unbound_functions_housed_in_a_class(self): - self.assertRegexpMatches(self.Dog2.bark(), __) + self.assertRegex(self.Dog2.bark(), __) def test_staticmethods_also_overshadow_instance_methods(self): fido = self.Dog2() - self.assertRegexpMatches(fido.bark(), __) - + self.assertRegex(fido.bark(), __) + # ------------------------------------------------------------------ - + class Dog3: def __init__(self): self._name = None @@ -100,37 +100,37 @@ def get_name_from_instance(self): def set_name_from_instance(self, name): self._name = name - - @classmethod + + @classmethod def get_name(cls): return cls._name - @classmethod + @classmethod def set_name(cls, name): cls._name = name - + name = property(get_name, set_name) name_from_instance = property(get_name_from_instance, set_name_from_instance) - + def test_classmethods_can_not_be_used_as_properties(self): fido = self.Dog3() with self.assertRaises(___): fido.name = "Fido" - + def test_classes_and_instances_do_not_share_instance_attributes(self): fido = self.Dog3() fido.set_name_from_instance("Fido") fido.set_name("Rover") self.assertEqual(__, fido.get_name_from_instance()) self.assertEqual(__, self.Dog3.get_name()) - + def test_classes_and_instances_do_share_class_attributes(self): fido = self.Dog3() fido.set_name("Fido") self.assertEqual(__, fido.get_name()) self.assertEqual(__, self.Dog3.get_name()) - + # ------------------------------------------------------------------ - + class Dog4: def a_class_method(cls): return 'dogs class method' @@ -140,15 +140,15 @@ def a_static_method(): a_class_method = classmethod(a_class_method) a_static_method = staticmethod(a_static_method) - + def test_you_can_define_class_methods_without_using_a_decorator(self): self.assertEqual(__, self.Dog4.a_class_method()) def test_you_can_define_static_methods_without_using_a_decorator(self): self.assertEqual(__, self.Dog4.a_static_method()) - + # ------------------------------------------------------------------ - + def test_heres_an_easy_way_to_explicitly_call_class_methods_from_instance_methods(self): fido = self.Dog4() self.assertEqual(__, fido.__class__.a_class_method()) diff --git a/python 3/koans/about_classes.py b/koans/about_classes.py similarity index 81% rename from python 3/koans/about_classes.py rename to koans/about_classes.py index fdc67e17a..22dc4e7f0 100644 --- a/python 3/koans/about_classes.py +++ b/koans/about_classes.py @@ -3,79 +3,81 @@ from runner.koan import * + class AboutClasses(Koan): class Dog: "Dogs need regular walkies. Never, ever let them drive." - - def test_instances_of_classes_can_be_created_adding_parenthesis(self): + + def test_instances_of_classes_can_be_created_adding_parentheses(self): + # NOTE: The .__name__ attribute will convert the class + # into a string value. fido = self.Dog() - self.assertEqual(__, type(fido).__name__) - + self.assertEqual(__, fido.__class__.__name__) + def test_classes_have_docstrings(self): - self.assertRegexpMatches(self.Dog.__doc__, __) - + self.assertRegex(self.Dog.__doc__, __) + # ------------------------------------------------------------------ - - class Dog2: - def __init__(self): + + class Dog2: + def __init__(self): self._name = 'Paul' - + def set_name(self, a_name): self._name = a_name - + def test_init_method_is_the_constructor(self): dog = self.Dog2() self.assertEqual(__, dog._name) - - def test_private_attributes_are_not_really_private(self): + + def test_private_attributes_are_not_really_private(self): dog = self.Dog2() dog.set_name("Fido") self.assertEqual(__, dog._name) # The _ prefix in _name implies private ownership, but nothing is truly # private in Python. - + def test_you_can_also_access_the_value_out_using_getattr_and_dict(self): fido = self.Dog2() fido.set_name("Fido") - + self.assertEqual(__, getattr(fido, "_name")) # getattr(), setattr() and delattr() are a way of accessing attributes # by method rather than through assignment operators - + self.assertEqual(__, fido.__dict__["_name"]) # Yes, this works here, but don't rely on the __dict__ object! Some # class implementations use optimization which result in __dict__ not # showing everything. - + # ------------------------------------------------------------------ - + class Dog3: - def __init__(self): + def __init__(self): self._name = None def set_name(self, a_name): self._name = a_name - + def get_name(self): return self._name - + name = property(get_name, set_name) - - + def test_that_name_can_be_read_as_a_property(self): fido = self.Dog3() fido.set_name("Fido") - + # access as method self.assertEqual(__, fido.get_name()) - + # access as property self.assertEqual(__, fido.name) - + # ------------------------------------------------------------------ - + class Dog4: - def __init__(self): + def __init__(self): self._name = None @property @@ -85,79 +87,82 @@ def name(self): @name.setter def name(self, a_name): self._name = a_name - + def test_creating_properties_with_decorators_is_slightly_easier(self): fido = self.Dog4() - + fido.name = "Fido" self.assertEqual(__, fido.name) - + # ------------------------------------------------------------------ - + class Dog5: - def __init__(self, initial_name): + def __init__(self, initial_name): self._name = initial_name @property def name(self): return self._name - + def test_init_provides_initial_values_for_instance_variables(self): - fido = self.Dog5("Fido") + fido = self.Dog5("Fido") self.assertEqual(__, fido.name) - + def test_args_must_match_init(self): - with self.assertRaises(___): self.Dog5() - + with self.assertRaises(___): + self.Dog5() + # THINK ABOUT IT: # Why is this so? - - def test_different_objects_have_difference_instance_variables(self): + + def test_different_objects_have_different_instance_variables(self): fido = self.Dog5("Fido") rover = self.Dog5("Rover") - + self.assertEqual(__, rover.name == fido.name) - + # ------------------------------------------------------------------ - + class Dog6: def __init__(self, initial_name): self._name = initial_name - + def get_self(self): return self - + def __str__(self): + # + # Implement this! + # return __ - + def __repr__(self): return "" def test_inside_a_method_self_refers_to_the_containing_object(self): fido = self.Dog6("Fido") - - self.assertEqual(__, fido.get_self()) # Not a string! - + + self.assertEqual(__, fido.get_self()) # Not a string! + def test_str_provides_a_string_version_of_the_object(self): fido = self.Dog6("Fido") - + self.assertEqual("Fido", str(fido)) - + def test_str_is_used_explicitly_in_string_interpolation(self): fido = self.Dog6("Fido") - + self.assertEqual(__, "My dog is " + str(fido)) - + def test_repr_provides_a_more_complete_string_version(self): fido = self.Dog6("Fido") self.assertEqual(__, repr(fido)) - + def test_all_objects_support_str_and_repr(self): - seq = [1,2,3] - + seq = [1, 2, 3] + self.assertEqual(__, str(seq)) self.assertEqual(__, repr(seq)) - + self.assertEqual(__, str("STRING")) self.assertEqual(__, repr("STRING")) - diff --git a/koans/about_comprehension.py b/koans/about_comprehension.py new file mode 100644 index 000000000..e5add6d9e --- /dev/null +++ b/koans/about_comprehension.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from runner.koan import * + + +class AboutComprehension(Koan): + + + def test_creating_lists_with_list_comprehensions(self): + feast = ['lambs', 'sloths', 'orangutans', 'breakfast cereals', + 'fruit bats'] + + comprehension = [delicacy.capitalize() for delicacy in feast] + + self.assertEqual(__, comprehension[0]) + self.assertEqual(__, comprehension[2]) + + def test_filtering_lists_with_list_comprehensions(self): + feast = ['spam', 'sloths', 'orangutans', 'breakfast cereals', + 'fruit bats'] + + comprehension = [delicacy for delicacy in feast if len(delicacy) > 6] + + self.assertEqual(__, len(feast)) + self.assertEqual(__, len(comprehension)) + + def test_unpacking_tuples_in_list_comprehensions(self): + list_of_tuples = [(1, 'lumberjack'), (2, 'inquisition'), (4, 'spam')] + comprehension = [ skit * number for number, skit in list_of_tuples ] + + self.assertEqual(__, comprehension[0]) + self.assertEqual(__, comprehension[2]) + + def test_double_list_comprehension(self): + list_of_eggs = ['poached egg', 'fried egg'] + list_of_meats = ['lite spam', 'ham spam', 'fried spam'] + + + comprehension = [ '{0} and {1}'.format(egg, meat) for egg in list_of_eggs for meat in list_of_meats] + + + self.assertEqual(__, comprehension[0]) + self.assertEqual(__, len(comprehension)) + + def test_creating_a_set_with_set_comprehension(self): + comprehension = { x for x in 'aabbbcccc'} + + self.assertEqual(__, comprehension) # remember that set members are unique + + def test_creating_a_dictionary_with_dictionary_comprehension(self): + dict_of_weapons = {'first': 'fear', 'second': 'surprise', + 'third':'ruthless efficiency', 'fourth':'fanatical devotion', + 'fifth': None} + + dict_comprehension = { k.upper(): weapon for k, weapon in dict_of_weapons.items() if weapon} + + self.assertEqual(__, 'first' in dict_comprehension) + self.assertEqual(__, 'FIRST' in dict_comprehension) + self.assertEqual(__, len(dict_of_weapons)) + self.assertEqual(__, len(dict_comprehension)) diff --git a/python 3/koans/about_control_statements.py b/koans/about_control_statements.py similarity index 73% rename from python 3/koans/about_control_statements.py rename to koans/about_control_statements.py index f1cb5c0bd..842c8d91f 100644 --- a/python 3/koans/about_control_statements.py +++ b/koans/about_control_statements.py @@ -17,7 +17,16 @@ def test_if_then_statements(self): if True: result = 'true value' self.assertEqual(__, result) - + + def test_if_then_elif_else_statements(self): + if False: + result = 'first value' + elif True: + result = 'true value' + else: + result = 'default value' + self.assertEqual(__, result) + def test_while_statement(self): i = 1 result = 1 @@ -25,7 +34,7 @@ def test_while_statement(self): result = result * i i += 1 self.assertEqual(__, result) - + def test_break_statement(self): i = 1 result = 1 @@ -34,39 +43,38 @@ def test_break_statement(self): result = result * i i += 1 self.assertEqual(__, result) - + def test_continue_statement(self): i = 0 result = [] while i < 10: i += 1 if (i % 2) == 0: continue - result.append(i) + result.append(i) self.assertEqual(__, result) - + def test_for_statement(self): phrase = ["fish", "and", "chips"] result = [] for item in phrase: result.append(item.upper()) self.assertEqual([__, __, __], result) - + def test_for_statement_with_tuples(self): round_table = [ - ("Lancelot", "Blue"), + ("Lancelot", "Blue"), ("Galahad", "I don't know!"), ("Robin", "Blue! I mean Green!"), - ("Arthur", "Is that an African Swallow or Amazonian Swallow?") + ("Arthur", "Is that an African Swallow or European Swallow?") ] result = [] for knight, answer in round_table: result.append("Contestant: '" + knight + "' Answer: '" + answer + "'") - + text = __ - - self.assertRegexpMatches(result[2], text) - - self.assertNoRegexpMatches(result[0], text) - self.assertNoRegexpMatches(result[1], text) - self.assertNoRegexpMatches(result[3], text) - \ No newline at end of file + + self.assertRegex(result[2], text) + + self.assertNotRegex(result[0], text) + self.assertNotRegex(result[1], text) + self.assertNotRegex(result[3], text) diff --git a/python 3/koans/about_decorating_with_classes.py b/koans/about_decorating_with_classes.py similarity index 91% rename from python 3/koans/about_decorating_with_classes.py rename to koans/about_decorating_with_classes.py index 358488c4c..00bd5fe90 100644 --- a/python 3/koans/about_decorating_with_classes.py +++ b/koans/about_decorating_with_classes.py @@ -4,7 +4,7 @@ from runner.koan import * import functools - + class AboutDecoratingWithClasses(Koan): def maximum(self, a, b): if a>b: @@ -18,17 +18,17 @@ def test_partial_that_wrappers_no_args(self): the partial. """ max = functools.partial(self.maximum) - + self.assertEqual(__, max(7,23)) self.assertEqual(__, max(10,-10)) - + def test_partial_that_wrappers_first_arg(self): max0 = functools.partial(self.maximum, 0) self.assertEqual(__, max0(-4)) self.assertEqual(__, max0(5)) - def test_partial_that_wrappers_all_args(self): + def test_partial_that_wrappers_all_args(self): always99 = functools.partial(self.maximum, 99, 20) always20 = functools.partial(self.maximum, 9, 20) @@ -40,43 +40,43 @@ def test_partial_that_wrappers_all_args(self): class doubleit: def __init__(self, fn): self.fn = fn - + def __call__(self, *args): return self.fn(*args) + ', ' + self.fn(*args) - + def __get__(self, obj, cls=None): if not obj: # Decorating an unbound function return self else: # Decorating a bound method - return functools.partial(self, obj) + return functools.partial(self, obj) @doubleit def foo(self): return "foo" - + @doubleit def parrot(self, text): return text.upper() - + def test_decorator_with_no_arguments(self): # To clarify: the decorator above the function has no arguments, even # if the decorated function does - + self.assertEqual(__, self.foo()) self.assertEqual(__, self.parrot('pieces of eight')) # ------------------------------------------------------------------ def sound_check(self): - #Note: no decorator + #Note: no decorator return "Testing..." def test_what_a_decorator_is_doing_to_a_function(self): #wrap the function with the decorator self.sound_check = self.doubleit(self.sound_check) - + self.assertEqual(__, self.sound_check()) # ------------------------------------------------------------------ @@ -84,24 +84,24 @@ def test_what_a_decorator_is_doing_to_a_function(self): class documenter: def __init__(self, *args): self.fn_doc = args[0] - + def __call__(self, fn): def decorated_function(*args): return fn(*args) - + if fn.__doc__: decorated_function.__doc__ = fn.__doc__ + ": " + self.fn_doc else: decorated_function.__doc__ = self.fn_doc return decorated_function - + @documenter("Increments a value by one. Kind of.") def count_badly(self, num): num += 1 if num==3: - return 5 + return 5 else: - num + return num @documenter("Does nothing") def idler(self, num): "Idler" @@ -115,7 +115,7 @@ def test_documentor_which_already_has_a_docstring(self): self.assertEqual(__, self.idler.__doc__) # ------------------------------------------------------------------ - + @documenter("DOH!") @doubleit @doubleit @@ -125,4 +125,4 @@ def homer(self): def test_we_can_chain_decorators(self): self.assertEqual(__, self.homer()) self.assertEqual(__, self.homer.__doc__) - \ No newline at end of file + diff --git a/python 2/koans/about_decorating_with_functions.py b/koans/about_decorating_with_functions.py similarity index 79% rename from python 2/koans/about_decorating_with_functions.py rename to koans/about_decorating_with_functions.py index 69cd02d93..a51260213 100644 --- a/python 2/koans/about_decorating_with_functions.py +++ b/koans/about_decorating_with_functions.py @@ -3,31 +3,30 @@ from runner.koan import * - + class AboutDecoratingWithFunctions(Koan): def addcowbell(fn): fn.wow_factor = 'COWBELL BABY!' return fn - + @addcowbell def mediocre_song(self): return "o/~ We all live in a broken submarine o/~" - + def test_decorators_can_modify_a_function(self): - self.assertMatch(__, self.mediocre_song()) - self.assertEqual(__, self.mediocre_song.wow_factor) - + self.assertRegex(self.mediocre_song(), __) + self.assertEqual(__, self.mediocre_song.wow_factor) + # ------------------------------------------------------------------ def xmltag(fn): def func(*args): return '<' + fn(*args) + '/>' return func - + @xmltag def render_tag(self, name): return name - + def test_decorators_can_change_a_function_output(self): self.assertEqual(__, self.render_tag('llama')) - diff --git a/python 3/koans/about_deleting_objects.py b/koans/about_deleting_objects.py similarity index 90% rename from python 3/koans/about_deleting_objects.py rename to koans/about_deleting_objects.py index 87eea9a23..152265c98 100644 --- a/python 3/koans/about_deleting_objects.py +++ b/koans/about_deleting_objects.py @@ -8,36 +8,36 @@ def test_del_can_remove_slices(self): lottery_nums = [4, 8, 15, 16, 23, 42] del lottery_nums[1] del lottery_nums[2:4] - + self.assertEqual(___, lottery_nums) - + def test_del_can_remove_entire_lists(self): lottery_nums = [4, 8, 15, 16, 23, 42] del lottery_nums with self.assertRaises(___): win = lottery_nums - + # ==================================================================== - + class ClosingSale: def __init__(self): self.hamsters = 7 self.zebras = 84 - + def cameras(self): return 34 - + def toilet_brushes(self): return 48 - + def jellies(self): return 5 - + def test_del_can_remove_attributes(self): crazy_discounts = self.ClosingSale() del self.ClosingSale.toilet_brushes del crazy_discounts.hamsters - + try: still_available = crazy_discounts.toilet_brushes() except AttributeError as e: @@ -47,16 +47,16 @@ def test_del_can_remove_attributes(self): still_available = crazy_discounts.hamsters except AttributeError as e: err_msg2 = e.args[0] - - self.assertRegexpMatches(err_msg1, __) - self.assertRegexpMatches(err_msg2, __) + + self.assertRegex(err_msg1, __) + self.assertRegex(err_msg2, __) # ==================================================================== class ClintEastwood: def __init__(self): self._name = None - + def get_name(self): try: return self._name @@ -65,13 +65,13 @@ def get_name(self): def set_name(self, name): self._name = name - + def del_name(self): del self._name - + name = property(get_name, set_name, del_name, \ "Mr Eastwood's current alias") - + def test_del_works_with_properties(self): cowboy = self.ClintEastwood() cowboy.name = 'Senor Ninguno' @@ -79,14 +79,14 @@ def test_del_works_with_properties(self): del cowboy.name self.assertEqual(__, cowboy.name) - - - # ==================================================================== + + + # ==================================================================== class Prisoner: def __init__(self): self._name = None - + @property def name(self): return self._name @@ -94,11 +94,11 @@ def name(self): @name.setter def name(self, name): self._name = name - + @name.deleter def name(self): self._name = 'Number Six' - + def test_another_way_to_make_a_deletable_property(self): citizen = self.Prisoner() citizen.name = "Patrick" @@ -108,12 +108,12 @@ def test_another_way_to_make_a_deletable_property(self): self.assertEqual(__, citizen.name) # ==================================================================== - + class MoreOrganisedClosingSale(ClosingSale): def __init__(self): self.last_deletion = None super().__init__() - + def __delattr__(self, attr_name): self.last_deletion = attr_name diff --git a/python 3/koans/about_dice_project.py b/koans/about_dice_project.py similarity index 92% rename from python 3/koans/about_dice_project.py rename to koans/about_dice_project.py index c9bdc44fe..27bc54a24 100644 --- a/python 3/koans/about_dice_project.py +++ b/koans/about_dice_project.py @@ -8,15 +8,15 @@ class DiceSet: def __init__(self): self._values = None - - @property + + @property def values(self): return self._values - + def roll(self, n): # Needs implementing! # Tip: random.randint(min, max) can be used to generate random numbers - pass + pass class AboutDiceProject(Koan): def test_can_create_a_dice_set(self): @@ -25,43 +25,43 @@ def test_can_create_a_dice_set(self): def test_rolling_the_dice_returns_a_set_of_integers_between_1_and_6(self): dice = DiceSet() - + dice.roll(5) self.assertTrue(isinstance(dice.values, list), "should be a list") self.assertEqual(5, len(dice.values)) for value in dice.values: self.assertTrue(value >= 1 and value <= 6, "value " + str(value) + " must be between 1 and 6") - + def test_dice_values_do_not_change_unless_explicitly_rolled(self): dice = DiceSet() dice.roll(5) first_time = dice.values second_time = dice.values self.assertEqual(first_time, second_time) - + def test_dice_values_should_change_between_rolls(self): dice = DiceSet() - + dice.roll(5) first_time = dice.values - + dice.roll(5) second_time = dice.values - + self.assertNotEqual(first_time, second_time, \ "Two rolls should not be equal") - + # THINK ABOUT IT: # # If the rolls are random, then it is possible (although not # likely) that two consecutive rolls are equal. What would be a # better way to test this? - + def test_you_can_roll_different_numbers_of_dice(self): dice = DiceSet() - + dice.roll(3) self.assertEqual(3, len(dice.values)) - + dice.roll(1) self.assertEqual(1, len(dice.values)) diff --git a/koans/about_dictionaries.py b/koans/about_dictionaries.py new file mode 100644 index 000000000..da1ed6bfe --- /dev/null +++ b/koans/about_dictionaries.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# +# Based on AboutHashes in the Ruby Koans +# + +from runner.koan import * + +class AboutDictionaries(Koan): + def test_creating_dictionaries(self): + empty_dict = dict() + self.assertEqual(dict, type(empty_dict)) + self.assertDictEqual({}, empty_dict) + self.assertEqual(__, len(empty_dict)) + + def test_dictionary_literals(self): + empty_dict = {} + self.assertEqual(dict, type(empty_dict)) + babel_fish = { 'one': 'uno', 'two': 'dos' } + self.assertEqual(__, len(babel_fish)) + + def test_accessing_dictionaries(self): + babel_fish = { 'one': 'uno', 'two': 'dos' } + self.assertEqual(__, babel_fish['one']) + self.assertEqual(__, babel_fish['two']) + + def test_changing_dictionaries(self): + babel_fish = { 'one': 'uno', 'two': 'dos' } + babel_fish['one'] = 'eins' + + expected = { 'two': 'dos', 'one': __ } + self.assertDictEqual(expected, babel_fish) + + def test_dictionary_is_unordered(self): + dict1 = { 'one': 'uno', 'two': 'dos' } + dict2 = { 'two': 'dos', 'one': 'uno' } + + self.assertEqual(__, dict1 == dict2) + + + def test_dictionary_keys_and_values(self): + babel_fish = {'one': 'uno', 'two': 'dos'} + self.assertEqual(__, len(babel_fish.keys())) + self.assertEqual(__, len(babel_fish.values())) + self.assertEqual(__, 'one' in babel_fish.keys()) + self.assertEqual(__, 'two' in babel_fish.values()) + self.assertEqual(__, 'uno' in babel_fish.keys()) + self.assertEqual(__, 'dos' in babel_fish.values()) + + def test_making_a_dictionary_from_a_sequence_of_keys(self): + cards = {}.fromkeys(('red warrior', 'green elf', 'blue valkyrie', 'yellow dwarf', 'confused looking zebra'), 42) + + self.assertEqual(__, len(cards)) + self.assertEqual(__, cards['green elf']) + self.assertEqual(__, cards['yellow dwarf']) + diff --git a/python 3/koans/about_exceptions.py b/koans/about_exceptions.py similarity index 95% rename from python 3/koans/about_exceptions.py rename to koans/about_exceptions.py index 956f45f24..d321bbc50 100644 --- a/python 3/koans/about_exceptions.py +++ b/koans/about_exceptions.py @@ -7,33 +7,33 @@ class AboutExceptions(Koan): class MySpecialError(RuntimeError): pass - + def test_exceptions_inherit_from_exception(self): mro = self.MySpecialError.mro() self.assertEqual(__, mro[1].__name__) self.assertEqual(__, mro[2].__name__) self.assertEqual(__, mro[3].__name__) self.assertEqual(__, mro[4].__name__) - + def test_try_clause(self): result = None try: self.fail("Oops") except Exception as ex: result = 'exception handled' - + ex2 = ex - + self.assertEqual(__, result) - + self.assertEqual(__, isinstance(ex2, Exception)) self.assertEqual(__, isinstance(ex2, RuntimeError)) - + self.assertTrue(issubclass(RuntimeError, Exception), \ "RuntimeError is a subclass of Exception") - + self.assertEqual(__, ex2.args[0]) - + def test_raising_a_specific_error(self): result = None try: @@ -41,10 +41,10 @@ def test_raising_a_specific_error(self): except self.MySpecialError as ex: result = 'exception handled' msg = ex.args[0] - + self.assertEqual(__, result) self.assertEqual(__, msg) - + def test_else_clause(self): result = None try: @@ -54,10 +54,10 @@ def test_else_clause(self): pass else: result = 'no damage done' - + self.assertEqual(__, result) - + def test_finally_clause(self): result = None try: @@ -67,5 +67,5 @@ def test_finally_clause(self): pass finally: result = 'always run' - + self.assertEqual(__, result) diff --git a/python 3/koans/about_extra_credit.py b/koans/about_extra_credit.py similarity index 99% rename from python 3/koans/about_extra_credit.py rename to koans/about_extra_credit.py index 54baef4ea..cb5a67702 100644 --- a/python 3/koans/about_extra_credit.py +++ b/koans/about_extra_credit.py @@ -17,4 +17,3 @@ class AboutExtraCredit(Koan): # test suite in runner/path_to_enlightenment.py def test_extra_credit_task(self): pass - \ No newline at end of file diff --git a/python 3/koans/about_generators.py b/koans/about_generators.py similarity index 82% rename from python 3/koans/about_generators.py rename to koans/about_generators.py index e22b677c7..a81a43ba6 100644 --- a/python 3/koans/about_generators.py +++ b/koans/about_generators.py @@ -11,27 +11,27 @@ from runner.koan import * class AboutGenerators(Koan): - + def test_generating_values_on_the_fly(self): result = list() bacon_generator = (n + ' bacon' for n in ['crunchy','veggie','danish']) - + for bacon in bacon_generator: result.append(bacon) - + self.assertEqual(__, result) - + def test_generators_are_different_to_list_comprehensions(self): num_list = [x*2 for x in range(1,3)] num_generator = (x*2 for x in range(1,3)) - + self.assertEqual(2, num_list[0]) - + # A generator has to be iterated through. with self.assertRaises(___): num = num_generator[0] - - self.assertEqual(__, list(num_generator)[0]) # This works though - + + self.assertEqual(__, list(num_generator)[0]) + # Both list comprehensions and generators can be iterated though. However, a generator # function is only called on the first iteration. The values are generated on the fly # instead of stored. @@ -43,30 +43,30 @@ def test_generator_expressions_are_a_one_shot_deal(self): attempt1 = list(dynamite) attempt2 = list(dynamite) - - self.assertEqual(__, list(attempt1)) - self.assertEqual(__, list(attempt2)) - + + self.assertEqual(__, attempt1) + self.assertEqual(__, attempt2) + # ------------------------------------------------------------------ - + def simple_generator_method(self): yield 'peanut' yield 'butter' yield 'and' yield 'jelly' - + def test_generator_method_will_yield_values_during_iteration(self): result = list() for item in self.simple_generator_method(): result.append(item) self.assertEqual(__, result) - def test_coroutines_can_take_arguments(self): + def test_generators_can_be_manually_iterated_and_closed(self): result = self.simple_generator_method() self.assertEqual(__, next(result)) self.assertEqual(__, next(result)) result.close() - + # ------------------------------------------------------------------ def square_me(self, seq): @@ -91,13 +91,13 @@ def test_generator_keeps_track_of_local_variables(self): self.assertEqual(__, list(result)) # ------------------------------------------------------------------ - - def generator_with_coroutine(self): + + def coroutine(self): result = yield yield result - - def test_generators_can_take_coroutines(self): - generator = self.generator_with_coroutine() + + def test_generators_can_act_as_coroutines(self): + generator = self.coroutine() # THINK ABOUT IT: # Why is this line necessary? @@ -109,36 +109,34 @@ def test_generators_can_take_coroutines(self): self.assertEqual(__, generator.send(1 + 2)) def test_before_sending_a_value_to_a_generator_next_must_be_called(self): - generator = self.generator_with_coroutine() + generator = self.coroutine() try: - generator.send(1+2) + generator.send(1 + 2) except TypeError as ex: - self.assertMatch(__, ex[0]) - + self.assertRegex(ex.args[0], __) + # ------------------------------------------------------------------ - + def yield_tester(self): value = yield if value: yield value else: yield 'no value' - + def test_generators_can_see_if_they_have_been_called_with_a_value(self): generator = self.yield_tester() next(generator) self.assertEqual('with value', generator.send('with value')) - + generator2 = self.yield_tester() next(generator2) self.assertEqual(__, next(generator2)) - def test_send_none_is_equivelant_to_next(self): + def test_send_none_is_equivalent_to_next(self): generator = self.yield_tester() - + next(generator) - # 'next(generator)' is exactly equivelant to 'generator.send(None)' + # 'next(generator)' is exactly equivalent to 'generator.send(None)' self.assertEqual(__, generator.send(None)) - - diff --git a/python 3/koans/about_inheritance.py b/koans/about_inheritance.py similarity index 87% rename from python 3/koans/about_inheritance.py rename to koans/about_inheritance.py index ac9cf0f62..5d2407dfa 100644 --- a/python 3/koans/about_inheritance.py +++ b/koans/about_inheritance.py @@ -5,85 +5,85 @@ class AboutInheritance(Koan): class Dog: - def __init__(self, name): + def __init__(self, name): self._name = name @property def name(self): return self._name - + def bark(self): return "WOOF" - + class Chihuahua(Dog): def wag(self): return "happy" - + def bark(self): return "yip" - + def test_subclasses_have_the_parent_as_an_ancestor(self): self.assertEqual(__, issubclass(self.Chihuahua, self.Dog)) - - def test_this_all_classes_in_python_3_ultimately_inherit_from_object_class(self): + + def test_all_classes_in_python_3_ultimately_inherit_from_object_class(self): self.assertEqual(__, issubclass(self.Chihuahua, object)) - + # Note: This isn't the case in Python 2. In that version you have # to inherit from a built in class or object explicitly - + def test_instances_inherit_behavior_from_parent_class(self): chico = self.Chihuahua("Chico") self.assertEqual(__, chico.name) - + def test_subclasses_add_new_behavior(self): chico = self.Chihuahua("Chico") self.assertEqual(__, chico.wag()) - + fido = self.Dog("Fido") with self.assertRaises(___): fido.wag() - + def test_subclasses_can_modify_existing_behavior(self): chico = self.Chihuahua("Chico") self.assertEqual(__, chico.bark()) - + fido = self.Dog("Fido") self.assertEqual(__, fido.bark()) - + # ------------------------------------------------------------------ - + class BullDog(Dog): def bark(self): return super().bark() + ", GRR" # Note, super() is much simpler to use in Python 3! - + def test_subclasses_can_invoke_parent_behavior_via_super(self): ralph = self.BullDog("Ralph") self.assertEqual(__, ralph.bark()) - + # ------------------------------------------------------------------ - + class GreatDane(Dog): def growl(self): return super().bark() + ", GROWL" - + def test_super_works_across_methods(self): george = self.GreatDane("George") self.assertEqual(__, george.growl()) # --------------------------------------------------------- - + class Pug(Dog): - def __init__(self, name): + def __init__(self, name): pass - + class Greyhound(Dog): - def __init__(self, name): + def __init__(self, name): super().__init__(name) def test_base_init_does_not_get_called_automatically(self): snoopy = self.Pug("Snoopy") with self.assertRaises(___): name = snoopy.name - + def test_base_init_has_to_be_called_explicitly(self): boxer = self.Greyhound("Boxer") - self.assertEqual(__, boxer.name) \ No newline at end of file + self.assertEqual(__, boxer.name) diff --git a/python 3/koans/about_iteration.py b/koans/about_iteration.py similarity index 56% rename from python 3/koans/about_iteration.py rename to koans/about_iteration.py index 5d69651f7..1faca8e33 100644 --- a/python 3/koans/about_iteration.py +++ b/koans/about_iteration.py @@ -7,13 +7,13 @@ class AboutIteration(Koan): def test_iterators_are_a_type(self): it = iter(range(1,6)) - - fib = 0 - + + total = 0 + for num in it: - fib += num - - self.assertEqual(__ , fib) + total += num + + self.assertEqual(__ , total) def test_iterating_with_next(self): stages = iter(['alpha','beta','gamma']) @@ -25,8 +25,8 @@ def test_iterating_with_next(self): next(stages) except StopIteration as ex: err_msg = 'Ran out of iterations' - - self.assertRegexpMatches(err_msg, __) + + self.assertRegex(err_msg, __) # ------------------------------------------------------------------ @@ -36,47 +36,51 @@ def add_ten(self, item): def test_map_transforms_elements_of_a_list(self): seq = [1, 2, 3] mapped_seq = list() - + mapping = map(self.add_ten, seq) - - self.assertNotEqual(list, type(mapping).__name__) - self.assertEqual(__, type(mapping).__name__) - # In Python 3 built in iterator funcs return iteratable view objects + + self.assertNotEqual(list, mapping.__class__) + self.assertEqual(__, mapping.__class__) + # In Python 3 built in iterator funcs return iterable view objects # instead of lists - + for item in mapping: mapped_seq.append(item) - + self.assertEqual(__, mapped_seq) - - # None, iterator methods actually return objects of iter type in + + # Note, iterator methods actually return objects of iter type in # python 3. In python 2 map() would give you a list. - + def test_filter_selects_certain_items_from_a_list(self): - def is_even(item): return (item % 2) == 0 + def is_even(item): + return (item % 2) == 0 seq = [1, 2, 3, 4, 5, 6] even_numbers = list() - + for item in filter(is_even, seq): even_numbers.append(item) - + self.assertEqual(__, even_numbers) - - def test_just_return_first_item_found(self): - def is_big_name(item): return len(item) > 4 - - names = ["Jim", "Bill", "Clarence", "Doug", "Eli"] - name = None + def test_filter_returns_all_items_matching_criterion(self): + def is_big_name(item): + return len(item) > 4 + + names = ["Jim", "Bill", "Clarence", "Doug", "Eli", "Elizabeth"] iterator = filter(is_big_name, names) + + self.assertEqual(__, next(iterator)) + self.assertEqual(__, next(iterator)) + try: - name = next(iterator) + next(iterator) + pass except StopIteration: msg = 'Ran out of big names' - self.assertEqual(__, name) - + self.assertEquals(__, msg) # ------------------------------------------------------------------ @@ -85,58 +89,44 @@ def add(self,accum,item): def multiply(self,accum,item): return accum * item - + def test_reduce_will_blow_your_mind(self): import functools # As of Python 3 reduce() has been demoted from a builtin function # to the functools module. - + result = functools.reduce(self.add, [2, 3, 4]) - self.assertEqual(__, type(result).__name__) + self.assertEqual(__, result.__class__) # Reduce() syntax is same as Python 2 - + self.assertEqual(__, result) - - result2 = functools.reduce(self.multiply, [2, 3, 4], 1) + + result2 = functools.reduce(self.multiply, [2, 3, 4], 1) self.assertEqual(__, result2) - + # Extra Credit: # Describe in your own words what reduce does. - + # ------------------------------------------------------------------ - def test_creating_lists_with_list_comprehensions(self): - feast = ['lambs', 'sloths', 'orangutans', 'breakfast cereals', 'fruit bats'] - - comprehension = [delicacy.capitalize() for delicacy in feast] - - self.assertEqual(__, comprehension[0]) - self.assertEqual(__, comprehension[2]) - def test_use_pass_for_iterations_with_no_body(self): for num in range(1,5): pass - + self.assertEqual(__, num) # ------------------------------------------------------------------ - + def test_all_iteration_methods_work_on_any_sequence_not_just_lists(self): - # Ranges are an iteratable sequence + # Ranges are an iterable sequence result = map(self.add_ten, range(1,4)) self.assertEqual(__, list(result)) - try: - # Files act like a collection of lines - file = open("example_file.txt") - - def make_upcase(line) : return line.strip().upper() - upcase_lines = map(make_upcase, file.readlines()) - self.assertEqual(__, list(upcase_lines)) - - # NOTE: You can create your own collections that work with each, - # map, select, etc. - finally: - # Arg, this is ugly. - # We will figure out how to fix this later. - if file: file.close() + def test_lines_in_a_file_are_iterable_sequences_too(self): + def make_upcase(line): + return line.strip().title() + + file = open("example_file.txt") + upcase_lines = map(make_upcase, file.readlines()) + self.assertEqual(__, list(upcase_lines)) + file.close() diff --git a/python 2/koans/about_lambdas.py b/koans/about_lambdas.py similarity index 96% rename from python 2/koans/about_lambdas.py rename to koans/about_lambdas.py index 2268e6c65..c1e688094 100644 --- a/python 2/koans/about_lambdas.py +++ b/koans/about_lambdas.py @@ -13,14 +13,14 @@ def test_lambdas_can_be_assigned_to_variables_and_called_explicitly(self): self.assertEqual(__, add_one(10)) # ------------------------------------------------------------------ - + def make_order(self, order): return lambda qty: str(qty) + " " + order + "s" - + def test_accessing_lambda_via_assignment(self): sausages = self.make_order('sausage') eggs = self.make_order('egg') - + self.assertEqual(__, sausages(3)) self.assertEqual(__, eggs(2)) diff --git a/python 3/koans/about_list_assignments.py b/koans/about_list_assignments.py similarity index 82% rename from python 3/koans/about_list_assignments.py rename to koans/about_list_assignments.py index 2c8e8639c..8a8d48090 100644 --- a/python 3/koans/about_list_assignments.py +++ b/koans/about_list_assignments.py @@ -11,7 +11,7 @@ class AboutListAssignments(Koan): def test_non_parallel_assignment(self): names = ["John", "Smith"] self.assertEqual(__, names) - + def test_parallel_assignments(self): first_name, last_name = ["John", "Smith"] self.assertEqual(__, first_name) @@ -22,12 +22,18 @@ def test_parallel_assignments_with_extra_values(self): self.assertEqual(__, title) self.assertEqual(__, first_names) self.assertEqual(__, last_name) - + + def test_parallel_assignments_with_fewer_values(self): + title, *first_names, last_name = ["Mr", "Bond"] + self.assertEqual(__, title) + self.assertEqual(__, first_names) + self.assertEqual(__, last_name) + def test_parallel_assignments_with_sublists(self): first_name, last_name = [["Willie", "Rae"], "Johnson"] self.assertEqual(__, first_name) self.assertEqual(__, last_name) - + def test_swapping_with_parallel_assignment(self): first_name = "Roy" last_name = "Rob" diff --git a/python 3/koans/about_lists.py b/koans/about_lists.py similarity index 88% rename from python 3/koans/about_lists.py rename to koans/about_lists.py index 474ecddc1..61cc3bb29 100644 --- a/python 3/koans/about_lists.py +++ b/koans/about_lists.py @@ -16,27 +16,27 @@ def test_creating_lists(self): def test_list_literals(self): nums = list() self.assertEqual([], nums) - + nums[0:] = [1] self.assertEqual([1], nums) - + nums[1:] = [2] self.assertListEqual([1, __], nums) - + nums.append(333) self.assertListEqual([1, 2, __], nums) - + def test_accessing_list_elements(self): noms = ['peanut', 'butter', 'and', 'jelly'] - + self.assertEqual(__, noms[0]) self.assertEqual(__, noms[3]) self.assertEqual(__, noms[-1]) self.assertEqual(__, noms[-3]) - + def test_slicing_lists(self): noms = ['peanut', 'butter', 'and', 'jelly'] - + self.assertEqual(__, noms[0:1]) self.assertEqual(__, noms[0:2]) self.assertEqual(__, noms[2:2]) @@ -50,7 +50,7 @@ def test_slicing_to_the_edge(self): self.assertEqual(__, noms[2:]) self.assertEqual(__, noms[:2]) - + def test_lists_and_ranges(self): self.assertEqual(range, type(range(5))) self.assertNotEqual([1, 2, 3, 4, 5], range(1,6)) @@ -58,6 +58,7 @@ def test_lists_and_ranges(self): self.assertEqual(__, list(range(5, 9))) def test_ranges_with_steps(self): + self.assertEqual(__, list(range(5, 3, -1))) self.assertEqual(__, list(range(0, 8, 2))) self.assertEqual(__, list(range(1, 8, 3))) self.assertEqual(__, list(range(5, -7, -4))) @@ -67,44 +68,42 @@ def test_insertions(self): knight = ['you', 'shall', 'pass'] knight.insert(2, 'not') self.assertEqual(__, knight) - + knight.insert(0, 'Arthur') - self.assertEqual(__, knight) - + self.assertEqual(__, knight) + def test_popping_lists(self): - stack = [10, 20, 30] + stack = [10, 20, 30, 40] stack.append('last') - + self.assertEqual(__, stack) - + popped_value = stack.pop() self.assertEqual(__, popped_value) self.assertEqual(__, stack) - + popped_value = stack.pop(1) self.assertEqual(__, popped_value) self.assertEqual(__, stack) - + # Notice that there is a "pop" but no "push" in python? - + # Part of the Python philosophy is that there ideally should be one and # only one way of doing anything. A 'push' is the same as an 'append'. - + # To learn more about this try typing "import this" from the python # console... ;) - + def test_making_queues(self): queue = [1, 2] queue.append('last') - + self.assertEqual(__, queue) - + popped_value = queue.pop(0) self.assertEqual(__, popped_value) self.assertEqual(__, queue) - - # Note, for Python 2 popping from the left hand side of a list is + + # Note, popping from the left hand side of a list is # inefficient. Use collections.deque instead. - - # This is not as issue for Python 3 through - + diff --git a/python 3/koans/about_method_bindings.py b/koans/about_method_bindings.py similarity index 87% rename from python 3/koans/about_method_bindings.py rename to koans/about_method_bindings.py index 45f09177b..020851bd5 100644 --- a/python 3/koans/about_method_bindings.py +++ b/koans/about_method_bindings.py @@ -11,10 +11,10 @@ def function2(): class Class: def method(self): - return "parrot" + return "parrot" class AboutMethodBindings(Koan): - def test_methods_are_bound_to_an_object(self): + def test_methods_are_bound_to_an_object(self): obj = Class() self.assertEqual(__, obj.method.__self__ == obj) @@ -22,12 +22,12 @@ def test_methods_are_also_bound_to_a_function(self): obj = Class() self.assertEqual(__, obj.method()) self.assertEqual(__, obj.method.__func__(obj)) - + def test_functions_have_attributes(self): obj = Class() self.assertEqual(__, len(dir(function))) self.assertEqual(__, dir(function) == dir(obj.method.__func__)) - + def test_methods_have_different_attributes(self): obj = Class() self.assertEqual(__, len(dir(obj.method))) @@ -38,13 +38,13 @@ def test_setting_attributes_on_an_unbound_function(self): def test_setting_attributes_on_a_bound_method_directly(self): obj = Class() - with self.assertRaises(___): obj.method.cherries = 3 - + with self.assertRaises(___): obj.method.cherries = 3 + def test_setting_attributes_on_methods_by_accessing_the_inner_function(self): obj = Class() obj.method.__func__.cherries = 3 self.assertEqual(__, obj.method.cherries) - + def test_functions_can_have_inner_functions(self): function2.get_fruit = function self.assertEqual(__, function2.get_fruit()) @@ -60,7 +60,7 @@ def __get__(self, obj, cls): return (self, obj, cls) binding = BoundClass() - + def test_get_descriptor_resolves_attribute_binding(self): bound_obj, binding_owner, owner_type = self.binding # Look at BoundClass.__get__(): @@ -68,8 +68,8 @@ def test_get_descriptor_resolves_attribute_binding(self): # binding_owner = obj # owner_type = cls - self.assertEqual(__, type(bound_obj).__name__) - self.assertEqual(__, type(binding_owner).__name__) + self.assertEqual(__, bound_obj.__class__.__name__) + self.assertEqual(__, binding_owner.__class__.__name__) self.assertEqual(AboutMethodBindings, owner_type) # ------------------------------------------------------------------ @@ -82,9 +82,9 @@ def __set__(self, obj, val): self.choice = val color = SuperColor() - - def test_set_descriptor_changes_behavior_of_attribute_assignment_changes(self): + + def test_set_descriptor_changes_behavior_of_attribute_assignment(self): self.assertEqual(None, self.color.choice) self.color = 'purple' self.assertEqual(__, self.color.choice) - + diff --git a/python 3/koans/about_methods.py b/koans/about_methods.py similarity index 92% rename from python 3/koans/about_methods.py rename to koans/about_methods.py ind