Problem creating a child class using an alternative constructor of parent - python

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.

Related

Class heritage in Python with dynamically generated method

I've created a module that includes 3 main things:
A main class with a __new__ method and child classes that inherit this one;
A make_method decorator that can associate a method to an existing class:
def make_method(obj):
'''Decorator to make the function a method of *obj*.
In the current context::
#make_method(Axes)
def toto(ax, ...):
...
makes *toto* a method of `Axes`, so that one can directly use:
ax.toto()
COPYRIGHT: from Yannick Copin
'''
def decorate(f):
setattr(obj, f.__name__, f)
return f
return decorate
A generic class.
The main class has a __new__ method that uses the make_method decorator to create a plotting function that will work for any child class. Here is the example:
class MainClass():
FREEPARAMETERS = ['mu_1', 'sigma_1']
PLTALL = ["self.param['" + k + "']" for k in FREEPARAMETERS]
def __new__(cls, *arg, **kwargs):
'''Returns the class with dynamically generated methods'''
obj = super(MainClass, cls).__new__(cls)
exec("#make_method(MainClass)\n" +
"def plot_a(self, x_lin):\n" +
" return self.likelihood_tot(x_lin, %s)"
% (", ".join(MainClass.PLTALL)))
return(obj)
Then, if I create a ChildClass that way:
class ChildClass(MainClass):
FREEPARAMETERS = ['mu_1', 'sigma_1', 'mu_2', 'sigma_2']
PLTALL = ["self.param['" + k + "']" for k in FREEPARAMETERS]
it will have a plot_a method that will only require to have an x_lin without having to enter the parameters by hand (provided a likelihood_tot method that takes the correct parameters).
All of that works very well (it may be a bit of an overkill for what I actually need but it's sweet). Then comes the generic class:
class generic():
'''Usage:
gen = MyModule.generic()
gen.set_model('model')
fitted_model_1 = gen.fit(dataset1)
fitted_model_2 = gen.fit(dataset2)
fitted_model_1.plot_a(x_lin)
fitted_model_2.plot_a(x_lin)'''
def set_model(self, classname):
'''Associates an uninstantiated class to self.model from its string'''
self.model = getattr(sys.modules[__name__], classname)
def fit(self, dataset, **kwargs):
'''Instantiates the class with the pandas,
apply the `minimize` method,
and gives that back as an output'''
model = self.model(dataset)
model.minimize(**kwargs)
return(model)
As stated in its documentation, the idea is to be able to call for a generic model to which I can for example pass different datasets without having to manually instance the class every time. Works fine.
The issue arises when doing the following, where ChildClass1 and ChildClass2 have different FREEPARAMETERS:
gen.set_model('ChildClass1')
fitted_childclass1 = gen.fit(dataset)
gen.set_model('ChildClass2')
fitted_childclass2 = gen.fit(dataset)
fitted_childclass2.plot_a(x_lin)
fitted_childclass1.plot_a(x_lin)
The first plot_a(x_lin), associated with the last call of the generic class, works fine. The second, however, gives me a KeyError: 'mu_2' or a TypeError: likelihood_tot() missing n required positional arguments depending on the relative number of FREEPARAMETERS between the latest instanced class and the one before, which means that somehow, the dynamically generated plot_a of fitted_childclass1 now has the parameters of fitted_childclass2.
Yet, when calling for childclass1.PLTALL or childclass2.PLTALL, I do get the expected result. It would seem that they are not interchanged after instantiation. All methods that are not dynamically generated are correct.
I can of course call the plot_a functions in between each, but that's obviously not the point. I would like both childclass1 and childclass2 to behave as if I instantiated them like:
childclass1 = MyModule.ChildClass1(dataset)
childclass1.minimize()
childclass2 = MyModule.ChildClass2(dataset)
childclass2.minimize()

Unable to understand code -newbie

I am new to python ( started 1 week ago) and this is the first time i am doing coding so i am not able to understand fairly simple things as well.
can you explain this function to to me? i understand that a function is being defined with 2 input required self and my_object, but what is happening next? please explain like you would to a newbie.
class chain():
def __init__(self, my_object):
self.o = my_object
def __getattr__(self, attr):
x = getattr(self.o, attr)
if hasattr(x, '__call__'):
method = x
return lambda *args: self if method(*args) is None else method(*args)
else:
prop = x
return prop
Firstly, chain is not a Function, it's a Class.
A class in simple words is a definition of an object. (say Car)
Now the __init__ function of the class simply defines what's "in it" meaning what variables or properties does it has. Say for example a class Car:
class Car:
def __init__(self,maxspeed,color):
self.speed = maxspeed #So what's defined under **__init__** is a property of a class.
self.color = color
So here Class car has speed and color as variables(or attributes or properties)
Now there are methods , of simply function that control the behaviour of the object and it's functionalities.
class Car:
def __init__(self,maxspeed,color):
self.speed = maxspeed #So what's defined under **__init__** is a property of a class.
self.color = color
def accelarate(self): #Method to increase the speed of car object.
self.sepped = self.speed + 10
Now the method you have is a magical one , __getattr__
Say a scenario where you want to acess the brand of the car , now you haven't define self.brand in it's __init__ function so you you'll get an error when you call it like:
>>>red_car = Car(100,red) #Creating an object named red_car of class Car
>>>red_car.color
>>>'red'
>>>red_car.brand
>>> Attribute Error , Class car dosen't has attribute brand
Now remove this error when calling an undefined property for a object or put simple we tell tell the class what to do if an undefined variable is called we use the method __getattr__.
class Dummy(object):
def __getattr__(self, attr):
return attr.upper()
d = Dummy()
d.does_not_exist # 'DOES_NOT_EXIST'
d.what_about_this_one # 'WHAT_ABOUT_THIS_ONE'
In the above code does_not_exist property (attribute) is NOT define but still we are not getting error as the getattr catches it and does as instructed. In this case it catches attr capitalises it and returns it rather than throwing an error in your face.
The class chain has a constructor that takes an argument my_object and assigns it to an instance variable self.o.
The method __getattr__ is a special magic method that has been overridden to delegate calls to the initial my_object variable we first received.
The result of the delegated call is checked for a method named __call__. If present, it is called and the returned value is returned. If not, the value itself is returned as-is.

Metaclasses and methods

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)

