Unable to call overridden setter property of superclass - python-2.7

I'm trying to use python properties and inheritance and something isn't behaving intuitively. I want to be able to utilize property getters and setters of the inherited class to avoid having to repeat code for repeated behavior.
In order to boil down my problem I created the following example. I have a Car with behavior for counting passengers and populating the car's seats when given some number of passengers (using the occupants property getter and setter). For a van with 3 rows of seats I should only have to define the behavior of the 3rd seat and defer to the inherited Car class for the first 2 rows...
class Car(object):
def __init__(self):
#Store vehicle passengers in a dictionary by row
# - by default we'll have 1 in the front seat and 1 in the back
self._contents = {'row1': 1,
'row2': 1}
#property
def occupants(self):
"""
Number of occupants in the vehicle
"""
#Get the number of people in row 1
row1 = self._contents['row1']
#Get the number of people in row 2
row2 = self._contents['row2']
return row1 + row2
#occupants.setter
def occupants(self, val):
#Start with an empty car
self._contents['row1'] = 0
self._contents['row2'] = 0
#Check to see whether there are more than 2 people entering the car
if val > 2:
#Put 2 in the front seats
self._contents['row1'] = 2
#Put the rest in the back seat - no matter how many there are!
self._contents['row2'] = val - 2
else:
#Since there are 2 or fewer people, let them sit in the front
self._contents['row1'] = val
class Van(Car):
def __init__(self):
super(Van, self).__init__()
#Van's have an additional 3rd row
self._contents['row3'] = 1
#property
def occupants(self):
#Number of people in first 2 rows
first_2_rows = super(Van, self).occupants
#Number of people in 3rd row
row3 = self._contents['row3']
#Total number of people
return first_2_rows + row3
#occupants.setter
def occupants(self, val):
#Start with an empty van (Car class handles row1 and row2)
self._contents['row3'] = 0
#Check if there are more than 4 people entering the van
if val > 4:
#Put all but 4 folks in the back row
self._contents['row3'] = val - 4
#Load the first 2 rows in the same manner as for a car
#This causes an AttributeError
super(Van, self).occupants = 4
else:
#This causes an AttributeError
super(Van, self).occupants = val
if __name__ == '__main__':
van = Van()
print "Van has {0} people".format(van.occupants)
print "Seating 6 people in the van..."
van.occupants = 6
print "Van has {0} people".format(van.occupants)
The output I get is as follows:
Van has 3 people
Seating 6 people in the van...
Traceback (most recent call last):
File "C:/scratch.py", line 74, in <module>
van.occupants = 6
File "C:/scratch.py", line 65, in occupants
super(Van, self).occupants = 4
AttributeError: 'super' object has no attribute 'occupants'
Process finished with exit code 1
What's especially interesting to me is that the superclass's getter is working fine, but when I try to use the setter I get the attribute error. Am I using super() incorrectly? Is there a better way to do this?
My actual application involves reading/writing between a text file format and a dictionary-like data structure. Some of the stuff in the text file is parsed out by my base class and some other special parameters are handled by the subclass. In the subclass setter I want to start by letting the base class parse whatever it needs from the text file (populating the data structure), then let the subclass parse out additional values for storage in the inherited data structure.
Some research lead me to this and eventually Issue 505028 where it is declared a "feature" instead of a bug. So with that out of the way, is there a way to make the above logic work using properties and inheritance? Do I have to use Car.occupants.fset(self, 4) or something? I may answer myself in a minute, but I'm going to go ahead and post this to share it with folks. SOrry if it's a duplicate.
edit:
Fixed some additional bugs like emptying all seats before setting occupants and the numeric logic in the Van occupants setter was wrong and incomplete (only became evident once the property error was fixed).

