initial commit

This commit is contained in:
Tommy Wang 2014-11-13 14:11:45 -06:00
commit 4cc4ed3bb4
8 changed files with 374 additions and 0 deletions

99
.gitignore vendored Normal file
View File

@ -0,0 +1,99 @@
# Created by https://www.gitignore.io
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.cache
nosetests.xml
coverage.xml
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
### vim ###
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~
### Emacs ###
# -*- mode: gitignore; -*-
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# Org-mode
.org-id-locations
*_archive
# flymake-mode
*_flymake.*
# eshell files
/eshell/history
/eshell/lastdir
# elpa packages
/elpa/
# reftex files
*.rel
# AUCTeX auto folder
/auto/

8
DESCRIPTION.rst Normal file
View File

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

1
MANIFEST.in Normal file
View File

@ -0,0 +1 @@
include DESCRIPTION.rst

6
README.rst Normal file
View File

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

2
setup.cfg Normal file
View File

@ -0,0 +1,2 @@
[sdist]
force-manifest = 1

37
setup.py Normal file
View File

@ -0,0 +1,37 @@
from setuptools import setup
from codecs import open
from os import path
from weakrefmethod import __version__
here = path.abspath(path.dirname(__file__))
with open(path.join(here, 'DESCRIPTION.rst'), encoding='utf-8') as f:
long_description = f.read()
URL = 'http://pypi.python.org/pypi/weakrefmethod'
setup(
name='weakrefmethod',
version=__version__,
description='A WeakMethod class for storing bound methods using weak references.',
long_description=long_description,
py_modules=['weakrefmethod'],
author='Tommy Wang',
author_email='twang@august8.net',
license='PSF',
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: Python Software Foundation License',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Operating System :: OS Independent',
'Topic :: Software Development :: Libraries',
'Topic :: Software Development :: Libraries :: Python Modules',
],
keywords='weakref WeakMethod',
url='http://pypi.python.org/pypi/weakrefmethod',
tests_require=['unittest2'],
test_suite='test_weakmethod',
)

163
test_weakmethod.py Normal file
View File

