AndreaCensi / contracts

PyContracts is a Python package that allows to declare constraints on function parameters and return values. Contracts can be specified using Python3 annotations, or inside a docstring. PyContracts supports a basic type system, variables binding, arithmetic constraints, and has several specialized contracts and an extension API.
http://andreacensi.github.io/contracts/
Other
399 stars 61 forks source link

Simpler syntax for checking type name #23

Closed AndreaCensi closed 9 years ago

AndreaCensi commented 9 years ago

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.

ChrisBeaumont commented 9 years ago

+1 -- I've started using PyContracts after your excellent Boston Python talk, and this is the main pain point for me so far

AndreaCensi commented 9 years ago

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)")
doctoryes commented 9 years ago

The ':' prefix is my favorite of the above. It's similar to the C++ ::ClassName syntax indicating global scope.

denniskempin commented 9 years ago

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.

AndreaCensi commented 9 years ago

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