As you've noted, Guido van Rossum says,
... the semantics of super ... only applies to
code attributes, not to data attributes.
So the workaround is to call a code attribute, not a data attribute. That means, in this case, that you need to keep a reference to the setter method; let's call it set_occupants. So instead of
#occupants.setter
def occupants(self, val):
use
def set_occupants(self, val):
...
occupants = property(get_occupants, set_occupants)
and instead of super(...).occupants = 4, you call the super's method:
super(Van, self).set_occupants(4)
class Car(object):
def __init__(self):
#Store vehicle passengers in a dictionary by row
# - by default we'll have 1 in the front seat and 1 in the back
self._contents = {'row1': 1,
'row2': 1}
def get_occupants(self):
"""
Number of occupants in the vehicle
"""
#Get the number of people in row 1
row1 = self._contents['row1']
#Get the number of people in row 2
row2 = self._contents['row2']
return row1 + row2
def set_occupants(self, val):
#Check to see whether there are more than 2 people entering the car
if val > 2:
#Put 2 in the front seats
self._contents['row1'] = 2
#Put the rest in the back seat - no matter how many there are!
self._contents['row2'] = val - 2
else:
#Since there are 2 or fewer people, let them sit in the front
self._contents['row1'] = val
occupants = property(get_occupants, set_occupants)
class Van(Car):
def __init__(self):
super(Van, self).__init__()
#Van's have an additional 3rd row
self._contents['row3'] = 1
def get_occupants(self):
#Number of people in first 2 rows
first_2_rows = super(Van, self).occupants
#Number of people in 3rd row
row3 = self._contents['row3']
#Total number of people
return first_2_rows + row3
def set_occupants(self, val):
#Check if there are more than 4 people entering the van
if val > 4:
#Put all but 4 folks in the back row
self._contents['row3'] = val - 4
#Load the first 2 rows in the same manner as for a car
super(Van, self).set_occupants(4)
occupants = property(get_occupants, set_occupants)
if __name__ == '__main__':
van = Van()
print "Van has {0} people".format(van.occupants)
print "Seating 6 people in the van..."
van.occupants = 6
print "Van has {0} people".format(van.occupants)
yields
Van has 3 people
Seating 6 people in the van...
Van has 6 people
To continue using the #property decorator and yet still be able to call the setter from super, and without having to manually add lots of extra attributes by hand, you could use a metaclass to do the work for you. A class decorator would also be possible, but the advantage of a metaclass is that you need only define it once as the metaclass of Car, and then the metaclass and its behaviour is inherited by all subclasses of Car, whereas, a class decorator would have to be applied to each subclass manually.
class MetaCar(type):
def __init__(cls, name, bases, clsdict):
super(MetaCar, cls).__init__(name, bases, clsdict)
for name, val in clsdict.items():
if isinstance(val, property):
setattr(cls, 'get_{}'.format(name), val.fget)
setattr(cls, 'set_{}'.format(name), val.fset)
setattr(cls, 'del_{}'.format(name), val.fdel)
class Car(object):
__metaclass__ = MetaCar
def __init__(self):
#Store vehicle passengers in a dictionary by row
# - by default we'll have 1 in the front seat and 1 in the back
self._contents = {'row1': 1,
'row2': 1}
#property
def occupants(self):
"""
Number of occupants in the vehicle
"""
#Get the number of people in row 1
row1 = self._contents['row1']
#Get the number of people in row 2
row2 = self._contents['row2']
return row1 + row2
#occupants.setter
def occupants(self, val):
#Check to see whether there are more than 2 people entering the car
if val > 2:
#Put 2 in the front seats
self._contents['row1'] = 2
#Put the rest in the back seat - no matter how many there are!
self._contents['row2'] = val - 2
else:
#Since there are 2 or fewer people, let them sit in the front
self._contents['row1'] = val
class Van(Car):
def __init__(self):
super(Van, self).__init__()
#Van's have an additional 3rd row
self._contents['row3'] = 1
#property
def occupants(self):
#Number of people in first 2 rows
first_2_rows = super(Van, self).occupants
#Number of people in 3rd row
row3 = self._contents['row3']
#Total number of people
return first_2_rows + row3
#occupants.setter
def occupants(self, val):
#Check if there are more than 4 people entering the van
if val > 4:
#Put all but 4 folks in the back row
self._contents['row3'] = val - 4
#Load the first 2 rows in the same manner as for a car
super(Van, self).set_occupants(4)
if __name__ == '__main__':
van = Van()
print "Van has {0} people".format(van.occupants)
print "Seating 6 people in the van..."
van.occupants = 6
print "Van has {0} people".format(van.occupants)