@ -0,0 +1,163 @@
import unittest2 as unittest
import gc
import weakref
import weakrefmethod
class Object:
def __init__(self, arg):
self.arg = arg
def __repr__(self):
return "<Object %r>" % self.arg
def __eq__(self, other):
if isinstance(other, Object):
return self.arg == other.arg
return NotImplemented
def __ne__(self, other):
result = self.__eq__(other)
if result is NotImplemented:
return NotImplemented
return not result
def __lt__(self, other):
if isinstance(other, Object):
return self.arg < other.arg
return NotImplemented
def __hash__(self):
return hash(self.arg)
def some_method(self):
return 4
def other_method(self):
return 5
class WeakMethodTestCase(unittest.TestCase):
def _subclass(self):
"""Return an Object subclass overriding `some_method`."""
class C(Object):
def some_method(self):
return 6
return C
def test_alive(self):
o = Object(1)
r = weakrefmethod.WeakMethod(o.some_method)
self.assertIsInstance(r, weakref.ReferenceType)
self.assertIsInstance(r(), type(o.some_method))
self.assertIs(r().__self__, o)
self.assertIs(r().__func__, o.some_method.__func__)
self.assertEqual(r()(), 4)
def test_object_dead(self):
o = Object(1)
r = weakrefmethod.WeakMethod(o.some_method)
self.assertIsInstance(r, weakref.ReferenceType)
self.assertIsInstance(r(), type(o.some_method))
self.assertIs(r().__self__, o)
self.assertIs(r().__func__, o.some_method.__func__)
self.assertEqual(r()(), 4)
def test_method_dead(self):
C = self._subclass()
o = C(1)
r = weakrefmethod.WeakMethod(o.some_method)
del C.some_method
gc.collect()
self.assertIs(r(), None)
def test_callback_when_object_dead(self):
# Test callback behavior when object dies first.
C = self._subclass()
calls = []
def cb(arg):
calls.append(arg)
o = C(1)
r = weakrefmethod.WeakMethod(o.some_method, cb)
del o
gc.collect()
self.assertEqual(calls, [r])
# Callback is only called once.
C.some_method = Object.some_method
gc.collect()
self.assertEqual(calls, [r])
def test_callback_when_method_dead(self):
# Test callback behavior when method dies first.
C = self._subclass()
calls = []
def cb(arg):
calls.append(arg)
o = C(1)
r = weakrefmethod.WeakMethod(o.some_method, cb)
del C.some_method
gc.collect()
self.assertEqual(calls, [r])
# Callback is only called once.
del o
gc.collect()
self.assertEqual(calls, [r])
def test_no_cycles(self):
# A WeakMethod doesn't create any reference cycle to itself.
o = Object(1)
def cb(_):
pass
r = weakrefmethod.WeakMethod(o.some_method, cb)
wr = weakref.ref(r)
del r
self.assertIs(wr(), None)
def test_equality(self):
def _eq(a, b):
self.assertTrue(a == b)
self.assertFalse(a != b)
def _ne(a, b):
self.assertTrue(a != b)
self.assertFalse(a == b)
x = Object(1)
y = Object(1)
a = weakrefmethod.WeakMethod(x.some_method)
b = weakrefmethod.WeakMethod(y.some_method)
c = weakrefmethod.WeakMethod(x.other_method)
d = weakrefmethod.WeakMethod(y.other_method)
# Objects equal, same method
_eq(a, b)
_eq(c, d)
# Objects equal, different method
_ne(a, c)
_ne(a, d)
_ne(b, c)
_ne(b, d)
# Objects unequal, same or different method
z = Object(2)
e = weakrefmethod.WeakMethod(z.some_method)
f = weakrefmethod.WeakMethod(z.other_method)
_ne(a, e)
_ne(a, f)
_ne(b, e)
_ne(b, f)
del x, y, z
gc.collect()
# Dead WeakMethod compare by identity
refs = a, b, c, d, e, f
for q in refs:
for r in refs:
self.assertEqual(q == r, q is r)
self.assertEqual(q != r, q is not r)
def test_hashing(self):
# Alive WeakMethods are hashable if the underlying object is
# hashable.
x = Object(1)
y = Object(1)
a = weakrefmethod.WeakMethod(x.some_method)
b = weakrefmethod.WeakMethod(y.some_method)
c = weakrefmethod.WeakMethod(y.other_method)
# Since WeakMethod objects are equal, the hashes should be equal.
self.assertEqual(hash(a), hash(b))
ha = hash(a)
# Dead WeakMethods retain their old hash value
del x, y
gc.collect()
self.assertEqual(hash(a), ha)
self.assertEqual(hash(b), ha)
# If it wasn't hashed when alive, a dead WeakMethod cannot be hashed.
self.assertRaises(TypeError, hash, c)

58
weakrefmethod.py Normal file
View File

@ -0,0 +1,58 @@
import weakref
__all__ = ['WeakMethod']
__version__ = '1.0.0'
class WeakMethod(weakref.ref):
"""
A custom 'weakref.ref' subclass which simulates a weak reference to
a bound method, working around the lifetime problem of bound methods
"""
__slots__ = '_func_ref', '_meth_type', '_alive', '__weakref__'
def __new__(cls, meth, callback=None):
try:
obj = meth.__self__
func = meth.__func__
except AttributeError:
raise TypeError('argument should be a bound method, not {}'.format(type(meth)))
def _cb(arg):
# The self-weakref trick is needed to avoid creating a reference cycle.
self = self_wr()
if self._alive:
self._alive = False
if callback is not None:
callback(self)
self = weakref.ref.__new__(cls, obj, _cb)
self._func_ref = weakref.ref(func, _cb)
self._meth_type = type(meth)
self._alive = True
self_wr = weakref.ref(self)
return self
def __call__(self):
obj = super(WeakMethod, self).__call__()
func = self._func_ref()
if obj is None or func is None:
return None
return self._meth_type(func, obj)
def __eq__(self, other):
if isinstance(other, WeakMethod):
if not self._alive or not other._alive:
return self is other
return weakref.ref.__eq__(self, other) and self._func_ref == other._func_ref
return False
def __ne__(self, other):
if isinstance(other, WeakMethod):
if not self._alive or not other._alive:
return self is not other
return weakref.ref.__ne__(self, other) or self._func_ref != other._func_ref
return True
__hash__ = weakref.ref.__hash__