3 ms·
currently using good old unittest but with the addition of hypothesis. hypothesis is a real productivity booster.
by dbcurtis 9y ago
currently using good old unittest but with the addition of hypothesis. hypothesis is a real productivity booster.
- eru 9y agoUsing hypothesis makes Python feel almost like a typed language for me.
- dbcurtis 9y agoIf you want to really make Python feel like a typed language, and still be idiomatically Pythonic, learn to write constructors that raise ValueError in appropriate ways.
- geofft 9y agoIs there a good way to do that that doesn't involve a bunch of if-this-or-that-then-raise ~boilerplate in your constructors? e.g., is there some nice library that will let me put annotations like "must be a list of at least two items" or "must be an integer between 0 and 99" with a more concise syntax?
- dbcurtis 9y agoUmmm, maybe? There seem to be libraries for everything, but I do it manually. The constructors become "non-trivial", but not overly-complicated, and it pays off in simpler, more robust code elsewhere. So my code would be something like: class Foo: def __init__(self, x, y=None): if y is None: x_ = x.x y = x.y self.x = int(x_) if self.x < 0 or self.x > 99: raise ValueError('x must be in range 0..99') if len(y) < 2: raise ValueError('y must be iterable, length > 2') self.y = y Soo... You can say: f = Foo(1,['a','b']) f = Foo(1,('a','b', 3)) f = Foo("42", "bar") f2 = Foo(f) Calling int(x_) will take anything that implements __int__(), or raise ValueError. len() will take anything that implements the iterator protocol and has length >= 2, or raise. Calling Foo() on an instance of Foo will cause some excess copies, but it does have the effect of making sure you passed in something Foo-ish. Where "Foo-ish" means "has an x attribute that can be made into an integer between 0 and 99, and a y attribute that is an iterable of length >= 2". The above code lets some exceptions bubble up, but they could be wrapped in try..except to turn them into ValueError with specific messages. Note that this code accepts anything Foo-ish without calling isinstance(). Abstract base cases with isinstance() are a more suitable choice for complex cases of checking protocol conformance. So whether or not the performance impact and extra code are worth it probably varies from project to project.
- kemayo 9y agoYou can use attrs validators for it, though it's not maximally concise. (i.e. you need functions declared, but it's easy to reuse them.) http://www.attrs.org/en/stable/examples.html#validators http://www.attrs.org/en/stable/examples.html#validators Also, the next attrs release looks like it's getting some support for PEP484 type hinting annotations, but I don't know the details beyond having glanced at some pull requests. So it might get simpler in the future. If so, that'd make it easy to trust the type-validation, and just have the range check as a custom validator.
- StavrosK 9y agoOr use type annotations and mypy/typeguard.
- eru 9y agoThat's a different problem, though admittedly I was not very precise. (And by 'feels like typed', I meant feels closer to something like Haskell or OCaml than having eg Java as my target.) Even the best constructor magic in Python will struggle helping you verify the types of your functions.