python accessing class variables in instances from derived classes - python

I have gone through many answers and not found what I understand to be the answer. This is a test program based on a problem in my real program. I want to have a class variable that I can change and have the change apply to all instances of a class, but not to a similar class even with the same form.
As is probably obvious, I am defining a class variable for X in line 3 and a class variable for Y in line 9. I am trying to access these in lines 23-25.
My model is
#! /usr/bin/python -t
class X:
clsvar = "Animal"
def show(self):
clsvar
def chg(self,creature):
clsvar = creature
class Y:
clsvar = "Plant"
def show(self):
clsvar
def chg(self,creature):
clsvar = creature
class A(X):
pass
class B(X):
pass
class C(Y):
pass
a = A()
b = B()
c = C()
print "1 " + a.show()
print "2 " + b.show()
print "3 " + c.show()
a.chg( "Dog")
print "4 " + a.show()
print "5 " + b.show()
print "6 " + c.show()
My result is
Traceback (most recent call last):
File "180.py", line 23, in ?
print "1 " + a.show()
File "180.py", line 5, in show
clsvar
NameError: global name 'clsvar' is not defined
I would have thought clsvar would appear in any of the derived classes and not need to be global. I am obviously being stupid here but I have tried this dozens of ways without success.
Incidentally, I was able to do this in Ruby.
#! /usr/bin/ruby -w
class X
##clsvar = "Animal"
def self.show
##clsvar
end
def self.chg(creature)
##clsvar = creature
end
end
class Y
##clsvar = "Plant"
def self.show
##clsvar
end
def self.chg(creature)
##clsvar = creature
end
end
class A < X
A.show
end
class B < X
B.show
end
class C < Y
C.show
end
a = A
b = B
c = C
puts "1 " + a.show
puts "2 " + b.show
puts "3 " + c.show
a.chg( "Dog")
puts "4 " + a.show
puts "5 " + b.show
puts "6 " + c.show
And the output is:
1 Animal
2 Animal
3 Plant
4 Dog
5 Dog
6 Plant

To access a class variable you have to do this:
MyClass.clsvar
Or even this:
an_instance.clsvar
The latter works only if the instance does not have any instance variable called clsvar.(*)
Python is not like Java. Take into account that, unlike Java, Python does have global variables. For example:
a = 1
class MyClass:
a = 2
def show(self):
print(a)
The show method would print 1, since it refers to the global variable a, and not to MyClass.a.
(*) A note on this. You can access MyClass.var using self.var and it's fine if you do not modify it. But setting the value is not equivalent. If you want to set a class variable then you have to use MyClass.var = value and not an_instance.var = value. The latter would create a new instance variable called var with value value, so MyClass.var will still have the old value.
By the way, I do not know Ruby, but I think the ## syntax is used to access class variables so that's why it works.
Finally, your code is incorrect. You probably want to put some return statements in those methods, otherwise you'll get some TypeErrors when executing it.

Related

Variable within an instance of a class does not take a new value when it is assigned.

