Simpler syntax for checking type name
Closed this issue · 5 comments
Suggested by @doctoryes:
One point we've agreed upon here: It'd be nice to not have to specify new_contract('Foo', Foo) for our own classes. Instead, it'd be nice if PyContracts could just search the namespace for the classname when it's specified in a contract.
There is a similar undocumented feature that uses the expression isinstance(classname)
:
@contract(x="isinstance(BlockKey)")
def f(x):
pass
This checks that the name of the type of x or any of its base classes is BlockKey
.
(String matching is good because this way PyContracts doesn't need to import modules or search namespaces.)
The current syntax isinstance(name)
is too verbose, and this this is a common enough use case that it deserves a more intuitive syntax. The main reason that this feature is undocumented because I couldn't decide on a good compact syntax.
Some possibilities:
@contract(x="^BlockKey")
@contract(x="$BlockKey")
@contract(x="@BlockKey")
or, in general, <glyph>ClassName
.
Some non-possibilities:
This one is intuitive enough but it clashes with "<" used as numerical comparison:
@contract(x="<BlockKey")
We cannot use simply:
@contract(x="BlockKey")
because it makes many expressions ambiguous; for example lisst
becomes a valid contract (a class named lisst
) instead of a mispelling of list
.
+1 -- I've started using PyContracts after your excellent Boston Python talk, and this is the main pain point for me so far
I'm currently inclined to use ":" which looks clean enough though I'm not entirely convinced:
@contract(x=":BlockKey")
If we want to get cute:
@contract(x="a BlockKey")
@contract(l="list(a BlockKey)")
The ':' prefix is my favorite of the above. It's similar to the C++ ::ClassName
syntax indicating global scope.
The : syntax might get confusing when using docstrings for defining types. Such as:
:type x: :BlockKey
I am wondering if it would be so bad if "lisst" would become a valid contract. During runtime, when checking the contract, the method could then raise a NameError.
Unfortunately that would defer the error from the time of parsing to the execution time, but it might make the syntax more intuitive, i.e. @contract(x=BlockKey) would be the same as @contract(x="BlockKey").
When first using pycontracts I kind of assumed that's how it would work, which unfortunately was not the case.
I'm closing this. The "$" was implemented to refer to external variables, including types. E.g.
from module import MyClass
@contract(x='list($MyClass)')
def f(x):
pass