Looking around the web I found that SomeClass.property.fset(self, value) can be used to call the setter of a property. In this case SomeClass is Car (the super-class of Van) and self is the current Van instance, which the car occupants setter operates on to populate the first 2 rows of the van (just like for a car).
class Car(object):
def __init__(self):
#Store vehicle passengers in a dictionary by row
# - by default we'll have 1 in the front seat and 1 in the back
self._contents = {'row1': 1,
'row2': 1}
#property
def occupants(self):
"""
Number of occupants in the vehicle
"""
#Get the number of people in row 1
row1 = self._contents['row1']
#Get the number of people in row 2
row2 = self._contents['row2']
return row1 + row2
#occupants.setter
def occupants(self, val):
#Start with an empty car
self._contents['row1'] = 0
self._contents['row2'] = 0
#Check to see whether there are more than 2 people entering the car
if val > 2:
#Put 2 in the front seats
self._contents['row1'] = 2
#Put the rest in the back seat - no matter how many there are!
self._contents['row2'] = val - 2
else:
#Since there are 2 or fewer people, let them sit in the front
self._contents['row1'] = val
class Van(Car):
def __init__(self):
super(Van, self).__init__()
#Van's have an additional 3rd row
self._contents['row3'] = 1
#property
def occupants(self):
#Number of people in first 2 rows
first_2_rows = super(Van, self).occupants
#Number of people in 3rd row
row3 = self._contents['row3']
#Total number of people
return first_2_rows + row3
#occupants.setter
def occupants(self, val):
#Start with an empty van (first 2 rows handled by Car class)
self._contents['row3'] = 0
#Check if there are more than 4 people entering the van
if val > 4:
#Put all but 4 folks in the back row
self._contents['row3'] = val - 4
#Load the first 2 rows in the same manner as for a car
Car.occupants.fset(self, 4)
else:
#Load the first 2 rows in the same manner as for a car
Car.occupants.fset(self, val)
if __name__ == '__main__':
van1 = Van()
van2 = Van()
print "Van has {0} people".format(van1.occupants)
print "Seating 6 people in van1"
van1.occupants = 6
print "Seating 2 people in van2"
van2.occupants = 2
print "van1 has {0} people".format(van1.occupants)
print "van2 has {0} people".format(van2.occupants)
The output is:
Van has 3 people
Seating 6 people in van1
Seating 2 people in van2
van1 has 6 people
van2 has 2 people
Props also to unutbu for lots of work on this and demonstrating that this could also be solved with the property function or a metaclass. I'm not yet sure which is more elegant since each method has pro's and con's.
If answering my own question is bad form in this instance, call me out and I'll gladly do whatever is necessary to follow the community protocol.

Related

Retrieve integer from django model field and add it to dictionary if it meets criteria