How to reference an existing class object with no defined variable?

I'm trying to use a function of a class object to create a new class object and running into problems. Here's the code I have so far:
class Room(object):
def __init__(self, name):
self.name = name
self.N = None
self.E = None
self.S = None
self.W = None
'''relevant code'''
def north(self,room):
self.N = Room(room)
self.N.S = self
def south(self,room):
self.S = Room(room)
self.S.N = self
So I want at least one of these print statements
room1 = Room('room1')
room1.north('room2')
print(room2.S)
print(Room(room2).S)
print(Room('room2').S)
to spit out 'room1', but the first two don't work because room2 as a variable is yet to be defined, and the last one doesn't work because it seems to be creating a new object instead of referencing the existing one, so it just prints the default 'None'.
Does there actually exist a way to reference an existing object with no variable set to it? Or is my only option to do something like this?
def north(self,room):
roomDict[room] = Room(room)
self.N = roomDict[room]
self.N.S = self
Edit: I realize I should probably be calling the new Room's south() function instead of directly changing the S variable, but that seems intuitively like it would cause a loop so I haven't touched it yet.
* Edited based on OP's clarification *
If you have a large number of objects you want to refer to without binding them to variables, dict is the way to go.
You can use #Berci's solution. But note that with that solution, if you already have a room named foo, you can't overwrite it by simply calling Room('foo') again -- doing that will just return the original foo room. To overwrite an existing room you must first do del Room.roomDict['foo'], and then call Room('foo'). This may be something you want, but maybe not.
The implementation below is less fanciful and doesn't require __new__ (in fact, Berci's solution doesn't need __new__ either and can be all done in __init__):
class Room:
registry = {}
def __init__(self, name):
self.registry[name] = self
# the rest of your __init__ code
If you want rooms to be non-overwritable, as they are in Berci's solution, just add two lines:
class Room:
registry = {}
def __init__(self, name):
if name in self.registry:
raise ValueError('room named "{}" already exists'.format(name))
self.registry[name] = self
It's not necessary to nest registry inside Room. You can make it an external dict if you want. The advantage of having the registry as a class attribute is that your Room object can access it as self.registry without knowing its global name. The (slight) disadvantage is that you need to type Room.registry or someroom.registry instead of just, say, registry, every time you access it.
Your dict solution can be brought to work. Use a class level roomDict and a new constructor not to create an already existing object referred by its name:
class Room(object):
roomDict = {}
def __new__(cls, name):
if name in cls.roomDict:
return cls.roomDict[name]
self = object.__new__(cls, name) # here the object is created
cls.roomDict[name] = self
return self
def __init__(self, name):
...
So that you can refer to room2 as Room('room2') afterwards.

Understanding class-wide variables that should be tied to object instances

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.

Categories