I'm trying to use PyContracts within a web application, so I have lots of custom-defined classes being passed around that I simply want to type check alongside other more traditional argument types. I'd like to use contractual programming (PyContracts) to accomplish this, for the sake of cleanliness and forced documentation.
When I reference a locally visible class by name, PyContracts doesn't seem to be aware of the type. For example:
from contracts import contract
class SomeClass:
pass
#contract
def f(a):
"""
:param a: Just a parameter
:type a: SomeClass
"""
print(a)
my_a = SomeClass()
f(my_a)
Raises the following error:
ContractSyntaxError: Unknown identifier 'SomeClass'. Did you mean 'np_complex64'? (at char 0), (line:1, col:1)
I know I can use new_contract to custom-define names and bind them to classes, but that's a lot of hassle to do for every type. I want to use the docstring syntax for PyContracts if at all possible, and I definitely need to use the string-defined contract format since I'm using boolean type logic ("None|str|SomeClass"). How do I accomplish this with local types and minimal intrusion into the rest of my codebase?
I hacked together a magic decorator that adds types before creating the contract. For anyone that's interested, it seems to work, but it's probably pretty slow if you define a large number of functions:
def magic_contract(*args, **kwargs):
# Check if we got called without arguments, just the function
func = None
if len(args) == 1 and len(kwargs) == 0 and callable(args[0]):
func = args[0]
args = tuple()
def inner_decorator(f):
for name, val in f.__globals__.items():
if isinstance(val, type):
new_contract(name, val)
return contract(*args, **kwargs)(f)
if func:
return inner_decorator(func)
return inner_decorator
And some test runs:
In [3]: class SomeClass:
...: pass
...:
In [4]: #magic_contract
...: def f(a):
...: """
...:
...: :param a: Some parameter
...: :type a: None|SomeClass
...: """
...: print(a)
...:
In [5]: f(None)
None
In [6]: f(SomeClass())
<__main__.SomeClass object at 0x7f1fa17c8b70>
In [7]: f(2)
...
ContractNotRespected: Breach for argument 'a' to f().
...
In [8]: #magic_contract(b='int|SomeClass')
...: def g(b):
...: print(type(b))
...:
In [9]: g(2)
<class 'int'>
In [10]: g(SomeClass())
<class '__main__.SomeClass'>
In [11]: g(None)
...
ContractNotRespected: Breach for argument 'b' to g().
...
Related
I want to hint the type of a variable as the return type of a specific function, (without having to manually specify what the return type of the function is).
I am unable to extract the return value type from the function in a way that it can be used as a hint for another variable.
def dostuff() -> T:
StuffContainer = namedtuple("StuffContainer", ["x", "y"])
return StuffContainer(1, 2)
def getX(a : T):
return a.x
The C++ equivalent of what I want done:
auto dostuff() {
struct data {
int x;
int y;
};
return data {1, 2};
}
int getX(decltype(dostuff()) a) {
return a.x;
}
There is no equivalent to decltype in the PEP 484 typing ecosystem. A little more broadly, there isn't really a great way of expressing "anonymous" types.
So, the canonical way you'd type your code is to do something more like this:
StuffContainer = namedtuple("StuffContainer", ["x", "y"])
def dostuff() -> StuffContainer:
return StuffContainer(1, 2)
def getX(a: StuffContainer):
return a.x
If the concern is that the type you're returning is too long and inconvenient to write out, you can perhaps shorten it a bit using type aliases:
StuffContainer = namedtuple("StuffContainer", ["x", "y"])
# S is now an alias of StuffContainer. The two types can be
# used interchangeably.
S = StuffContainer
def dostuff() -> S:
return StuffContainer(1, 2)
def getX(a: S):
return a.x
If the concern is that you don't want to encode that dostuff returns specifically a namedtuple, and you want to only commit to returning some object with an 'x' and 'y' attribute, you can perhaps use Protocols -- you can find more info about them in the PEP and in the mypy docs. (Though there isn't any info about them yet in the official Python typing module docs, unfortunately.)
For example:
from typing import NamedTuple
# If you're using Python 3.8+, you can do:
from typing import Protocol
# If you want to support older versions of Python,
# run 'pip install typing_extensions' and do the below instead
from typing_extensions import Protocol
# Any type that has 'x' and 'y' attributes is compatible with this.
class HasXAndY(Protocol):
# Making these properties to declare that they're read-only,
# for maximum flexibility.
#property
def x(self) -> int: ...
#property
def y(self) -> int: ...
def dostuff() -> HasXAndY:
# Note: I'm switching to a version of namedtuple that lets me associate
# types with each field, mostly just for demonstration purposes. At runtime,
# it behaves identically to collections.namedtuple.
StuffContainer = NamedTuple("StuffContainer", [("x", int), ("y", int)])
return StuffContainer(1, 2)
def get_x(obj: HasXAndY) -> int:
return obj.x
# Type-checks
get_x(dostuff())
class Unrelated:
def __init__(self, x: int, y: int, z: int) -> None:
self.x = x
self.y = y
self.z = z
# Also type-checks
get_x(Unrelated(1, 2, 3))
Im not sure I really like this, but you can do this.
The type hints for methods are stored in annotations, so you can edit them dynamically.
In [25]: class T:
...: x: int
...: y: str
...:
In [26]: def dostuff() -> T:
...: pass
...:
In [27]: def getX(a: T):
...: pass
...:
In [28]: getX
Out[28]: <function __main__.getX(a: __main__.T)>
In [29]: getX.__annotations__['return']=T.__annotations__['x']
In [30]: getX
Out[30]: <function __main__.getX(a: __main__.T) -> int>
I dont really think this is what you meant, but maybe..
I am confused as to when the __index__ method of a class is called. I had assumed the method would be called when ever the object was used in an indexing operation. I am not seeing __index__ called when the object is a subclass of int.
In [1]: class foo(int):
...: def __new__(cls, value):
...: return int.__new__(cls, value)
...: def __index__(self):
...: return int(self)+1
In [2]: i=foo(0)
In [3]: i
Out[3]: 0
In [4]: i.__index__()
Out[4]: 1
In [5]: [0,1,2][:i.__index__()]
Out[5]: [0]
In [6]: [0,1,2][:i]
Out[6]: []
it appears that int(i) is being used as the index not i.__index__(). What have I misunderstood?
Edited to simplify and correct the example.
__index__ implements lossless conversion to int. Python only calls __index__ if it actually needs such a conversion. In your case, it doesn't need a conversion, because your i is already an int. Python just uses the int value directly.
If you want to see the code that handles this, it's under PyNumber_Index.
Consider this code:
class MyClass:
def __init__(self, x):
self.x = x
def myfunct(arg):
arg.x += 1
return arg
a = MyClass(0)
print(a.x)
b = myfunct(a)
print(a.x)
print(b.x)
This returns:
0
1
1
I would expect this code to behave in the same way as this one:
def myfunct(arg):
arg += 1
return arg
c = 0
print(c)
d = myfunct(c)
print(c)
print(d)
However the latter returns:
0
0
1
I understand this is due to Python's way of passing arguments by assignment, as explained in this post, or this post.
However, I can't figure out a way to work around the behavior exhibited in the first code, which is unwanted in the project I am working on. How can I pass an object as an argument to a function, return a madified object, and keep the original one untouched?
The simple solution is, just create or pass a copy. To do that you have to possibiltys. Either you create a .copy() method on the class or use the copy module.
A copy method could look like this:
class MyClass:
def __init__(self, x):
self.x = x
def copy(self):
return self.__class__(self.x)
The copy module works like this:
import copy
b = copy.copy(a)
You can use either way to create a function that simply returns a new copy of the argument:
def myfunct(arg):
arg = arg.copy() # Or copy.copy(arg)
arg.x += 1
return arg
Edit: As many other answers say, my approach shown above doesn't work if you have mutable objects in mutable objects (as example an object of your class, that has another object of your class in its args attribute). In that case use the copy.deepcopy function instead:
def myfunct(arg):
arg = copy.deepcopy(arg)
arg.x += 1
return arg
You're explicitly modifying your object. Python supports this behavior by default, but if you want to prevent modification of your object you may want to update the __setattr__ to manage attribute modification.
If you want to prevent the original object from modifying and you want to modify the object sent to the function you can add a __copy__ method to your object to be copyable in a way you like, then pass a copy of your object to the function using copy.copy().
class MyClass:
def __init__(self, x):
self.x = x
# default copy
def __copy__(self):
cls = self.__class__
result = cls.__new__(cls)
result.__dict__.update(self.__dict__)
return result
Demo:
In [21]: a = MyClass(0)
...: print(a.x)
...:
0
# import copy
In [22]: b = myfunct(copy.copy(a))
In [24]: a.x
Out[24]: 0
In [25]: b.x
Out[25]: 1
from copy import deepcopy
def myfunct(arg):
new_arg = deepcopy(arg)
new_arg.x += 1
return new_arg
I would recommend deepcopy over copy since you want to make sure that all references to the original object are cut.
I think the concept is called overloading.
Right now I'm writing a class that will provide getter and setter methods, and am stuck on a design issue.
Both methods are one-liners that simply set a value, or return a value.
def set_number(self, num):
self.count = num
def get_number(self):
return self.count
Would it be better to save space and make the class "look" smaller by doing something that will basically combine the two methods into one and then just decide which line should be executed depending on whether the num argument is provided?
Or should I just stick to clarity and keep them separated? Some people feel that it's a "waste of space" to keep all these one-liners on their own, while others disagree and prefer splitting them up.
Any reasons why I would choose one or the other?
In Python, you should prefer not to use getters and setters at all. Instead simply reference the count attribute directly, e.g. print instance.count or instance.count = 5
The point of getters and setters in other languages is for encapsulation and for future-proofing in case you need to add logic to the getters or setters. In Python you can accomplish this by later using property, which will not break your existing API.
#property
def number(self):
# do extra logic if necessary
return self.count
Properties can also have setters - see: http://docs.python.org/library/functions.html#property
Python is not Java. =)
Extra bonus reading material:
http://tomayko.com/writings/getters-setters-fuxors
http://eli.thegreenplace.net/2009/02/06/getters-and-setters-in-python/
Generally in python it's very bad style to use explicit getter/setter methods. Just do something like this:
In [1]: class Foo(object):
...: def __init__(self):
...: self.num = 1
...:
...:
In [2]: f = Foo()
In [3]: f.num
Out[3]: 1
In [4]: f.num = 2
In [5]: f.num
Out[5]: 2
If you really need logic, you can preserve this same API at a later date by using properties. Notice how the same interface is preserved below while still adding functionality.
In [8]: class Foo(object):
...: def __init__(self):
...: self._num = 1
...: #property
...: def num(self):
...: return self._num
...: #num.setter
...: def num(self, num):
...: if num < 0:
...: raise ValueError("Your Foo would be too small!")
...: self._num = num
...:
...:
In [10]: f = Foo()
In [11]: f.num
Out[11]: 1
In [12]: f.num = 2
In [13]: f.num
Out[13]: 2
In [14]: f.num = -1
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/home/Daenyth/<ipython console> in <module>()
/home/Daenyth/<ipython console> in num(self, num)
ValueError: Your Foo would be too small!
Regarding "overloading" -- That term does not apply here. That is the term for changing the behavior of operators like +, ==, and so on, by changing the definition for those methods on your object. You can do some overloading in python via __cmp__, __add__, and so on.
I would leave it as two separate methods for the sake of code readabilty and maintainabilty.
I don't think so, but I thought I'd ask just in case. For example, for use in a class that encapsulates an int:
i = IntContainer(3)
i + 5
And I'm not just interested in this int example, I was looking for something clean and general, not overriding every int and string method.
Thanks, sunqiang. That's just what I wanted. I didn't realize you could subclass these immutable types (coming from C++).
class IntContainer(int):
def __init__(self,i):
#do stuff here
self.f = 4
def MultiplyBy4(self):
#some member function
self *= self.f
return self
print 3+IntContainer(3).MultiplyBy4()
This should do what you need:
class IntContainer(object):
def __init__(self, x):
self.x = x
def __add__(self, other):
# do some type checking on other
return self.x + other
def __radd__(self, other):
# do some type checking on other
return self.x + other
Output:
In [6]: IntContainer(3) + 6
Out[6]: 9
In [7]: 6 + IntContainer(3)
Out[7]: 9
For more information search for "radd" in the following docs:
http://docs.python.org/reference/datamodel.html#special-method-names
You'll find other such methods for "right addition", "right subtraction", etc.
Here's another link covering the same operators:
http://www.siafoo.net/article/57#reversed-binary-operations
By the way, Python does have casting operators:
http://www.siafoo.net/article/57#casts
But, they won't accomplish what you need in your example (basically because methods don't have any type annotation for parameters, so there's no good way to cast implicitly). So you can do this:
class IntContainer2(object):
def __init__(self, x):
self.x = x
def __int__(self):
return self.x
ic = IntContainer2(3)
print int(ic) + 6
print 6 + int(ic)
But this will fail:
print ic + 6 # error: no implicit coercion
You won't get conversion operators like in C++ because Python does not have this kind of strong static type system. The only automatic conversion operators are those which handle default numeric values (int/float); they are predefined in the language and cannot be changed.
Type "conversion" is usually done by constructors/factories. You can then overload standard methods like __add__ to make it work more like other classes.
sometimes maybe just subclass from int directly is enough. then __add__ and __radd__ need not costuming.
class IntContainer(int):
pass
i = IntContainer(3)
print i + 5 # 8
print 4 + i # 7
class StrContainer(str):
pass
s = StrContainer(3)
print s + '5' # 35
print '4' + s # 43
Is this what you need?
In [1]: class IntContainer(object):
...: def __init__(self, val):
...: self.val = val
...: def __add__(self, val):
...: return self.val + val
...: def __radd__(self, val):
...: return self.val + val
...:
...:
In [2]: i = IntContainer(3)
In [3]: i + 5
Out[3]: 8
In [4]:
Sorry for coming to the party 8.5 years late.
You can derive from an immutable (ie. int). You cannot define __init__ because the immutable is already created and can't be modified (by definition). This is where __new__ comes in handy.
class IntContainer(int):
def __new__ (cls, val):
ival = int.__new__(cls, val)
ival._rval = 'IntContainer(%d)' % ival
return ival
def __repr__ (self):
return self._rval
In [1]: i = IntContainer(3)
In [2]: i
Out[2]: IntContainer(3)
In [3]: repr(i)
Out[3]: 'IntContainer(3)'
In [4]: str(i)
Out[4]: '3'
In [5]: i + 5
Out[5]: 8
In [6]: 4 + i
Out[6]: 7
In [7]: int(i)
Out[7]: 3
In [8]: float(i)
Out[8]: 3.0
Now, to answer your question about conversion operators. You can also define __int__, __long__, __float__, and obviously, __str__. To convert or cast to an arbitrary object, you will most likely need to modify the other object to get what you want. You can use the __new__ method of that other object. Or if the other object is already created, try using __call__.