├── .gitignore ├── .travis.yml ├── LICENCE ├── MANIFEST.in ├── README.rst ├── dev-requirements.txt ├── requirements.txt ├── setup.py └── weakreflist ├── __init__.py ├── testDocExamples.py ├── testweakreflist.py └── weakreflist.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | dist 3 | build 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.8" 6 | - "nightly" 7 | - "pypy" 8 | - "pypy3" 9 | # command to install dependencies 10 | install: 11 | - "pip install -r requirements.txt" 12 | - "pip install -r dev-requirements.txt" 13 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install coverage coveralls; fi 14 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install restructuredtext_lint Pygments; fi 15 | # command to run tests 16 | script: 17 | - export COVERALLS_REPO_TOKEN=Kp8xKQJRR5tMESCRfHu1AveGCMI61gc7Q 18 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then nosetests --with-coverage --cover-package=weakreflist; fi 19 | - if [[ $TRAVIS_PYTHON_VERSION != '2.7' ]]; then nosetests; fi 20 | - if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then rst-lint README.rst; fi 21 | 22 | after_success: 23 | - coveralls 24 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.rst 2 | include LICENCE 3 | include *.txt 4 | include *.py 5 | include weakreflist/*.py 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | *********** 2 | weakreflist 3 | *********** 4 | -------------------------------------------------------------------- 5 | A WeakList class for storing objects using weak references in a list 6 | -------------------------------------------------------------------- 7 | 8 | .. image:: https://pypip.in/v/weakreflist/badge.png 9 | :target: https://pypi.python.org/pypi/weakreflist 10 | 11 | **Table of Contents** 12 | 13 | .. contents:: 14 | :local: 15 | :depth: 1 16 | :backlinks: none 17 | 18 | 19 | ============ 20 | Installation 21 | ============ 22 | 23 | Install it from PyPi:: 24 | 25 | pip install weakreflist 26 | 27 | or from Github:: 28 | 29 | git clone git@github.com:apieum/weakreflist.git 30 | cd weakreflist 31 | python setup.py install 32 | 33 | 34 | ===== 35 | Usage 36 | ===== 37 | 38 | ``WeakList`` provides the same methods as the built-in ``list`` but will remove any ``weakref``-compatible objects that 39 | are released by the garbage collector. 40 | 41 | 42 | **Example for CPython:** 43 | 44 | .. code-block:: python 45 | 46 | from weakreflist import WeakList 47 | 48 | class A(object): 49 | """weakrefs don't function directly on object()""" 50 | 51 | objectA = A() 52 | my_list = WeakList([objectA]) 53 | assert len(my_list) == 1 54 | del objectA 55 | assert len(my_list) == 0 # objectA removed from list 56 | 57 | 58 | *Note:* 59 | Pypy has a different implementation of garbage collection which changes some behavior of ``weakref``. 60 | This may be true in other 3\ :superscript:`rd`-party compilers such as Jython and Cython. 61 | Some interactive interpreters, like IPython, `may also delay calls to `_ 62 | ``gc``. 63 | 64 | Due to this, you will need to explicitly call ``gc.collect()`` which has a negative impact on performance! 65 | 66 | **Example for other Python implementations** 67 | 68 | .. code-block:: python 69 | 70 | from weakreflist import WeakList 71 | import gc 72 | 73 | class A(object): 74 | """weakrefs don't function directly on object()""" 75 | 76 | objectA = A() 77 | my_list = WeakList([objectA]) 78 | assert len(my_list) == 1 79 | del objectA 80 | 81 | assert len(my_list) == 1 # gc did not run 82 | gc.collect() # must be called explicitly 83 | assert len(my_list) == 0 84 | 85 | 86 | =========== 87 | Development 88 | =========== 89 | 90 | Your feedback, code review, improvements, bug reports, and help to document is appreciated. 91 | You can contact me by email: apieum [at] gmail [dot] com 92 | 93 | Setup 94 | ----- 95 | 96 | Install recommended packages for running tests:: 97 | 98 | pip install -r dev-requirements.txt 99 | 100 | *Note:* 101 | Sometimes `--spec-color` doesn't function. This can potentially be fixed by the following: 102 | 103 | #. Uninstall ``nosespec`` and ``nosecolor`` 104 | #. Reinstall ``nosecolor`` alone 105 | #. Reinstall ``nosespec`` 106 | 107 | Be certain to reinstall them separately and in the order given, otherwise you will continue 108 | to experience the same issue! 109 | 110 | Run tests 111 | --------- 112 | 113 | :: 114 | 115 | git clone git@github.com:apieum/weakreflist.git 116 | cd weakreflist 117 | nosetests --with-spec --spec-color ./weakreflist 118 | # or with watch 119 | # nosetests --with-spec --spec-color --with-watch ./weakreflist 120 | 121 | 122 | ============ 123 | Contributors 124 | ============ 125 | 126 | Thanks to `BoonsNaibot `_ for the following contributions: 127 | * extended slicing support 128 | * `__reversed__`, `count`, `extend`, and `insert` methods. 129 | 130 | 131 | 132 | .. image:: https://secure.travis-ci.org/apieum/weakreflist.png?branch=master 133 | :target: https://travis-ci.org/apieum/weakreflist 134 | -------------------------------------------------------------------------------- /dev-requirements.txt: -------------------------------------------------------------------------------- 1 | mock>=1.0.1 2 | nose>=1.3.0 3 | nosecolor>=0.1 4 | nose-watch>=0.9.1 5 | nosespec>=0.1 6 | pinocchio>=0.4.1 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apieum/weakreflist/3ad6650ac5878d226c0c4e45a4f45f5db7aa89a6/requirements.txt -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from setuptools import setup 3 | import os 4 | 5 | pkgName = 'weakreflist' 6 | setup( 7 | name=pkgName, 8 | version='0.4', 9 | url='http://www.python.org/pypi/' + pkgName, 10 | author='Grégory Salvan', 11 | author_email='apieum@gmail.com', 12 | license='LGPL', 13 | description='A WeakList class for storing objects using weak references in a list.', 14 | long_description=open(os.path.join(os.path.dirname(__file__), 'README.rst')).read(), 15 | classifiers=[ 16 | "Programming Language :: Python", 17 | "Programming Language :: Python :: 3", 18 | "Development Status :: 5 - Production/Stable", 19 | "Environment :: Other Environment", 20 | "Intended Audience :: Developers", 21 | "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)", 22 | "Operating System :: OS Independent", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | ], 25 | include_package_data=True, 26 | packages=['weakreflist'], 27 | zip_safe=True, 28 | ) 29 | -------------------------------------------------------------------------------- /weakreflist/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from .weakreflist import WeakList 3 | 4 | __all__ = ['WeakList'] 5 | -------------------------------------------------------------------------------- /weakreflist/testDocExamples.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import unittest 3 | from .weakreflist import WeakList 4 | import sys 5 | is_pypy = "__pypy__" in sys.builtin_module_names 6 | 7 | 8 | class DocExampleTest(unittest.TestCase): 9 | if not is_pypy: 10 | def test_example1(self): 11 | class A(object): 12 | """weakrefs don't function directly on object()""" 13 | 14 | objectA = A() 15 | my_list = WeakList([objectA]) 16 | assert len(my_list) == 1 17 | del objectA 18 | assert len(my_list) == 0 # objectA removed from list 19 | else: 20 | def test_example2(self): 21 | import gc 22 | 23 | class A(object): 24 | """weakrefs don't function directly on object()""" 25 | 26 | objectA = A() 27 | my_list = WeakList([objectA]) 28 | assert len(my_list) == 1 29 | del objectA 30 | 31 | assert len(my_list) == 1 # gc did not run 32 | gc.collect() # must be called 33 | assert len(my_list) == 0 34 | -------------------------------------------------------------------------------- /weakreflist/testweakreflist.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import weakref 3 | import unittest 4 | from .weakreflist import WeakList 5 | import sys 6 | is_pypy = "__pypy__" in sys.builtin_module_names 7 | if is_pypy: 8 | import gc 9 | 10 | 11 | class WeakrefListTest(unittest.TestCase): 12 | 13 | class objectFake(object): 14 | __count__ = 0 15 | 16 | def __init__(self): 17 | type(self).__count__ += 1 18 | self.index = type(self).__count__ 19 | 20 | def __gt__(self, other): 21 | return self.index > other.index 22 | 23 | def __lt__(self, other): 24 | return self.index < other.index 25 | 26 | def __eq__(self, other): 27 | return self.index == other.index 28 | 29 | def setUp(self): 30 | self.wr_list = WeakList() 31 | 32 | def ref_item(self, index): 33 | return list.__getitem__(self.wr_list, index) 34 | 35 | def test_it_is_instance_of_list(self): 36 | self.assertIsInstance(self.wr_list, list) 37 | 38 | def test_it_stores_a_weakref_ref(self): 39 | fake_obj = self.objectFake() 40 | self.wr_list.append(fake_obj) 41 | self.assertIsInstance(self.ref_item(0), weakref.ReferenceType) 42 | 43 | def test_if_a_weakref_is_already_stored_it_reuses_it(self): 44 | fake_obj = self.objectFake() 45 | self.wr_list.append(fake_obj) 46 | self.wr_list.append(fake_obj) 47 | self.assertEqual(self.ref_item(0), self.ref_item(1)) 48 | self.assertIs(self.wr_list[0], self.wr_list[1]) 49 | 50 | def test_it_knows_if_it_contains_an_object(self): 51 | fake_obj0 = self.objectFake() 52 | fake_obj1 = self.objectFake() 53 | self.wr_list.append(fake_obj0) 54 | self.assertTrue(fake_obj0 in self.wr_list) 55 | self.assertFalse(fake_obj1 in self.wr_list) 56 | 57 | def test_when_all_bindings_to_an_object_are_deleted_all_ref_in_list_are_deleted(self): 58 | fake_obj = self.objectFake() 59 | self.wr_list.append(fake_obj) 60 | self.wr_list.append(fake_obj) 61 | self.assertEqual(2, len(self.wr_list)) 62 | del fake_obj 63 | if is_pypy: 64 | gc.collect() 65 | self.assertEqual(0, len(self.wr_list)) 66 | 67 | def test_it_can_remove_a_value(self): 68 | fake_obj = self.objectFake() 69 | self.wr_list.append(fake_obj) 70 | self.wr_list.append(fake_obj) 71 | self.assertEqual(2, len(self.wr_list)) 72 | self.wr_list.remove(fake_obj) 73 | self.assertEqual(1, len(self.wr_list)) 74 | 75 | def test_it_can_remove_all_values(self): 76 | fake_obj = self.objectFake() 77 | self.wr_list.append(fake_obj) 78 | self.wr_list.append(fake_obj) 79 | self.assertEqual(2, len(self.wr_list)) 80 | self.wr_list.remove_all(fake_obj) 81 | self.assertEqual(0, len(self.wr_list)) 82 | 83 | def test_it_can_del_a_slice(self): 84 | fake_obj0 = self.objectFake() 85 | fake_obj1 = self.objectFake() 86 | self.wr_list.append(fake_obj0) 87 | self.wr_list.append(fake_obj0) 88 | self.wr_list.append(fake_obj1) 89 | del self.wr_list[1:] 90 | with self.assertRaises(IndexError): 91 | self.wr_list[1] 92 | self.assertEqual(1, len(self.wr_list)) 93 | 94 | def test_it_returns_value_and_not_a_ref(self): 95 | fake_obj = self.objectFake() 96 | self.wr_list.append(fake_obj) 97 | self.wr_list[0] = fake_obj 98 | self.assertEqual(fake_obj, self.wr_list[0]) 99 | 100 | def test_it_returns_the_index_of_a_value(self): 101 | fake_obj0 = self.objectFake() 102 | fake_obj1 = self.objectFake() 103 | self.wr_list.append(fake_obj0) 104 | self.wr_list.append(fake_obj0) 105 | self.wr_list.append(fake_obj1) 106 | self.assertEqual(0, self.wr_list.index(fake_obj0)) 107 | self.assertEqual(2, self.wr_list.index(fake_obj1)) 108 | 109 | def test_it_supports_addition(self): 110 | fake_obj = self.objectFake() 111 | expected = WeakList([fake_obj]) 112 | self.wr_list += expected 113 | self.assertEqual(expected, self.wr_list) 114 | self.assertEqual(1, len(self.wr_list)) 115 | 116 | def test_addition_update_finalizer(self): 117 | fake_obj = self.objectFake() 118 | wr_list = WeakList([fake_obj]) 119 | self.wr_list += wr_list 120 | del fake_obj 121 | if is_pypy: 122 | gc.collect() 123 | self.assertEqual(0, len(wr_list)) 124 | self.assertEqual(0, len(self.wr_list)) 125 | 126 | def test_it_supports_iteration(self): 127 | fake_obj = self.objectFake() 128 | self.wr_list.extend([fake_obj, fake_obj, fake_obj, fake_obj]) 129 | num_mock = 0 130 | for mock in self.wr_list: 131 | num_mock += 1 132 | self.assertEqual(mock, fake_obj) 133 | self.assertEqual(num_mock, 4) 134 | 135 | def test_it_appends_ref_values_at_init(self): 136 | fake_obj = self.objectFake() 137 | wr_list = WeakList([fake_obj]) 138 | self.assertEqual(1, len(wr_list)) 139 | del fake_obj 140 | if is_pypy: 141 | gc.collect() 142 | self.assertEqual(0, len(wr_list)) 143 | 144 | def test_it_supports_slice_on_int(self): 145 | self.wr_list = WeakList(range(10)) 146 | self.assertEqual([self.ref_item(1), self.ref_item(2), self.ref_item(3)], self.wr_list[1:4]) 147 | 148 | def test_it_supports_slice_on_objects(self): 149 | fake_obj1 = self.objectFake() 150 | fake_obj2 = self.objectFake() 151 | fake_obj3 = self.objectFake() 152 | fake_obj4 = self.objectFake() 153 | self.wr_list = WeakList([fake_obj1, fake_obj2, fake_obj3, fake_obj4]) 154 | expected = WeakList([self.ref_item(1)(), self.ref_item(2)()]) 155 | self.assertEqual(expected, self.wr_list[1:3]) 156 | 157 | def test_get_slice_update_finalizer(self): 158 | fake_obj1 = self.objectFake() 159 | fake_obj2 = self.objectFake() 160 | self.wr_list = WeakList([fake_obj1, fake_obj2]) 161 | sliced = self.wr_list[1:] 162 | del fake_obj2 163 | if is_pypy: 164 | gc.collect() 165 | self.assertEqual(1, len(self.wr_list)) 166 | self.assertEqual(0, len(sliced)) 167 | 168 | def test_it_supports_slice_with_steps_on_objects(self): 169 | fake_obj1 = self.objectFake() 170 | fake_obj2 = self.objectFake() 171 | fake_obj3 = self.objectFake() 172 | fake_obj4 = self.objectFake() 173 | self.wr_list = WeakList([fake_obj1, fake_obj2, fake_obj3, fake_obj4]) 174 | expected = WeakList([self.ref_item(1)(), self.ref_item(3)()]) 175 | self.assertEqual(expected, self.wr_list[1::2]) 176 | 177 | def test_it_can_set_slice_on_int(self): 178 | self.wr_list[0:] = range(3) 179 | self.assertEqual(self.wr_list[1:], [1, 2]) 180 | self.assertEqual(3, len(self.wr_list)) 181 | 182 | def test_it_can_set_slice_on_objects(self): 183 | fake_obj0 = self.objectFake() 184 | fake_obj1 = self.objectFake() 185 | self.wr_list.append(fake_obj0) 186 | self.wr_list[1:2] = [fake_obj0, fake_obj1] 187 | self.assertEqual(self.ref_item(1)(), fake_obj0) 188 | self.assertEqual(self.ref_item(2)(), fake_obj1) 189 | self.assertEqual(3, len(self.wr_list)) 190 | 191 | def test_it_can_del_slices(self): 192 | fake_obj0 = self.objectFake() 193 | fake_obj1 = self.objectFake() 194 | self.wr_list[0:] = [fake_obj0, fake_obj0, fake_obj1] 195 | del self.wr_list[:2] 196 | self.assertEqual(self.ref_item(0)(), fake_obj1) 197 | self.assertEqual(1, len(self.wr_list)) 198 | 199 | def test_it_supports_extend(self): 200 | fake_obj0 = self.objectFake() 201 | fake_obj1 = self.objectFake() 202 | self.wr_list.extend([fake_obj0, fake_obj0, fake_obj1]) 203 | self.assertEqual(self.ref_item(2)(), fake_obj1) 204 | 205 | def test_extend_update_finalizer(self): 206 | fake_obj = self.objectFake() 207 | wr_list = WeakList([fake_obj]) 208 | self.wr_list.extend(wr_list) 209 | del fake_obj 210 | if is_pypy: 211 | gc.collect() 212 | self.assertEqual(0, len(wr_list)) 213 | self.assertEqual(0, len(self.wr_list)) 214 | 215 | def test_it_supports_count(self): 216 | fake_obj0 = self.objectFake() 217 | fake_obj1 = self.objectFake() 218 | self.wr_list.extend([fake_obj0, fake_obj0, fake_obj1]) 219 | self.assertEqual(2, self.wr_list.count(fake_obj0)) 220 | 221 | def test_it_supports_insert(self): 222 | fake_obj0 = self.objectFake() 223 | fake_obj1 = self.objectFake() 224 | self.wr_list.extend([fake_obj0, fake_obj1]) 225 | self.wr_list.insert(1, fake_obj0) 226 | self.assertEqual(self.ref_item(1)(), fake_obj0) 227 | self.assertEqual(self.ref_item(2)(), fake_obj1) 228 | 229 | def test_it_supports_reverse(self): 230 | fake_obj0 = self.objectFake() 231 | fake_obj1 = self.objectFake() 232 | self.wr_list.extend([fake_obj0, fake_obj1]) 233 | self.wr_list.reverse() 234 | self.assertEqual(self.ref_item(0)(), fake_obj1) 235 | self.assertEqual(self.ref_item(1)(), fake_obj0) 236 | 237 | def test_it_supports_reversed(self): 238 | fake_obj0 = self.objectFake() 239 | fake_obj1 = self.objectFake() 240 | self.wr_list.extend([fake_obj0, fake_obj1]) 241 | expected = reversed(self.wr_list) 242 | self.wr_list.reverse() 243 | self.assertEqual(expected, self.wr_list) 244 | 245 | def test_reversed_update_finalizer(self): 246 | fake_obj0 = self.objectFake() 247 | fake_obj1 = self.objectFake() 248 | self.wr_list.extend([fake_obj0, fake_obj1]) 249 | wr_list = reversed(self.wr_list) 250 | del fake_obj1 251 | if is_pypy: 252 | gc.collect() 253 | self.assertEqual(1, len(wr_list)) 254 | self.assertEqual(1, len(self.wr_list)) 255 | 256 | def test_it_supports_sort(self): 257 | fake_obj0 = self.objectFake() 258 | fake_obj1 = self.objectFake() 259 | self.wr_list.extend([fake_obj1, fake_obj0]) 260 | expected = WeakList(sorted(list(self.wr_list))) 261 | self.wr_list.sort() 262 | self.assertGreater(fake_obj1, fake_obj0) 263 | self.assertEqual(expected, self.wr_list) 264 | 265 | def test_it_supports_sort_with_reverse(self): 266 | fake_obj0 = self.objectFake() 267 | fake_obj1 = self.objectFake() 268 | self.wr_list.extend([fake_obj0, fake_obj1]) 269 | expected = WeakList(sorted(list(self.wr_list), reverse=True)) 270 | not_expected = WeakList(sorted(list(self.wr_list))) 271 | self.wr_list.sort(reverse=True) 272 | self.assertEqual(expected, self.wr_list) 273 | self.assertNotEqual(not_expected, self.wr_list) 274 | 275 | def test_it_supports_sort_with_key(self): 276 | def index_plus_2_if_odd(item): 277 | return item.index + 2 if item.index % 2 != 0 else item.index 278 | 279 | fake_obj0 = self.objectFake() 280 | fake_obj1 = self.objectFake() 281 | self.wr_list.extend([fake_obj1, fake_obj0]) 282 | expected = WeakList(sorted(list(self.wr_list), key=index_plus_2_if_odd)) 283 | self.wr_list.sort(key=index_plus_2_if_odd) 284 | self.assertEqual(expected, self.wr_list) 285 | 286 | def test_it_supports_pop(self): 287 | fake_obj0 = self.objectFake() 288 | fake_obj1 = self.objectFake() 289 | self.wr_list.extend([fake_obj0, fake_obj1]) 290 | self.assertIs(fake_obj1, self.wr_list[1]) 291 | given = self.wr_list.pop(0) 292 | self.assertIs(fake_obj0, given) 293 | self.assertIs(fake_obj1, self.wr_list[0]) 294 | 295 | def test_pop_without_parameter_returns_last_item(self): 296 | fake_obj0 = self.objectFake() 297 | fake_obj1 = self.objectFake() 298 | self.wr_list.extend([fake_obj0, fake_obj1]) 299 | given = self.wr_list.pop() 300 | self.assertIs(fake_obj1, given) 301 | self.assertIs(1, len(self.wr_list)) 302 | 303 | def test_pop_on_empty_list_raise_IndexError(self): 304 | with self.assertRaises(IndexError): 305 | given = self.wr_list.pop() 306 | 307 | def test_pop_on_unexisting_index_raises_IndexError(self): 308 | fake_obj0 = self.objectFake() 309 | fake_obj1 = self.objectFake() 310 | self.wr_list.extend([fake_obj0, fake_obj1]) 311 | with self.assertRaises(IndexError): 312 | given = self.wr_list.pop(2) 313 | 314 | if sys.version_info < (3, ): 315 | def test_it_supports_sort_with_cmp(self): 316 | def compare(item1, item2): 317 | return cmp(item2.index, item1.index) 318 | 319 | fake_obj0 = self.objectFake() 320 | fake_obj1 = self.objectFake() 321 | self.wr_list.extend([fake_obj0, fake_obj1]) 322 | expected = WeakList(sorted(list(self.wr_list), cmp=compare)) 323 | not_expected = WeakList(sorted(list(self.wr_list))) 324 | self.wr_list.sort(cmp=compare) 325 | self.assertEqual(expected, self.wr_list) 326 | self.assertNotEqual(not_expected, self.wr_list) 327 | 328 | def test_it_supports_sort_with_key_and_cmp(self): 329 | def index_plus_2_if_odd(item): 330 | return item.index + 2 if item.index % 2 != 0 else item.index 331 | 332 | def compare(item1, item2): 333 | return cmp(item2, item1) 334 | 335 | fake_obj0 = self.objectFake() 336 | fake_obj1 = self.objectFake() 337 | self.wr_list.extend([fake_obj0, fake_obj1]) 338 | expected = WeakList(sorted(list(self.wr_list), cmp=compare, key=index_plus_2_if_odd)) 339 | self.wr_list.sort(cmp=compare, key=index_plus_2_if_odd) 340 | self.assertEqual(expected, self.wr_list) 341 | 342 | 343 | if __name__ == "__main__": 344 | unittest.main() 345 | -------------------------------------------------------------------------------- /weakreflist/weakreflist.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | from weakref import ref, ReferenceType 3 | import sys 4 | 5 | __all__ = ["WeakList"] 6 | 7 | 8 | def is_slice(index): 9 | return isinstance(index, slice) 10 | 11 | 12 | class WeakList(list): 13 | 14 | def __init__(self, items=list()): 15 | list.__init__(self, self._refs(items)) 16 | 17 | def value(self, item): 18 | return item() if isinstance(item, ReferenceType) else item 19 | 20 | def ref(self, item): 21 | try: 22 | item = ref(item, self.remove_all) 23 | finally: 24 | return item 25 | 26 | def __contains__(self, item): 27 | return list.__contains__(self, self.ref(item)) 28 | 29 | def __getitem__(self, index): 30 | items = list.__getitem__(self, index) 31 | return type(self)(self._values(items)) if is_slice(index) else self.value(items) 32 | 33 | def __setitem__(self, index, item): 34 | items = self._refs(item) if is_slice(index) else self.ref(item) 35 | return list.__setitem__(self, index, items) 36 | 37 | def __iter__(self): 38 | return iter(self[index] for index in range(len(self))) 39 | 40 | def __reversed__(self): 41 | reversed_self = type(self)(self) 42 | reversed_self.reverse() 43 | return reversed_self 44 | 45 | def append(self, item): 46 | list.append(self, self.ref(item)) 47 | 48 | def remove(self, item): 49 | return list.remove(self, self.ref(item)) 50 | 51 | def remove_all(self, item): 52 | item = self.ref(item) 53 | while list.__contains__(self, item): 54 | list.remove(self, item) 55 | 56 | def index(self, item): 57 | return list.index(self, self.ref(item)) 58 | 59 | def count(self, item): 60 | return list.count(self, self.ref(item)) 61 | 62 | def pop(self, index=-1): 63 | return self.value(list.pop(self, self.ref(index))) 64 | 65 | def insert(self, index, item): 66 | return list.insert(self, index, self.ref(item)) 67 | 68 | def extend(self, items): 69 | return list.extend(self, self._refs(items)) 70 | 71 | def __iadd__(self, other): 72 | return list.__iadd__(self, self._refs(other)) 73 | 74 | def _refs(self, items): 75 | return map(self.ref, items) 76 | 77 | def _values(self, items): 78 | return map(self.value, items) 79 | 80 | def _sort_key(self, key=None): 81 | return self.value if key is None else lambda item: key(self.value(item)) 82 | 83 | if sys.version_info < (3,): 84 | def sort(self, cmp=None, key=None, reverse=False): 85 | return list.sort(self, cmp=cmp, key=self._sort_key(key), reverse=reverse) 86 | 87 | def __setslice__(self, lower_bound, upper_bound, items): 88 | return self.__setitem__(slice(lower_bound, upper_bound), items) 89 | 90 | def __getslice__(self, lower_bound, upper_bound): 91 | return self.__getitem__(slice(lower_bound, upper_bound)) 92 | else: 93 | def sort(self, key=None, reverse=False): 94 | return list.sort(self, key=self._sort_key(key), reverse=reverse) 95 | --------------------------------------------------------------------------------