So, I'm working on a command line RPG for the sake of filling time, and re-stretching my Python muscles as I've been out of practice for a couple of years. I used to code in a really functional manner but I'm trying to get my head around object-orientated programming.
Preamble aside, I have an issue where after creating an instance of a class, my class variable is no longer being defined. I've made 2 versions of this which I'll use to demonstrate since I'm finding it hard to articulate.
Below I created a "character" class which I intended to use as a basis for both player characters and npcs. In the first draft I was updating this class, before realising it was going to affect subclasses, when I really just wanted it as a template. Either way, this particular code block worked; it adds the values of 2 dictionaries together, then assigns them to character.characterStats. It then prints them as per displayStats().
from collections import Counter
class character:
def __init__(self, *args, **kwargs):
pass
def __setattr__(self, name, value):
pass
characterRace = ''
characterStats = {}
charLocation = ''
charName = ''
class race:
def __init__(self):
pass
baseStatsDict = {
'Strength' : 5,
'Agility' : 5,
'Toughness' : 5,
'Intelligence' : 5 }
humanStatsDict = {
'Strength' : 1,
'Agility' : 1,
'Toughness' : 1,
'Intelligence' : 1 }
def displayRace():
print("Race: ", character.characterRace, "\n")
def displayStats():
for stat, value in character.characterStats.items():
print(stat, "=", value)
print("\n")
def raceSelection():
playerInput = input("I am a ")
playerInput
playerLower = playerInput.lower()
while "human" not in playerLower:
if "human" in playerLower:
character.characterStats = dict(Counter(race.baseStatsDict)+Counter(race.humanStatsDict))
character.characterRace = 'Human'
break
playerInput = input()
playerInput
playerLower = playerInput.lower()
playerChar = character()
raceSelection()
displayRace()
displayStats()
And this was the output:
Race: Human
Strength = 6
Agility = 6
Toughness = 6
Intelligence = 6
This however is the new code when I tried to tidy it up and turn the class into the template it was meant to be, and started using the class instance playerChar which for whatever reason can't assign the new value to playerChar.characterStats. playerChar.displayStats() prints the characterRace and characterStats variables as empty, even though they are assigned when the player enters the value human.
from collections import Counter
class character:
characterRace = ''
characterStats = {}
def __init__(self):
pass
def displayRace(self):
print("Race: ", self.characterRace, "\n")
def displayStats(self):
for stat, value in self.characterStats.items():
print(stat, "=", value)
print("\n")
class race:
def __init__(self):
pass
baseStatsDict = {
'Strength' : 5,
'Agility' : 5,
'Toughness' : 5,
'Intelligence' : 5 }
humanStatsDict = {
'Strength' : 1,
'Agility' : 1,
'Toughness' : 1,
'Intelligence' : 1 }
def raceSelection():
playerInput = input("I am a ")
playerInput
playerLower = playerInput.lower()
while "human" not in playerLower:
if "human" in playerLower:
playerChar.characterStats = dict(Counter(race.baseStatsDict)+Counter(race.humanStatsDict))
playerChar.characterRace = 'Human'
break
playerInput = input()
playerInput
playerLower = playerInput.lower()
playerChar = character()
raceSelection()
playerChar.displayRace()
playerChar.displayStats()
So this will output:
Race:
\n
\n
\n
So I know it's able to draw from the class race dictionaries and add their contents together as from the previous code. If I try and print the player.x characteristics it won't throw any errors so it recognises they exist. If anyone could explain to me what's going wrong and why in this new iteration, I'd be very grateful.
EDIT: So a friend and I have tried passing the class as an argument of raceSelection(), we've tried printing a string after each call/update of a variable and we've tried entering a string into the variable, printing it, then redefining the variable with a new string.
Input:
class character:
charRace = ''
charStats = {}
charLocation = ''
charName = ''
charString = "Cole said define a string."
Within the if statements:
if "human" in playerLower:
print("Oh, you're just a really ugly human.")
playerChar.charStats = dict(Counter(race.baseStatsDict)+Counter(race.humanStatsDict))
playerChar.charRace = 'Ugly Human'
print("playerChar.charString = ", playerChar.charString)
playerChar.charString = "Redefine."
print("playerChar.charString = ", playerChar.charString)
break
Output:
Oh, you're just a really ugly human.
playerChar.charString = Cole said define a string.
playerChar.charString = Cole said define a string.
Race:
It should not be character.characterStats.items(), but self.characterStats.items(). Similarly for all other values that belong to one, specific character.
Using the name of the class assigns a value that belongs to the class, and is the same for all objects you create. Lookup instance vs class attributes.
So, after trying to move the variables in and out of __init__, trying setattr(), trying to pass any sort of argument through the class just so it had some data, trying to run the instance of the class through a function, none of those solutions came to work in the end.
The solution turned out to be to create a subclass of character and manipulate that instead. I figured this would be alright as well since the player character will mutate throughout gameplay, and will never see further subclasses of itself.

pickle not saving (or loading?) object list variable outside __init__

