Casting in defining attributes of a Class? - python

I'm a Python newbie and I'm dealing with Object Programming.
Studying my teacher code, I've found something strange at self.items = list(items).
Look at the overall code:
class Truck():
'''
This class models a truck
'''
def __init__(self, plate_number, capacity, items):
'''
Constructor of an object of class Truck
:param plate_number: a unique number Identifying the truck
:param capacity: maximum weight that the truck can carry
:param items: list of objects of class Item carried by the truck
'''
self.plate_number = plate_number
self.capacity = capacity
self.items = list(items)
So, why should I cast the parameter items, whereas the other parameters don't need this operation?

The other parameters (plate_number and capacity) are probably immutable (a string and an int presumably). However items is a list (if we are to trust the docstring) which is mutable.
Using it as it is in Truck's __init__ would have resulted in changes made to truck_obj.items affecting the original passed items list as well (and vice-versa).
Calling list(items) results in a newly created list being assigned to self.items. This can also be achieved by using slicing syntax: self.items = items[:].
Compare:
class A:
def __init__(self, items):
self.items = items
li = [1, 2]
obj = A(li)
li[0] = 9
print(obj.items)
# [9, 2] oops! we modified obj.items without intending to
to
class A:
def __init__(self, items):
self.items = list(items) # or items[:]
li = [1, 2]
obj = A(li)
li[0] = 9
print(obj.items)
# [1, 2]

Your instructor is saying "I can only work with items in the class, if they're in a list". What this roughly translates to is, "The items attribute of class Truck must be an ordered, indexed, and mutable collection".
For example, there is a chance that someone passes in a tuple for items, and if for example, you needed to add or remove items in the Truck class, you can't add or remove items from a tuple (but you can with a list).
Casting to a list is explicit, and should it not raise an error, is guaranteed to be a list regardless of what gets passed into items. This is a part of what we call "pythonic".
EDIT: To piggy back off of a very good point by DeepSpace, casting to list also creates a shallow copy of the list to manipulate, as opposed to the list itself.

In this example your teacher is assuming that plate_number and capacity are two immutable variables and items should be represented as a list.
So, when passing variables in __init__() method your items variables must be an iterable and can be represented as a list.
You can see this example with a generator:
class Truck():
def __init__(self, plate_number, capacity, items):
self.plate_number = plate_number
self.capacity = capacity
self.items = list(items)
def __repr__(self):
return "plate_number: %r\ncapacity: %r\nitems: %r" % (self.plate_number, self.capacity, self.items)
# Test
if __name__ == '__main__':
number = 12
capacity = 13
# Generator wich can be represented as a list
items = (k for k in [1,2,3,4])
app = Truck(number, capacity, items)
print(app)
# OR
#print(repr(app))
Output:
plate_number: 12
capacity: 13
items: [1, 2, 3, 4]

Related

How to overload constructors in python?

Suppose I want to send a list in python and I will have a MyList class that will have 3 constructors. sending an empty list would return nothing, sending a list would convert it into a linked list and sending a linked list will copy its item and make another linkedlist from it
Task 1:
i) Create a Node class that will hold two fields i.e an integer element and a reference to the next Node.
ii) Create a Linked list Abstract Data Type (ADT)named MyList.The elements in the list are Nodes consisting of an integer type key (all keys are unique) and a reference to the next node.
Task 2:
Constructors:(3)
MyList ( )
Pre-condition: None.
Post-condition: This is the default constructor of MyList class. This constructor creates an empty list.
b. MyList (int [] a) or Myst(a)
Pre-condition: Array cannot be empty.
Post-condition: This is the default constructor of MyList class. This constructor creates a list from an array.
c. MyList (MyList a) or MyList(a)
Pre-condition: List cannot be empty.
Post-condition: This is the default constructor of MyList class. This constructor creates a list from another list.
For Task 2 : You can try something like this, by checking the input in the constructor of your class:
class MyList():
def __init__(self, a=None):
self.list = []
if a is None:
return
elif isinstance(a, list):
if len(a) == 0:
raise ValueError('List cannot be empty.')
else:
self.list = a
return
elif isinstance(a, MyList):
if len(a.list) == 0:
raise ValueError('List cannot be empty.')
else:
self.list = a.list.copy()
else:
raise TypeError('Unkonw type for a')

Recursively iterating through nodes referenced by other nodes