I'm having trouble getting the code right for this project that I am working on...
Basically I have a function with some variables defined:
def mealSelect():
calorielimit = Profile.objects.get(pk=user_id).bmr
breakfast = Breakfast.objects.all()
lunch = Lunch.objects.all()
dinner = Dinner.objects.all()
snack = Snack.objects.all()
breakfastcals = Breakfast.objects.value_list('calories')
lunchcals = Lunch.objects.value_list('calories')
dinnercals = Dinner.objects.value_list('calories')
snackcals = Snack.objects.value_list('calories')
todayscals = {}
What I am trying to do is:
Retrieve a random object for each meal (breakfast, lunch, dinner and
snack)
If the random objects combined are less than the calorielimit append
them to a dictionary with the keys Meal 1, Meal 2, Meal 3, Meal 4
If the random objects combined exceed the calorielimit, continue
until a combination is found that does not exceed the calorie limit
Return the qualifying dictionary
Or if there's a better way to achieve a set of 4 objects below the calorie limit, the concept above is the way I tried and I just couldn't figure out how to make it work so I'm guessing there's probably an easier, more elegant way to get 1 model from each model that when combined are below the calorielimit.
Any help is very much appreciated!
Assuming you Breakfast, Lunch, Dinner and Snack models have a field called cals for calories
def mealSelect():
calorielimit = Profile.objects.get(pk=user_id).bmr
totalcals = calorielimit + 1
while totalcals > calorielimit:
breakfast = Breakfast.objects.all().order_by('?')[0]
lunch = Lunch.objects.all().order_by('?')[0]
dinner = Dinner.objects.all().order_by('?')[0]
snack = Snack.objects.all().order_by('?')[0]
totalcals = breakfast.cals + lunch.cals + dinner.cals + snack .calls
Your meal plan is breakfast, lunch, dinner and snack
You can get all the results from DB, and consolidate all the combinations of the meals and just pick at random a meal that meets your criteria.
from itertools import product
def mealSelect():
calorielimit = Profile.objects.get(pk=user_id).bmr
breakfast = Breakfast.objects.values('calories', 'pk')
lunch = Lunch.objects.values('calories', 'pk')
dinner = Dinner.objects.values('calories', 'pk')
snack = Snack.objects.values('calories', 'pk')
allcals = product(breakfast, lunch, snack, dinner) # create all combinations of the four meals
allmeals = [] # desired list
for item in allcals:
if sum(i.get('calories', 0) for i in item) < calorielimit:
allmeals.append(item)
random_set = allmeals[random.randit(0, len(allmeals))] #pick an index at random
breakfast_item, lunch_item, snack_item, dinner_item = random_set
breakfast_obj = Breakfast.objects.get(id=breakfast_item["id"])
lunch_obj = Lunch.objects.get(id=lunch_item["id"])
snack_obj = Snack.objects.get(id=snack_item["id"])
dinner_obj = Dinner.objects.get(id=dinner_item["id"])
return {"breakfast": breakfast_obj, "lunch": lunch_obj, "snack": snack_obj, "dinner": dinner_obj}
I would suggest something like :
Pick a Breakfast randomly
update the remaining calorielimit
pick a Lunch randomly which has calories less than the updated calorielimit
update the remaining calorielimit
pick a Dinner randomly which has calories less then the updated calorielimit
update the remaining calorielimit
pick a Snack randomly which has calories less then the updated calorie limit
in Code it might look like this:
import random
def mealSelect():
calorielimit = Profile.objects.get(pk=user_id).bmr
breakfast = random.choice(Breakfast.objects.filter(calories__lte=calorielimit))
calorielimit -= breakfast.caloriesbreakfast
lunch = random.choice(Lunch.objects.filter(calories__lte=calorielimit))
calorielimit -= lunch.calories
dinner = random.choice(Dinner.objects.filter(calories__lte=calorielimit))
calorielimit -= dinner.calories
snack = random.choice(Snack.objects.filter(calories__lte=calorielimit))
return {"Meal 1": breakfast, "Meal 2": lunch, "Meal 3": dinner, "Meal 4": snack}
This might not work depending on the actual meals in general since for example Breakfast, Lunch and Dinner might already exceed the calorielimit. For this to work you would need to add every meal after retrieving it via random.choice to the dictonary, put everything within a while loop under the condition that all items of the dictionary are not None.
For this you'd also need a small custom function
import random
def defaultchoice(lst,default):
try:
return random.choice(lst)
except IndexError:
return default
With this my approach would look like treturn_dicthis :
def mealSelect():
return_dict = {}
while not all(return_dict.values()):
calorielimit = Profile.objects.get(pk=user_id).bmr
breakfast = defaultchoice(Breakfast.objects.filter(calories__lte=calorielimit), None)
calorielimit -= breakfast.caloriesbreakfast
return_dict["Meal 1"] = breakfast
lunch = defaultchoice(Lunch.objects.filter(calories__lte=calorielimit), None)
calorielimit -= lunch.calories
return_dict["Meal 2"] = lunch
dinner = defaultchoice(Dinner.objects.filter(calories__lte=calorielimit),None)
calorielimit -= dinner.calories
return_dict["Meal 3"] = dinner
snack = random.choice(Snack.objects.filter(calories__lte=calorielimit), None)
return_dict["Meal 4"] = snack
return return_dict

