Here is a class that assigns a symbol to a player. It should accept a move and add the move to the existing repository of moves of the player.
class Player:
...: positions = []
...: def __init__(self,symbol):
...: self.symbol = symbol
...: def move(self,position):
...: self.position = position
...: self.positions.append(self.position)
My problem is that positions is behaving "globally" in the sense that it is not tied to an object instance, to demonstrate:
>>>a = Player('x')
>>>b = Player('y')
>>>a.move(1)
>>>b.positions
[1]
When you say,
class Player:
positions = []
positions will be a class variable and the same object is used by all the instances of the class. You can confirm by this
player1, player2 = Player(), Player()
print player1.positions is player2.positions # True
print Player.positions is player1.positions # True
If you want to create instance variables (separate positions variable for each and every instance of Player), you can create that in __init__ function. It is a special initializer function, which gets the current actual object as the first parameter. You can create positions variable and attach it to that object like this
class Player:
def __init__(self):
self.positions = []
player1, player2 = Player(), Player()
print player1.positions is player2.positions # False
Here, self refers to the newly constructed object and you are creating a new variable in that object by self.positions and you are initializing it with an empty list by
self.positions = []
So, whenever you create a new instances of Player, self will refer to the new instance created and new variable positions will be created on every instance, which means separate positions variable for each and every instance.
And whenever move is called, you don't have to create a new position variable on self. Instead you can do this
def move(self, position):
self.positions.append(position)
If you are using Python 2.x, its better to use new style classes, like this
class Player(object):
def __init__(self):
self.positions = []
Declare it inside the __init__ method. Anyhting declared outside the __init__ method will be a class attribute and will be shared between all instances of the class.
You don't have to pass it anything:
class Player:
...: def __init__(self,symbol):
...: self.symbol = symbol
self.positions = []
...: def move(self,position):
...: self.position = position
...: self.positions.append(self.position)
Yes you need to make class variable into instance variable, which will bind only to a particular instance.
Currently, positions = [] is class variable which can access from all the instances. So, you better assign to a particular instance variable.
You can do that by define inside __init__(), which will call when you create an instance of that class.
IMO you'd also want to initialize self.position to None in init, lest you hit this error:
p = Player()
p.position
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Player instance has no attribute 'position'
So you'd want this:
class Player(object): # making it a new-style class
def __init__(self, ...):
...
self.position = None
My problem is that positions is behaving "globally" in the sense that it is not tied to an object instance, to demonstrate
Yes, that is by design of the language. Python follows a rule that is not like some other modern languages with OO support, but is very simple and easily understood: everything you write inside the class block describes a part of the class. (Remember, in Python, everything is an object - including the classes themselves.)
So I do not really understand what the problem is. You clearly already know how to make things that belong to the instances (hint: you are already doing it with symbol). You may just need to re-think your design and be clearer about what you intend to happen.
Related
I have a class that creates blocks in a 2d board. I defined it so it does so given their height, length and location on the board but also made an alternative constructor to create blocks by passing its coordinates in the board.
class Block:
""" Create a block of given height and length in a starting location."""
def __init__(self, name: str, h: int, l: int, location: Tuple[int,int]):
self.name = name
self.coords = [tuple_sum(t1=location, t2=(i//l , i%l)) for i in range(h*l)]
# tuple_sum does (a, b)+(c, d) -> (a+c, b+d)
#classmethod
def from_coords(cls, name: str, coords: List[Tuple]):
block = cls.__new__(cls)
block.name = name
block.coords = coords
return block
...
def __str__(self) -> str:
return f'Name: {self.name}\nCoords: {self.coords}'
I'm trying to create a child class for the blank spaces in the board. I thought that using the from_coords constructor would do everything but for some reason I don't understand the created elements are not initialized, i.e don't have name or coords attributes.
class SpacesBlock(Block):
""" Make a block of spaces from a list of coordenates """
def __init__(self, coords):
super().from_coords(" ",coords)
...
spaces = SpacesBlock([(0,0),(0,1)])
print(spaces)
AttributeError Traceback (most recent call last)
\block.py in <module>
space = SpacesBlock([(0,0),(0,1)])
----> print(spaces)
AttributeError: 'SpacesBlock' object has no attribute 'name'
I thought it was the from_coords constructor but it works fine
a = Block.from_coords("A",[(0,0),(1,0)])
print(a)
Name: A
Coords: [(0, 0), (1, 0)]
I know I can just define the name and coords in the init of SpacesBlock and everything is fine but I am very curious about why it doesn't work as I thought it would. What am I missing?
from_coords is not a constructor. It is a factory method.
__init__ actually initializes an already-existing (i.e., allocated) object. Because it's an ordinary method, receiving a self parameter, it is able to do so. A classmethod cannot initialize an already-existing object unless you provide it one explicitly.
What happens in your code is that super().from_coords(" ",coords) creates a separate instance of Block, which is then discarded. The self instance of SpacesBlock doesn't get its .name set; that happened to the other instance.
The point of #classmethod (as opposed to #staticmethod) is that, because you receive a parameter which is the class, it can still behave polymorphically even though you don't have an object instance. (As you've found, you can use this to make __new__ work polymorphically.)
Your factory method can already behave polymorphically: SpacesBlock.from_coords will call the method, and pass SpacesBlock as the cls, so that cls.__new__ creates a new SpacesBlock instance. However, __init__ doesn't get called this way; and with your current organizational structure, it's not clear how you would call it or what you would pass.
Real justified uses for __new__ are rare.
The normal way to use factory methods is to have them call __init__, by determining parameters to use for the __init__ call. In your case, a list of coordinates could be whatever arbitrary positions; but a width, height and location give you a way to specify multiple coordinates. It would be easier, therefore, to use the actual constructor for the list-of-coordinates approach to construction.
With that setup, the code looks something like:
class Block:
def __init__(self, name: str, coords: List[Tuple]):
self.name = name
self.coords = coords
#classmethod
def from_grid(cls, name: str, h: int, l: int, location: Tuple[int,int]):
coords = [tuple_sum(location, (i//l , i%l)) for i in range(h*l)]
return cls(name, coords)
class SpacesBlock(Block):
pass # thus far, doesn't actually do anything different
The important thing to note here is that from_grid can be used for either class now. Block.from_grid creates a Block instance, and SpacesBlock.from_grid creates a SpacesBlock instance - in either case, using the (height, length, location) approach. To create either class from a list of coordinates directly, simply call the constructor directly.
When you execute
spaces = SpacesBlock([(0,0),(0,1)])
you create an instance SpaceBlock. Then __init__ runs to initialize this object. The problem is, that the __init__ function in SpaceBlock does not modify the instance you have just created. Instead, it creates and instantiates another object. This new object is not used for anything, and the one you originally created it left without any modifications, in particular without the name and coords attributes.
You could fix it by modifying __init__ e.g. as follows:
def __init__(self, coords):
x = super().from_coords(" ",coords)
self.name = x.name
self.coords = x.coords
but this would be an unnecessarily convoluted code.
I'm working on a game using python.
The AI in the game uses variables that the player has, and vice versa.
For an example:
class Player():
def __init__(self, canvas...):
self.id = canvas.create_rectangle(...)
...
def touching_AI(self):
aipos = canvas.coords(AI object)
pos = canvas.coords(self.id)
...
#the function above checks if the player is touching the AI if it
#is, then call other functions
this = player(canvas...)
class AI():
def __init__(self, canvas...):
self.id = canvas.create_rectangle(...)
def chase_player(self):
playerpos = canvas.coords(this.id)
pos = canvas.coords(self.id)
...
# a lot of code that isn't important
Obviously, Python says that the AI object in the player class isn't defined. Both classes depend on the other to work. However, one isn't defined yet, so if I put one before the other, it returns an error. While there is probably a workaround for these two functions only, there are more functions that I didn't mention.
In summary, is there a way (pythonic or non-pythonic) to use and/or define an object before it is created (i.e even making more files)?
you do not
instead use arguments
class Player():
def __init__(self, canvas...):
self.id = canvas.create_rectangle(...)
...
def touching(self,other):
aipos = canvas.coords(other.object)
pos = canvas.coords(self.id)
...
#the function above checks if the player is touching the AI if it
#is, then call other functions
class AI():
def __init__(self, canvas...):
self.id = canvas.create_rectangle(...)
def chase(self,player):
playerpos = canvas.coords(player.id)
pos = canvas.coords(self.id)
then
player = Player(canvas...)
ai = AI(...)
ai.chase(player)
player.touching(ai)
but even better is to define a base object type that defines your interface
class BaseGameOb:
position = [0,0]
def distance(self,other):
return distance(self.position,other.position)
class BaseGameMob(BaseGameOb):
def chase(self,something):
self.target = something
def touching(self,other):
return True or False
then all your things inherit from this
class Player(BaseGameMob):
... things specific to Player
class AI(BaseGameMob):
... things specific to AI
class Rat(AI):
... things specific to a Rat type AI
You do not have a dependency cycle problem. But, you have the following problem,
You are trying it use an AI object, but you did not create the object anywhere. It needs to look like,
foo = AI() #creating the object
bar(foo) #using the object
The syntax is wrong around canvas.coords(AI object).
The way to call a function is foo(obj) without the type.
When defining a function you can optionally mention the type like def foo(bar : 'AI'):
The proof you can depend classes on each other, https://pyfiddle.io/fiddle/b75f2de0-2956-472d-abcf-75a627e77204/
You can initialize one without specifying the type and assign it in afterwards. Python kind of pretends everyone are grown-ups so..
e.g.:
class A:
def __init__(self, val):
self.val = val
self.b = None
class B:
def __init__(self, a_val):
self.a = A(a_val)
a_val = 1
b = B(1)
a = b.a
a.b = b
Adding a method to metaclass works perfectly in the below example.
class Test(object):
def __init__(self, x):
self.x = x
def double(self):
return self.x*2
# method to add
def quadruple(self):
return self.x*4
# creating metaclass
TypeTest = type('TypeTest', (Test,), {'triple': triple,
'quadruple': quadruple})
# prints 8
TypeTest(2).quadruple()
The below example doesn't work and I have no idea why. It simply doesn't recognise self in the parsed function and a TypeError occurs.
class Vehicle(object):
def __init__(self, wheels, door=False):
self.wheels = wheels
self.door = door
# method to add
def check_load(self, x):
if x > self.load:
return "Load won't fit"
else:
return "Load will fit"
# creating metaclass
Truck = type('Truck', (Vehicle,), dict(wheels=4,door=True, load=100,
check_load=check_load))
# TypeError: check_load() missing 1 required positional argument: 'x'
Truck.check_load(10)
First of all: You are not creating a metaclass, you are creating regular classes. type() is the (base) metaclass here, calling it creates a new class object (the same type of object that a class statement produces).
The first type() call is essentially equivalent to:
class TypeTest(Test)
triple = triple
quadruple = quadruple
and the second example is the same as:
class Truck(Vehicle)
wheels = 4
door = True
load = 100
check_load = check_load
You forgot to create an instance of your Truck class:
Truck.check_load(10)
This leaves the check_load() function with nothing to bind to, there is no self.
In your first example you did create an instance:
TypeTest(2).quadruple()
Notice the call, passing in 2.
Create an instance for self to be bound to:
Truck(4, True).check_load(10)
If you wanted your class to not need arguments to create an instance, you'll need to provide a different __init__ method too, one that overrides the Vehicle.__init__ method:
def init(self): pass
Truck = type('Truck', (Vehicle,), dict(
wheels=4,door=True, load=100,
check_load=check_load, __init__=init))
Now you can create the instance without arguments:
Truck().check_load(10)
I have just started an exercise where I am supposed to complete a basic 'angrybirds' clone.
I am stuck at the point where I want to remove an object from a list. The list contains all of the obstacles used in-game (boxes).
So if I want to remove a box after it was hit I have to make a method to do that. This fails no matter how I do it.
class spel(object):
def __init__(self):
self.obstacles = [obstacle(50,pos=(200,90)),]
#defines all other stuff of the game
class obstacle(object):
def __init__(self,size,pos):
#defines how it looks like
def break(self):
#methode that defines what happens when the obstacles gets destroyed
spel.obstacles.remove(self)
The error I get is:
AttributeError: 'NoneType' object has no attribute 'obstacles'
After the last line.
Please excuse me for my noob-level, but the point is that I won't ever have to code again after this, so there is no need to explain everything.
You have defined 'spel' as a class, not an object. Thus, you have received an error because Python is trying to find a member 'obstacles' of the spel class, which doesn't exist before the __init__ method of individual spel objects is run.
To associate an object of the spel class with each individual obstacle you create, you could try giving objects of the obstacle class a data member that refers to their associated spel object. The data member can be instantiated in the obstacle class' __init__ function. Like this:
class obstacle(object):
def __init__(self, spel, size, pos):
self.spel = spel
#etc
def break(self):
self.spel.obstacles.remove(self)
Hope that helps.
You haven't instantiated the spel class.
If you want to use a class like that, you have to intantiate( create an instance of) it.
Outside of a class like so:
app = spel() # app is an arbitrary name, could be anything
then you would call it's method like this:
app.obstacles.remove(self)
Or in you're case, from within another class:
self.spel = spel()
self.spel.obstacles.remove(self)
I propose the following:
class spel(object):
obstacles = []
def __init__(self,size,pos):
spel.obstacles.append(obstacle(size,pos))
#defines all other stuff of the game
class obstacle(object):
def __init__(self,size,pos):
self.size = size
self.pos = pos
def brak(self):
#methode that defines what happens when the obstacles gets destroyed
spel.obstacles.remove(self)
from pprint import pprint
a = spel(50,(200,90))
pprint( spel.obstacles)
print
b = spel(5,(10,20))
pprint( spel.obstacles )
print
c = spel(3,None)
pprint( spel.obstacles )
print
spel.obstacles[0].brak()
pprint( spel.obstacles )
return
[<__main__.obstacle object at 0x011E0A30>]
[<__main__.obstacle object at 0x011E0A30>,
<__main__.obstacle object at 0x011E0B30>]
[<__main__.obstacle object at 0x011E0A30>,
<__main__.obstacle object at 0x011E0B30>,
<__main__.obstacle object at 0x011E0AF0>]
[<__main__.obstacle object at 0x011E0B30>,
<__main__.obstacle object at 0x011E0AF0>]
I'm new to python, and it really confused me.
I want to write something like
class line :
points = []
def add(self, point) :
self.points.append(point)
line1 = line()
line2 = line()
line1.add("Some point")
print line2.points
Output: ['Some point']
And the result is like they refers to the same list.
But I do want a object member not a class member.
And I tried, if points is int, or some other simple type it works fine.
Besides, I know if I write
def __init__(self) :
self.points = []
it will also work, but I don't get why they are pointing to same list by default.
ps. I know writing a init will work.
class line :
name = "abc"
def changename(self, name) :
self.name = name
line1 = line()
line2 = line()
line1.changename(123)
print line2.name
But the code above output "abc"
So I don't understand why same kind of declaration act different by type.
Because in this case
class line :
points = []
points is a class member. It belongs to the class, and since line1 and line2 are of the same class, points is the same list.
So you are right in using
def __init__(self) :
self.points = []
instead to create an instance member which is tied to the instance, not the class.
To answer your edit
If you write:
def changename(self, name) :
self.name = name
you override your class member and create a new instance member using self.name = name.
To get back to your first example, it would be like writing:
def add(self, point) :
self.points = self.points + [point]
The first syntax:
class line :
points = []
makes points a class attribute. Therefore, it will be shared between all instances of the class.
To make sure that an attribute belongs to a class, you should do as follows:
class line(object) :
def __init__(self):
self.points = []
i.e., creating the attributed only in __init__, when a new instance is created.
As others have already explained, and as you apparently already understand, the following creates a class attribute, shared by every instance:
class line:
points = []
When you reference this attribute in your code, Python attempts to find it the current scope, and then follows the enclosing scopes. So if you call self.points.append(), you are indeed changing the points class attribute.
When you assign to self.points in your code, you are defining a new points instance attribute. So in your second example when points is a string, the changename function actually creates a new instance attribute when called.
You can try the following:
print line.name # prints the class attribute
print line1.name # prints the class attribute
print line2.name # prints the instance attribute
You will notice that calling changename did create a new instance attribute, while the class attribute is left unchanged. As changename was never called on line1, a reference to its name will resolve to the line class attribute.