How could I recursively iterate through nodes with reference to a previous node? Expecting output 4,3,2,1 in the example below:
class Node:
def __init__(self, parent, value):
self.parent = parent
self.value = value
def append(self, value):
return Node(self, value)
def list(l):
print(l.value)
while l.parent is not None:
list(l.parent)
l = Node(None, 1)
l = l.append(2)
l = l.append(3)
l = l.append(4)
list(l)
Your class structure already succesfully passes the node's self value to its child node. The problem is your list function. while l.parent is not None: never ends, because nothing in the loop is changing the value of l. Calling list recursively will create a new context where another variable named l has a different value from the first context's l, but this has no effect on the the first l or the first loop. Recursive functions generally do not require an actual loop in order to iterate over the elements of a data structure. Try:
def list(l):
print(l.value)
if l.parent is not None:
list(l.parent)
Or:
def list(l):
while l is not None:
print(l.value)
l = l.parent
(I recommend the latter because the first one will crash with "maximum recursion depth exceeded" if the chain has more than 999 elements)
Result:
4
3
2
1
Bonus style tip: consider naming your function something other than list. In general you should avoid overwriting the names of built-in functions and types.
I should vote to close your question for the lack of a clear problem statement, but anyway...
Within an object in Python, how can I pass a reference of my current object
The same way as you'd do with just any object.
to object b of the same class
This is actually irrelevant but anyway...
such that when I call b.parent, I can get back to object a?
class Foo(object):
def __init__(self, parent=None):
self.parent = parent
a = Foo()
b = Foo(a)
print(b.parent is a)
Now for the answer to the question you didn't ask, see (and accept) Kevin's answer ;-)

Calling a class for testing - Python

First off this is a homework assignment I'm working on, but I really just need help on an error.
So the project is to implement a vector (a list in all but name for this project), using the Array class. The array class I'm using can be found here.
My error is that every time I try to call my code to test it, specifically the getitem and setitem functions, I wind up with an error stating:
builtins.TypeError: 'type' object does not support item assignment
Below is the class I'm currently building, (so far it seems that only len and contains are working).
class Vector:
"""Vector ADT
Creates a mutable sequence type that is similar to Python's list type."""
def __init__(self):
"""Constructs a new empty vector with initial capacity of two elements"""
self._vector = Array(2)
self._capacity = 2
self._len = 0
def __len__(self):
"""Returns the number of items in the vector"""
return self._len
def __contains__(self, item):
"""Determines if the given item is stored in the vector"""
if item in self._vector:
return True
else:
return False
def __getitem__(self, ndx):
"""Returns the item in the index element of the list, must be within the
valid range"""
assert ndx >= 0 and ndx <= self._capacity - 1, "Array subscript out of range"
return self._vector[ndx]
def __setitem__(self, ndx, item):
"""Sets the elements at position index to contain the given item. The
value of index must be within a valid range"""
assert ndx >= 0 and ndx <= self._capacity - 1, "Array subscript out of range"
self._vector[ndx] = item
def append(self, item):
"""Adds the given item to the list"""
if self._len < self._capacity:
self._vector[self._len] = item
self._len += 1
I'm trying to call the code by either typing:
Vector()[i] = item
or
Vector[i] = item
However, trying:
Vector[i] = item
Gives me the error, and:
Vector()[i] = item
Doesn't really seem to do anything other than not cause an error.
You need to create an instance of your Vector class. Try:
vector = Vector()
vector[0] = 42
The error means that you are trying erroneously to assign to the Vector class itself, which does not make much sense.
Try using the replace method instead of assigning a value.
Vector is a class; Vector() creates an instance of that class.
So
Vector[i] = item
gives an error: Vector.__setitem__ is an instance method (runs against an instance of a class, ie an object), not a classmethod (runs against a class). (You could in theory make it a classmethod, but I have trouble picturing a use case where that would make sense.)
On the other hand,
Vector()[i] = item
# 1. creates a Vector() object
# 2. calls {new_object}.__setitem__(self, i, item)
# 3. doesn't keep any reference to {new_object}, so
# (a) you have no way to interact with it any more and
# (b) it will be garbage-collected shortly.
Try
v = Vector()
v[i] = item
print(item in v) # => True

using __class__ to change python class, Heap Error

I am trying to make an class = that extends from list return a slice of itself instead of a list type. The reason I want to do this is because I have many other methods to manipulate the instance of A.
I am running python 2.7.3
Say I have:
class B():
def __init__(self, t, p):
self.t = t
self.p = p
class Alist(list):
def __init__(self, a_list_of_times = []):
for a_time in a_list_of_times:
self.append(a_time )
def __getslice__(self, i, j):
return super(Alist, self).__getslice__(i,j)
def plot_me(self):
pass
# other code goes here!
alist1 = Alist()
for i in range(0, 1000000):
alist1.append(B(i, i)) # yes ten million, very large list!
alist = alist1[1000:200000] # will return a list!
alist2 = Alist(alist) # will return Alist istance
The problem is that remaking the entire list as seen in making variable b is VERY VERY SLOW (comparative to the slice). What I want to do is simply change the class of alist (currently of type list)to Alist
When I try:
alist.__class__ = Alist
>>>> TypeError: __class__ assignment: only for heap types.
Which is very sad since I can do this for my own object types.
I understand that it is not standard, but it is done.
Reclassing an instance in Python.
Is there a way around this? Also I have obviously simplified the problem, where my objects a bit more complex. Mainly what I am finding is that remaking the list into my Alist version is slow. And I need to do this operation a lot (unavoidable). Is there a way to remake A? or a solution to this to make it speed up?
In my version, I can do about a 10,000 (size of my slice) slice in 0.07 seconds, and converting it to my version of Alist takes 3 seconds.
The UserList class (moved to collections in Python 3) is perfectly designed for this. It is a list by all other means but has a data attribute that you can store an underlying list in without copying.
from UserList import UserList
class Alist(UserList):
def __init__(self, iterable, copy=True):
if copy:
super(Alist, self).__init__(iterable)
else:
self.data = iterable
def plot_me(self):
pass