i don't understand why list in object is shared with other objects

I make test python 2.7 code like below.
class test(object):
#initial 5*5 array with 1
value = [[1 for col in range(5)] for row in range(5)]
def __init__(self, name) :
self.name = name
test_list = [test("TEST"), test("test")]
test_list[0].value[0][0] = 0
print test_list[0].value[0][0]
print test_list[1].value[0][0]
What I expect is result like this
0
1
But Actual result is like
0 0
So it look like object "TEST" and "test" are sharing list.
If I change that code as below, then this problem doesn't appear.
class test(object):
def __init__(self, name) :
self.value = [[1 for col in range(5)] for row in range(5)]
self.name = name
test_list = [test("TEST"), test("test")]
test_list[0].value[0][0] = 0
print test_list[0].value[0][0]
print test_list[1].value[0][0]
I want to know why this two codes work different.
In the first example, you've made value a class variable, so it belongs to the actual class and is shared among all instances of the class. In the second example, value belongs to instances of the class, and a separate copy of value is made for each instance.

returning a replaced field in a list of namedtuples in a namedtuple

I have a Restaurant and Dish type namedtuple defined below:
Restaurant = namedtuple('Restaurant', 'name cuisine phone menu')
Dish = namedtuple('Dish', 'name price calories')
r1 = Restaurant('Thai Dishes', 'Thai', '334-4433', [Dish('Mee Krob', 12.50, 500),
Dish('Larb Gai', 11.00, 450)])
I need to change the price of the dish by 2.50. I have the following code:
def Restaurant_raise_prices(rest):
result = []
for item in rest:
for dish in item.menu:
dish = dish._replace(price = dish.price + 2.50)
result.append(item)
return result
It replaces the price field and returns the Dish namedtuple:
[Dish(name='Mee Krob', price=15.0, calories=500), Dish(name='Larb Gai', price=13.5, calories=450)]
How can I change my code to add the restaurant as well?
but it only returns the Dish. What if I wanted the entire Restaurant too? How can I make the change so that the output is:
Restaurant('Thai Dishes', 'Thai', '334-4433', [Dish(name='Mee Krob', price=15.0, calories=500), Dish(name='Larb Gai', price=13.5, calories=450)])
Named tuples are first and foremost tuples, and as such immutable. That means that you cannot modify the existing objects. If you wanted to change them, you would have to create new tuples containing the new values and replace all references to the old tuple with that new tuple.
The code you are using does not work for that, since dish = dish._replace(…) will replace the value of dish with a new tuple, but changing what dish references will not update the reference that exists within the restaurant tuple. Also, with the line result.append(item) being part of the inner loop where you iterate over the dishes, you end up with multiple (unchanged!) copies of the same restaurant tuple in the result.
You could change it like this to make it work (btw. assuming you only pass a single restaurant to the function—so you only need one loop for the dishes):
def restaurant_raise_prices (rest):
dishes = []
for dish in rest.menu:
dishes.append(dish._replace(price=dish.price + 2.50))
rest = rest._replace(menu=dishes)
return rest
This will return a new restaurant with the changed prices for each dish (note that r1 won’t reflect this change):
>>> r1
Restaurant(name='Thai Dishes', cuisine='Thai', phone='334-4433', menu=[Dish(name='Mee Krob', price=12.5, calories=500), Dish(name='Larb Gai', price=11.0, calories=450)])
>>> restaurant_raise_prices(r1)
Restaurant(name='Thai Dishes', cuisine='Thai', phone='334-4433', menu=[Dish(name='Mee Krob', price=15.0, calories=500), Dish(name='Larb Gai', price=13.5, calories=450)])
A cleaner way however would be to introduce proper types that are mutable, so you can make this a bit better. After all, restaurants are objects that can change: They can modify their menu all the time, without becoming a new restaurant. So it makes sense to have the restaurants—as well as the dishes—be mutable objects instead:
class Restaurant:
def __init__ (self, name, cuisine, phone):
self.name = name
self.cuisine = cuisine
self.phone = phone
self.menu = []
def __str__ (self):
return '{} ({}) - {} ({} dishes)'.format(self.name, self.cuisine, self.phone, len(self.menu))
class Dish:
def __init__ (self, name, price, calories):
self.name = name
self.price = price
self.calories = calories
def raise_price (self, amount):
self.price += amount
def __str__ (self):
return '{} (price: {}, calories: {})'.format(self.name, self.price, self.calories)
>>> r1 = Restaurant('Thai Dishes', 'Thai', '334-4433')
>>> r1.menu.append(Dish('Mee Krob', 12.50, 500))
>>> r1.menu.append(Dish('Larb Gai', 11.00, 450))
>>> print(r1)
Thai Dishes (Thai) - 334-4433 (2 dishes)
>>> for dish in r1.menu:
print(dish)
Mee Krob (price: 12.5, calories: 500)
Larb Gai (price: 11.0, calories: 450)
>>> for dish in r1.menu:
dish.raise_price(2.50)
print(dish)
Mee Krob (price: 15.0, calories: 500)
Larb Gai (price: 13.5, calories: 450)

