├── .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 |
--------------------------------------------------------------------------------