Python inspect iterable in __new__ method

I'm trying to write a python (2.7) matrix module. (I know about numpy, this is just for fun.)
My Code:
from numbers import Number
import itertools
test2DMat = [[1,2,3],[4,5,6],[7,8,9]]
test3DMat = [[[1,2,3],[4,5,6],[7,8,9]],[[2,3,4],[5,6,7],[8,9,0]],[[9,8,7],[6,5,4],[3,2,1]]]
class Dim(list):
def __new__(cls,inDim):
# If every item in inDim is a number create a Vec
if all(isinstance(item,Number) for item in inDim):
#return Vec(inDim)
return Vec.__new__(cls,inDim)
# Otherwise create a Dim
return list.__new__(cls,inDim)
def __init__(self,inDim):
# Make sure every item in inDim is iterable
try:
for item in inDim: iter(item)
except TypeError:
raise TypeError('All items in a Dim must be iterable')
# Make sure every item in inDim has the same length
# or that there are zero items in the list
if len(set(len(item) for item in inDim)) > 1:
raise ValueError('All lists in a Dim must be the same length')
inDim = map(Dim,inDim)
list.__init__(self,inDim)
class Vec(Dim):
def __new__(cls,inDim):
if cls.__name__ not in [Vec.__name__,Dim.__name__]:
newMat = list.__new__(Vec,inDim)
newMat.__init__(inDim)
return newMat
return list.__new__(Vec,inDim)
def __init__(self,inDim):
list.__init__(self,inDim)
class Matrix(Dim):
def __new__(cls,inMat):
return Dim.__new__(cls,inMat)
def __init__(self,inMat):
super(Matrix,self).__init__(inMat)
Current Functionality:
So far I have written a few classes, Matrix, Dim, and Vec. Matrix and Vec are both subclasses of Dim. When creating a matrix, one would first start out with a list of lists and they would create a matrix like:
>>> startingList = [[1,2,3],[4,5,6],[7,8,9]]
>>> matrix.Matrix(startingList)
[[1,2,3],[4,5,6],[7,8,9]]
This should create a Matrix. The created Matrix should contain multiple Dims all of the same length. Each of these Dims should contain multiple Dims all of the same length, etc. The last Dim, the one that contains numbers, should contain only numbers and should be a Vec instead of a Dim.
The Problem:
All of this works, for lists. If I were however, to use an iterator object instead (such as that returned by iter()) this does not function as I want it to.
For example:
>>> startingList = [[1,2,3],[4,5,6],[7,8,9]]
>>> matrix.Matrix(iter(startingList))
[]
My Thoughts:
I'm fairly certain that this is happening because in Dim.__new__ I iterate over the input iterable which, when the same iterable is then passed to Matrix.__init__ it has already been iterated over and will therefore appear to be empty, resulting in the empty matrix that I get.
I have tried copying the iterator using itertools.tee(), but this also doesn't work because I don't actually call Matrix.__init__ it gets called implicitly when Matrix.__new__ returns and I therefore cannot call it with different parameters than those passed to Matrix.__init__. Everything I have thought of to do comes up against this same problem.
Is there any way for me to preserve the existing functionality and also allow matrix.Matrix() to be called with an iterator object?
The key is that Vec.__init__ is getting called twice; once inside your __new__ method and once when you return it from the __new__ method. So if you mark it as already initialised and return early from Vec.__init__ if it is already initialised, then you can ignore the second call:
class A(object):
def __new__(cls, param):
return B.__new__(cls, param + 100)
class B(A):
def __new__(cls, param):
b = object.__new__(B)
b.__init__(param)
return b
def __init__(self, param):
if hasattr(self, 'param'):
print "skipping __init__", self
return
self.param = param
print A(5).param
What you would need to do is check if the variable that is passed in is a tuple or list. If it is then you can use it directly, otherwise you need to convert the iterator into a list/tuple.
if isinstance(inDim, collections.Sequence):
pass
elif hastattr(inDim, '__iter__'): # this is better than using iter()
inDim = tuple(inDim)
else:
# item is not iterable
There is also a better way of checking that the length of all the lists are the same:
if len(inDim) > 0:
len_iter = (len(item) for item in inDim)
first_len = len_iter.next()
for other_len in len_iter:
if other_len != first_len:
raise ValueError('All lists in a Dim must be the same length')

Categories