setting an attribute at create retrieves None value - Python

So I have an Article class that models the articles in a store. When I create a new article, I want it to have an EAN 13 code. So I initialize the article with a 12 digits code and use the check_ean13() funtion to retrieve the control digit. It works but seems like in any moment, when the object is created, rewrite the ean13 attribute and replaces it for None. Any ideas?
Main
if __name__ == "__main__":
# create article
art1 = Article("123456789087", "Article 1", 145.6, 200.0)
print art1
print art1.get_ean13()
class Article
class Article:
def __init__(self, cod, art_name, cost, listprice):
self.ean13 = self.set_ean13(cod)
self.art_name = art_name
self.cost = cost
self.listprice = listprice
self.commission = None
self.promotion=[]
def get_ean13(self):
return self.ean13
def set_ean13(self,cod):
cd = self.check_ean13(cod)
ean13 = cod + str(cd)
self.ean13=ean13
def check_ean13(self, code):
checksum = 0
for i, digit in enumerate(reversed(code)):
checksum += int(digit) * 3 if (i % 2 == 0) else int(digit)
return (10 - (checksum % 10)) % 10
output:
None - Article 1 list price: 400.0
None
self.ean13 = self.set_ean13(cod)
set_ean13 doesn't return anything, so you're effectively doing self.ean13 = None here. Just call the method without assigning the result.
self.set_ean13(cod)

Sort a list of lists within a class Python

I have created the following
class uniquePlayers():
def __init__(self):
self._item = [ [] for i in range(5) ]
self.count = 0
def addPlayer(self, firstInstance, firstName, lastName, Country, long):
self._item[0].append(firstInstance)
self._item[1].append(firstName.lower())
self._item[2].append(lastName.lower())
self._item[3].append(Country)
self._item[4].append(long.lower())
self.count += 1
def sortByKey(self, index = 4 ):
self._item[index].sort()
I am trying to sort the whole class, keeping everything from the other lists following along. Obviously what I have done is just sort the 'long' list
You should keep the data that belongs together together. So instead of having 5 lists with one attribute each, have a single list with all the player data:
def __init__(self):
self._players = []
def addPlayer(self, firstInstance, firstName, lastName, Country, long):
player = (firstInstance, firstName.lower(), lastName.lower(), Country, long.lower())
self._players.append(player)
def sortByKey(self, index=4):
self._players.sort(key=lambda x: x[index])
You could also use a named tuple for the player data, to access it more easily, or even better, introduce a new type (class) for the players.