Skip to content

Commit 4cc4ed3

Browse files
committed
initial commit
0 parents  commit 4cc4ed3

8 files changed

+374
-0
lines changed

Diff for: .gitignore

+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Created by https://www.gitignore.io
2+
3+
### Python ###
4+
# Byte-compiled / optimized / DLL files
5+
__pycache__/
6+
*.py[cod]
7+
8+
# C extensions
9+
*.so
10+
11+
# Distribution / packaging
12+
.Python
13+
env/
14+
build/
15+
develop-eggs/
16+
dist/
17+
downloads/
18+
eggs/
19+
lib/
20+
lib64/
21+
parts/
22+
sdist/
23+
var/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
28+
# PyInstaller
29+
# Usually these files are written by a python script from a template
30+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
31+
*.manifest
32+
*.spec
33+
34+
# Installer logs
35+
pip-log.txt
36+
pip-delete-this-directory.txt
37+
38+
# Unit test / coverage reports
39+
htmlcov/
40+
.tox/
41+
.coverage
42+
.cache
43+
nosetests.xml
44+
coverage.xml
45+
46+
# Translations
47+
*.mo
48+
*.pot
49+
50+
# Django stuff:
51+
*.log
52+
53+
# Sphinx documentation
54+
docs/_build/
55+
56+
# PyBuilder
57+
target/
58+
59+
60+
### vim ###
61+
[._]*.s[a-w][a-z]
62+
[._]s[a-w][a-z]
63+
*.un~
64+
Session.vim
65+
.netrwhist
66+
*~
67+
68+
69+
### Emacs ###
70+
# -*- mode: gitignore; -*-
71+
*~
72+
\#*\#
73+
/.emacs.desktop
74+
/.emacs.desktop.lock
75+
*.elc
76+
auto-save-list
77+
tramp
78+
.\#*
79+
80+
# Org-mode
81+
.org-id-locations
82+
*_archive
83+
84+
# flymake-mode
85+
*_flymake.*
86+
87+
# eshell files
88+
/eshell/history
89+
/eshell/lastdir
90+
91+
# elpa packages
92+
/elpa/
93+
94+
# reftex files
95+
*.rel
96+
97+
# AUCTeX auto folder
98+
/auto/
99+

Diff for: DESCRIPTION.rst

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Python 3.4 include a ``WeakMethod`` class, for storing bound methods using weak references
2+
(see the `Python weakref module <http://docs.python.org/library/weakref.html>`_).
3+
4+
This project is a backport of the WeakMethod class, and tests, for Python 2.6. The tests
5+
require the `unittest2 package <http://pypi.python.org/pypi/unittest2>`_.
6+
7+
* Github repository & issue tracker:
8+

Diff for: MANIFEST.in

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include DESCRIPTION.rst

Diff for: README.rst

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
weakrefmethod
2+
=============
3+
4+
Backport of WeakMethod from Python 3.4 to Python 2.6+
5+
6+
`docs <https://docs.python.org/3/library/weakref.html#weakref.WeakMethod>`_

Diff for: setup.cfg

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[sdist]
2+
force-manifest = 1

Diff for: setup.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from setuptools import setup
2+
from codecs import open
3+
from os import path
4+
from weakrefmethod import __version__
5+
6+
here = path.abspath(path.dirname(__file__))
7+
with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f:
8+
long_description = f.read()
9+
10+
URL = 'http://pypi.python.org/pypi/weakrefmethod'
11+
12+
setup(
13+
name='weakrefmethod',
14+
version=__version__,
15+
description='A WeakMethod class for storing bound methods using weak references.',
16+
long_description=long_description,
17+
py_modules=['weakrefmethod'],
18+
author='Tommy Wang',
19+
author_email='[email protected]',
20+
license='PSF',
21+
classifiers=[
22+
'Development Status :: 3 - Alpha',
23+
'Environment :: Console',
24+
'Intended Audience :: Developers',
25+
'License :: OSI Approved :: Python Software Foundation License',
26+
'Programming Language :: Python',
27+
'Programming Language :: Python :: 2.6',
28+
'Programming Language :: Python :: 2.7',
29+
'Operating System :: OS Independent',
30+
'Topic :: Software Development :: Libraries',
31+
'Topic :: Software Development :: Libraries :: Python Modules',
32+
],
33+
keywords='weakref WeakMethod',
34+
url='http://pypi.python.org/pypi/weakrefmethod',
35+
tests_require=['unittest2'],
36+
test_suite='test_weakmethod',
37+
)

