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