Python beginner question here. I'm trying to save and load objects using pickle in a text based game I'm making, and list variables are not loading as expected. This is the code I wrote to test the problem:
import pickle
class my_class(object):
def __init__(self, x):
self.x = x
self.y = [1,2,3]
a = [4,5,6]
b = 8
def save(object1):
print("Game saved")
pickle_out = open("saveobject1","wb")
pickle.dump(object1, pickle_out)
pickle_out.close()
def load():
print("Loading......")
pickle_in = open("saveobject1","rb")
object1 = pickle.load(pickle_in)
return object1
object1 = my_class(10)
print ("object1.x = " + str(object1.x))
print ("object1.y = " + str(object1.y))
print ("object1.a = " + str(object1.a))
print ("object1.b = " + str(object1.b))
print ("\n")
answer = input("Would you like to save (s) or load (l)?: ")
if answer == "s":
save(object1)
object1.x = 20
object1.y[2] = 6
object1.a[2] = 12
object1.b = 16
if answer == "l":
object1 = load()
print ("object1.x = " + str(object1.x))
print ("object1.y = " + str(object1.y))
print ("object1.a = " + str(object1.a))
print ("object1.b = " + str(object1.b))
print ("\n")
List variables within init save and load OK (y in this example) but list variables outside init do not (a in this example). However, non-list variables outside init do save and load. Thanks in advance for advice.
All variables within __init__ are instance variables, and so will be saved. Both the list and non-list variables outside __init__ are class variables, so won't be saved.
However, when you change object1.b that creates the instance variable b instead of setting the class variable, so it will be saved. However, when you modify object1.a, you are not reassigning it, just an element of it, so it is still an (unsaved) class variable.
If you want to save it, make it an instance variable or save the class variables separately.

dynamic instances of a class object overwriting each other

I have a simple class that stores simple data. The class is as follows.
class DataFormater:
def __init__(self, N, P, K, price):
self.N = N
self.P = P
self.K = K
self.price = price
The code that calls this class is
from DataFormater import DataFormater
#global variables
ObjectList = [0,1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50]
ObjectListCounter = 0
# main
print "enter you N-P-K values, followed by a coma, then the price"
print "example ----> 5 5 5 %50 "
print "return as many values as you want to sort, then enter, 'done!' when done."
while True:
RawData = raw_input()
if RawData == 'done!':
break
else:
ObjectList[ObjectListCounter] = DataFormater
ObjectList[ObjectListCounter].N = int(RawData[0])
# very simple test way of putting first indice in ObjectList[ObjectListCounter].N
ObjectListCounter += 1
print ObjectList[0].N
print ObjectList[1].N
My idea is that ObjectList[0] would create that object '1' that I could call with 1.N
But, when I call these, it seems that I have overwritten the previous instances.
this is what prints...
return as many values as you want to sort, then enter, 'done!' when done.
12
1
done!
1
1
Thanks so much! And I know that my post is messy, I don't exactly know how to make it more "pretty"
So, it looks like you are assigning the actual class (instead of an instance of the class) in your loop. Where you do this:
ObjectList[ObjectListCounter] = DataFormater
I think what you actually want is this
ObjectList[ObjectListCounter] = DataFormater(...insert args here....)
EDIT to address the comments:
Your class init method looks like this:
def __init__(self, N, P, K, price):
That means that to create an instance of your class, it would look like this:
my_formater = DataFormater(1, 2, 3, 4)
You would then be able to access my_formater.N which would have a value of 1.
What you are trying to do instead is access a CLASS level attribute, DataFormater.N. This is generally used in situations where you have a constant variable that does not change between instances of the class. For example:
class DataFormater():
CONSTANT_THING = 'my thing that is always the same for every instance'
You would then be able to access that variable directly from the class, like this:
DataFormater.CONSTANT_THING
I hope that clears things up.

Class and while loop?