Diff for: test_weakmethod.py

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
import unittest2 as unittest
2+
import gc
3+
import weakref
4+
import weakrefmethod
5+
6+
class Object:
7+
def __init__(self, arg):
8+
self.arg = arg
9+
def __repr__(self):
10+
return "<Object %r>" % self.arg
11+
def __eq__(self, other):
12+
if isinstance(other, Object):
13+
return self.arg == other.arg
14+
return NotImplemented
15+
def __ne__(self, other):
16+
result = self.__eq__(other)
17+
if result is NotImplemented:
18+
return NotImplemented
19+
return not result
20+
def __lt__(self, other):
21+
if isinstance(other, Object):
22+
return self.arg < other.arg
23+
return NotImplemented
24+
def __hash__(self):
25+
return hash(self.arg)
26+
def some_method(self):
27+
return 4
28+
def other_method(self):
29+
return 5
30+
31+
32+
class WeakMethodTestCase(unittest.TestCase):
33+
def _subclass(self):
34+
"""Return an Object subclass overriding `some_method`."""
35+
class C(Object):
36+
def some_method(self):
37+
return 6
38+
return C
39+
40+
def test_alive(self):
41+
o = Object(1)
42+
r = weakrefmethod.WeakMethod(o.some_method)
43+
self.assertIsInstance(r, weakref.ReferenceType)
44+
self.assertIsInstance(r(), type(o.some_method))
45+
self.assertIs(r().__self__, o)
46+
self.assertIs(r().__func__, o.some_method.__func__)
47+
self.assertEqual(r()(), 4)
48+
49+
def test_object_dead(self):
50+
o = Object(1)
51+
r = weakrefmethod.WeakMethod(o.some_method)
52+
self.assertIsInstance(r, weakref.ReferenceType)
53+
self.assertIsInstance(r(), type(o.some_method))
54+
self.assertIs(r().__self__, o)
55+
self.assertIs(r().__func__, o.some_method.__func__)
56+
self.assertEqual(r()(), 4)
57+
58+
def test_method_dead(self):
59+
C = self._subclass()
60+
o = C(1)
61+
r = weakrefmethod.WeakMethod(o.some_method)
62+
del C.some_method
63+
gc.collect()
64+
self.assertIs(r(), None)
65+
66+
def test_callback_when_object_dead(self):
67+
# Test callback behavior when object dies first.
68+
C = self._subclass()
69+
calls = []
70+
def cb(arg):
71+
calls.append(arg)
72+
o = C(1)
73+
r = weakrefmethod.WeakMethod(o.some_method, cb)
74+
del o
75+
gc.collect()
76+
self.assertEqual(calls, [r])
77+
# Callback is only called once.
78+
C.some_method = Object.some_method
79+
gc.collect()
80+
self.assertEqual(calls, [r])
81+
82+
def test_callback_when_method_dead(self):
83+
# Test callback behavior when method dies first.
84+
C = self._subclass()
85+
calls = []
86+
def cb(arg):
87+
calls.append(arg)
88+
o = C(1)
89+
r = weakrefmethod.WeakMethod(o.some_method, cb)
90+
del C.some_method
91+
gc.collect()
92+
self.assertEqual(calls, [r])
93+
# Callback is only called once.
94+
del o
95+
gc.collect()
96+
self.assertEqual(calls, [r])
97+
98+
def test_no_cycles(self):
99+
# A WeakMethod doesn't create any reference cycle to itself.
100+
o = Object(1)
101+
def cb(_):
102+
pass
103+
r = weakrefmethod.WeakMethod(o.some_method, cb)
104+
wr = weakref.ref(r)
105+
del r
106+
self.assertIs(wr(), None)
107+
108+
def test_equality(self):
109+
def _eq(a, b):
110+
self.assertTrue(a == b)
111+
self.assertFalse(a != b)
112+
def _ne(a, b):
113+
self.assertTrue(a != b)
114+
self.assertFalse(a == b)
115+
x = Object(1)
116+
y = Object(1)
117+
a = weakrefmethod.WeakMethod(x.some_method)
118+
b = weakrefmethod.WeakMethod(y.some_method)
119+
c = weakrefmethod.WeakMethod(x.other_method)
120+
d = weakrefmethod.WeakMethod(y.other_method)
121+
# Objects equal, same method
122+
_eq(a, b)
123+
_eq(c, d)
124+
# Objects equal, different method
125+
_ne(a, c)
126+
_ne(a, d)
127+
_ne(b, c)
128+
_ne(b, d)
129+
# Objects unequal, same or different method
130+
z = Object(2)
131+
e = weakrefmethod.WeakMethod(z.some_method)
132+
f = weakrefmethod.WeakMethod(z.other_method)
133+
_ne(a, e)
134+
_ne(a, f)
135+
_ne(b, e)
136+
_ne(b, f)
137+
del x, y, z
138+
gc.collect()
139+
# Dead WeakMethod compare by identity
140+
refs = a, b, c, d, e, f
141+
for q in refs:
142+
for r in refs:
143+
self.assertEqual(q == r, q is r)
144+
self.assertEqual(q != r, q is not r)
145+
146+
def test_hashing(self):
147+
# Alive WeakMethods are hashable if the underlying object is
148+
# hashable.
149+
x = Object(1)
150+
y = Object(1)
151+
a = weakrefmethod.WeakMethod(x.some_method)
152+
b = weakrefmethod.WeakMethod(y.some_method)
153+
c = weakrefmethod.WeakMethod(y.other_method)
154+
# Since WeakMethod objects are equal, the hashes should be equal.
155+
self.assertEqual(hash(a), hash(b))
156+
ha = hash(a)
157+
# Dead WeakMethods retain their old hash value
158+
del x, y
159+
gc.collect()
160+
self.assertEqual(hash(a), ha)
161+
self.assertEqual(hash(b), ha)
162+
# If it wasn't hashed when alive, a dead WeakMethod cannot be hashed.
163+
self.assertRaises(TypeError, hash, c)

