initial commit
This commit is contained in:
commit
4cc4ed3bb4
99
.gitignore
vendored
Normal file
99
.gitignore
vendored
Normal 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
8
DESCRIPTION.rst
Normal 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
1
MANIFEST.in
Normal file
@ -0,0 +1 @@
|
||||
include DESCRIPTION.rst
|
6
README.rst
Normal file
6
README.rst
Normal 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>`_
|
37
setup.py
Normal file
37
setup.py
Normal 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
163
test_weakmethod.py
Normal 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
58
weakrefmethod.py
Normal 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__
|
Loading…
Reference in New Issue
Block a user