I'm new to Python so I'm sorry if I've made any silly mistake. I have a class with a method isSorted(). I want this method to return True if two lists (a and b) are the same:
class Puzzle:
def isSorted(self):
return set(self.a) == set(self.b)
I use this method outside the class, like this:
puzzle = Puzzle()
while puzzle.isSorted():
#do things
The loop never ends. Is there any way I could solve this, and is it bad code? I've tried
puzzleIsSorted = puzzle.isSorted()
outside the class and re-assigning it in the while loop, but it still didn't work.
edit: full code:
import random
class Puzzle:
def __init__(self):
self.f = open("file.txt", "r")
self.a = self.f.readlines()
self.b = self.a
random.shuffle(self.b)
def swapLine(self, line):
self.b[len(self.b) - 1], self.b[line] = self.b[line], self.b[len(self.b) - 1]
def isSorted(self):
return set(self.a) == set(self.b)
puzzle = Puzzle()
moveCount = 0
print `puzzle.b`
while not puzzle.isSorted():
x = input("Enter a line to swap: ")
puzzle.swapLine(x)
print "\n" + `puzzle.b`
print "Game over in " + `moveCount` + " moves"
First of all, you must copy the list to make a new one in Puzzle's __init__:
def __init__(self):
self.f = open("file.txt", "r")
self.a = self.f.readlines()
# you must copy the list to make another list
self.b = self.a[:]
random.shuffle(self.b)
In your method:
def isSorted(self):
return set(self.a) == set(self.b)
The condition is always True since you only swap lines, you do not add or delete them. sets do not care for order.
If you want to check for order you can drop the set:
def isSorted(self):
return self.a == self.b
You were very close. Make two changes. 1) Don't use sets, stick with lists because they make sure the order matters whereas sets ignore ordering. 2) Be sure to make a copy of the list using slicing rather than an assignment to b. The current code makes a and b aliases for the same list.
Here's some working code to get you started:
import random
text = '''\
Dog
Fox
Cat
Rat
Jay
'''
class Puzzle:
def __init__(self):
self.a = text.splitlines()
self.b = self.a[:] # <== Fixed: Make a copy using slicing
random.shuffle(self.b)
def swapLine(self, line):
self.b[len(self.b) - 1], self.b[line] = self.b[line], self.b[len(self.b) - 1]
def isSorted(self):
return self.a == self.b # <== Fixed: Don't use sets
puzzle = Puzzle()
moveCount = 0
print `puzzle.b`
while not puzzle.isSorted():
x = random.randrange(5) # <== Made this automatic for testing purposes
puzzle.swapLine(x)
print `puzzle.b`
moveCount += 1 # <== Fixed: Update the move counter
print "Game over in " + `moveCount` + " moves"

Python OOP, using loops to number objects as they are created

I'm stumped on a python problem. I'm writing a program that receives a command from Scratch (MIT) and then should create a new object, in this case named PiLight. The object only need to be created when the command is received so it doesn't have to loop, just be able to executed repeatedly and have the number increment each time it is executed.A list will not work for me due to the requirements of the program and talking between Scratch. I'm trying to figure out a way for the constructor, once initialized, to print out a statement something like
class Newpilight:
def __init__(self):
print "Pilight" + pilnumber + " created"
pilnumber should be 1 for 1st object, 2 for 2nd, etc
From there I need the creation of the object to change the number in the name of the object as well
PiLight(PiLnumber) = Newpilight()
I tried messing around with for loops but just ended up making more of a mess
Use number generator as class variable
from itertools import count
class NewPilight(object):
nums = count()
def __init__(self):
self.num = self.nums.next()
print "Pilight {self.num} created".format(self=self)
Then using in code:
>>> pl1 = NewPilight()
Pilight 0 created
>>> pl2 = NewPilight()
Pilight 1 created
>>> pl3 = NewPilight()
Pilight 2 created
>>> pl3.num
2
The trick is to have the nums (what is actually a generator of numbers, not list of numbers) as class property and not property of class instance. This way it is globally shared by all class instances.
class NewPilight:
def __init__(self, number):
self.number = number
print "Pilight" + number + " created"
for x in range(5):
NewPilight(x)
if you need to keep objects:
all_pilights = []
for x in range(5):
all_pilights.append( NewPilight(x) )
and now you have access to objects as
print all_pilights[0].number
print all_pilights[1].number
print all_pilights[2].number
class NewPiLight(object):
global_pilnumber = 0 # Since this is on the class definition, it is static
def __init__(self):
print "Pilight %s created" % NewPiLight.global_pilnumber
self.pilnumber = NewPiLight.global_pilnumber # Set the variable for this instance
NewPiLight.global_pilnumber += 1 # This increments the static variable

Categories