Diff for: weakrefmethod.py

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import weakref
2+
3+
__all__ = ['WeakMethod']
4+
5+
__version__ = '1.0.0'
6+
7+
8+
class WeakMethod(weakref.ref):
9+
"""
10+
A custom 'weakref.ref' subclass which simulates a weak reference to
11+
a bound method, working around the lifetime problem of bound methods
12+
"""
13+
14+
__slots__ = '_func_ref', '_meth_type', '_alive', '__weakref__'
15+
16+
def __new__(cls, meth, callback=None):
17+
try:
18+
obj = meth.__self__
19+
func = meth.__func__
20+
except AttributeError:
21+
raise TypeError('argument should be a bound method, not {}'.format(type(meth)))
22+
23+
def _cb(arg):
24+
# The self-weakref trick is needed to avoid creating a reference cycle.
25+
self = self_wr()
26+
if self._alive:
27+
self._alive = False
28+
if callback is not None:
29+
callback(self)
30+
self = weakref.ref.__new__(cls, obj, _cb)
31+
self._func_ref = weakref.ref(func, _cb)
32+
self._meth_type = type(meth)
33+
self._alive = True
34+
self_wr = weakref.ref(self)
35+
return self
36+
37+
def __call__(self):
38+
obj = super(WeakMethod, self).__call__()
39+
func = self._func_ref()
40+
if obj is None or func is None:
41+
return None
42+
return self._meth_type(func, obj)
43+
44+
def __eq__(self, other):
45+
if isinstance(other, WeakMethod):
46+
if not self._alive or not other._alive:
47+
return self is other
48+
return weakref.ref.__eq__(self, other) and self._func_ref == other._func_ref
49+
return False
50+
51+
def __ne__(self, other):
52+
if isinstance(other, WeakMethod):
53+
if not self._alive or not other._alive:
54+
return self is not other
55+
return weakref.ref.__ne__(self, other) or self._func_ref != other._func_ref
56+
return True
57+
58+
__hash__ = weakref.ref.__hash__

0 commit comments

Comments
 (0)