People who come from strongly typed languages that offer
interfaces often are confused by lack of one in Python. Python, being dynamic typing programming language, follows
duck typing principal. Here we will see how programmer can assert duck typing between two Python classes. Setup environment before proceed:
$ virtualenv env
$ env/bin/pip install wheezy.core
Let play a bit with
duck test `looks like`. Place the following code snippet into file `test.py`:
from wheezy.core.introspection import looks
class IFoo(object):
def foo(self):
pass
class Foo(object):
def bar(self):
pass
assert looks(Foo).like(IFoo)
and run it:
test.py:11: UserWarning: 'foo': is missing.
assert looks(Foo).like(IFoo)
Traceback (most recent call last):
File "test.py", line 11, in
assert looks(Foo).like(IFoo)
AssertionError
The error says the function `foo` is missing in `Foo` implementation. Let fix that and look at arguments checks.
from wheezy.core.introspection import looks
class IFoo(object):
def foo(self, a, b=None):
pass
class Foo(object):
def foo(self):
pass
assert looks(Foo).like(IFoo)
Here is output from run:
test.py:11: UserWarning: 'foo': argument names or defaults have no match.
assert looks(Foo).like(IFoo)
Traceback (most recent call last):
File "test.py", line 11, in
assert looks(Foo).like(IFoo)
AssertionError
So far so good. Let fix it and take a look at properties:
from wheezy.core.introspection import looks
class IFoo(object):
def foo(self, a, b=None):
pass
@property
def bar(self):
pass
class Foo(object):
def foo(self, a, b=None):
pass
def bar(self):
pass
assert looks(Foo).like(IFoo)
Here is output:
test.py:21: UserWarning: 'bar': is not property.
assert looks(Foo).like(IFoo)
Traceback (most recent call last):
File "test.py", line 21, in
assert looks(Foo).like(IFoo)
AssertionError
Look at other examples available
here. A simple `duck test` using `looks like` approach asserts conformance between two classes in Python.
People coming from other language might also find interesting zope.interface and abc in Python 3.
ReplyDeleteNice!
ReplyDeleteThis begs a question - why do we have isinstance but not this kind of support in the language?
Related discussion on comp.lang.python - https://groups.google.com/group/comp.lang.python/browse_thread/thread/6cfbf2b69f4a441f?hl=en#
ReplyDeleteCan't you do this with ABC already?
ReplyDeleteThis is not the same thing. It doesn't require me to subclass anything, thus object is light. I do no need run-time checks only a single time once the module is imported. It works with any version of python.
DeleteWait, so this is supposed to fail?! (From your tests)
ReplyDeleteclass IFoo(object):
..def __len__(self):
....pass
class Foo(IFoo):
..pass
assert not looks(Foo).like(IFoo, notice=['__len__'])
self.assert_warning("'__len__': is missing.")
Is Foo not supposed to be a subclass of IFoo? Because as your tests stand now, len(Foo) would work (or, rather, would throw the same TypeError that len(IFoo) does).
`assert not` will not fail, however you will see warning message, as expected.
DeleteAfter looking at the code, I see that attributes of parent classes aren't examined, which makes this effort incomplete. An empty subclass with will be reported as failing to matching its parent's interface, which is obviously not the case.
ReplyDeleteThe looks like checks are explicit. If you need to look at whole hierarchy tree just list them all in separate asserts. This adds readability to your code.
Delete