Function to set properties of an object of a class composition - python

I would like construct a class composition that includes a function set_props for setting the instance variables of components.
The application for this is in defining new objects for drawing in matplotlib. One example is that I would like to have a function drawMyArrow that draws an arrow with possibly different colors (and other specifications) for its head, tail, and arc. I would like to be able to pass various specifications for the head, tail, and arc via keyword arguments in drawMyArrow. I haven't worked with classes before, but reading up on this online, I believe that the best way to solve my problem is to define a class MyArrow that is a composition of some classes ArrowHead and ArrowArc.
To illustrate my problem, I am using a toy example (that I also used for a previous question here). Let's define a class Room that is a composition of the classes wall, window, and door.
class Door:
def __init__(self, color='white', height=2.3, width=1.0, locked=True):
self.color = color
self.height = height
self.width = width
self.locked=locked
class Window:
def __init__(self, color='white', height=1.0, width=0.8):
self.color = color
self.height = height
self.width = width
class Wall:
def __init__(self, color='white', height=2.5, width=4.0):
self.color = color
self.height = height
self.width = width
class Room:
def __init__(self):
self.door = Door()
self.window = Window()
self.wall = Wall()
If I want to have a function for Room that can set properties of its components, I can do something like this:
def set_windowprops(r, color=None, width=None, height=None):
if not color==None: r.window.color=color
if not width==None: r.window.widht=width
if not height==None: r.window.height=height
return r
But if I decide to add more instance variables to Window, I would have to go back to this function and add the new instance variables. Can I write set_windowprops so that it automatically accepts all instance variables of Window as keywords?
Ideally, I would like to write a function like this:
def set_props(r, windowcolor=None, windowwidth=None, windowheight=None,
doorcolor=None, doorwidth=None, doorheight=None, doorlocked=None,
wallcolor=None, wallwidth=None, wallheight=None):
if not windowcolor==None: r.window.color=windowcolor
if not windowwidth==None: r.window.widht=windowwidth
if not windowheight==None: r.window.height=windowheight
if not doorcolor==None: r.door.color=doorcolor
if not doorwidth==None: r.door.widht=doorwidth
if not doorheight==None: r.door.height=dooorheight
if not doorlocked==None: r.door.locked=doorlocked
if not wallcolor==None: r.wall.color=wallcolor
if not wallwidth==None: r.wall.widht=wallwidth
if not wallheight==None: r.wall.height=wallheight
return r
but without the need of hardcoding all instance variables of components into the function.
I was looking into using keyword dictionaries like so:
window_vars = getNamesOfInstanceVariables(Window) #TODO
window_kwargs = {}
for v in window_vars:
window_kwargs[v] = None
def set_windowprops(r, **kwargs):
for kw in kwargs:
if not kwargs[kw]==None:
r["window"][kw] = kwargs[kw] #TODO
return r
Two issues keep me from getting this to work:
(1) In the last line, I am pretending that r is a dictionary (of dictionaries) and using dictionary syntax to assign a value to r.window.kw. But that doesn't work because r is an instance of Room and not a dictionary. What would be the syntax for setting instance variables if the name of the component class and the name of the instance variable are given as strings?
(2) I have tried using inspect to write getNamesOfInstanceVariables, but I am unable to get it to work robustly. Many classes in matplotlib inherit from base classes. I would like getNamesOfInstanceVariables to return all instance variables that a user can set for an object of this class. For example, the class FancyArrow in matplotlib has Patch as base class and instance variables head_length and head_width. So I would getNamesOfInstanceVariables(FancyArrow) to return ['head_length','head_width', *listOfInstanceVarsForPatch].
EDIT
Let me add a bit of background on why I am asking for a dynamical way to write these functions. Let's say I have finished my script and it includes the classes Window, Door, Wall and many class compositions of these three. One of these many class compositions is Room. This class Room has ten hardcoded set_ functions that look like this:
def set_windowcolor(r, color):
r.window.color = color
return r
I now decide that I want to add another instance variable to the class Window. For example,
class Window:
def __init__(self, color='white', height=1.0, width=0.8, open=False):
self.color = color
self.height = height
self.width = width
self.open = open # new attribute of Window
Similar to all the other instance variables of window, this new attribute of Window should be customizable in all classe compositions that contain a Window. So I would go through my code, find the class Room and add a function
def set_windowopen(r, open):
r.window.open = open
return r
I would also have to look for all other class compositions that contain a Window and update them manually as well. I don't want to do this because it is a lot of work and I am likely going to overlook some class dependencies in the process and introduce bugs into my code. I am looking for a solution that either
generates set functions for single properties (e.g. set_windowcolor) automatically in Room for all instance variables of Window or
automatically adjusts the list of keyword arguments in set_windowprops or set_props.

Here is what I would do
class Room:
def __init__(self, kw_door=None, kw_window=None, kw_wall=None):
if kw_door:
self.door = Door(**kw_door)
else:
self.door = Door()
if kw_window:
self.window = Window(**kw_window)
else:
self.window = Window()
if kw_wall:
self.wall = Wall(**kw_wall)
else:
self.wall = Wall()
effectively you are accepting a dictionary that will be unpacked into the instance creation, and when the class definition gets new attributes, they too will be unpacked if they are found in the passed dictionary.

To answer you first question. If you want to set window attribute of r and kw attribute of that `windows as in your example, you cat do the following:
setattr(getattr(r, "window"), kw, kwargs[kw])
You get attribute named window of r. And for that windows attributes set its own whose name is in variable kw to a new value from kwargs[kw]. As your line has attempted.
But to be perfectly honest, why a function with many possible arguments would be preferred over just accessing/setting the attributes themselves? Or in other words, on the face of it, it looks more complicated then it needs to be.
As for you second question. You should be also able to just use dir() to get list of instance attributes, but yes, inherited attributes will be getting in your way and I am not immediately sure if there is an elegant way around it (i.e. not walk the inheritance tree and do the pruning yourself). But if you genuinely wanted to dynamically walk attributes of an instance that are subject to for instance setting through a function, I'd say add a (class) attribute defining what those are to be for each type rely on that as a defined interface.
side note: There is only one instance of None (and True and False) and it can be tested for identity (not just equality) and since it reads a little better you would normally see if somevar is not None instead of if not somevar == None.

Related

Python design question: When dependencies can't be instantiated ahead of time

Sorry for the poor title but if I knew the class of problems I was tackling perhaps I could find an answer.
In an ideal situation, one creates an object with all its needed dependencies, and then uses the object. However in some cases, the information needed to create a dependency is not known before the object is used.
I will try to create a concrete example that has to do with making a house. The following will be the dependencies
class WallMakerAlgorithm(abc.ABC):
def build(self):
# Put specific algorithm here that will build the wall
pass
class DoorMakerAlgorithm(abc.ABC):
def __init__(self, color: float, material: str):
# subclasses can override init to add more fancy stuff
self.color = color
self.material = material
def build(self):
# Put specific algorithm here that will build the door
pass
These are used by a house builder class. In the ideal case, a HouseBuilder will know its dependencies on init:
class BasicHouseBuilder:
def __init__(self, door_maker_algorithm: DoorMakerAlgorithm, wall_maker_algorithm: WallMakerAlgorithm):
self.door_maker_algorithm = door_maker_algorithm
self.wall_maker_algorithm = wall_maker_algorithm
def build(self):
self.wall_maker_algorithm.build()
self.door_maker_algorithm.build()
But now assume that for some (contrived) reason, the customer hasn't decided on the color of the doors. They tell you that once the wall is built, they will know the color because they will be able to see it. This creates a chicken and egg kind of problem. I can't create the HouseBuilder because I can't create the door making algorithm, and I can't create the door making algorithm without actually running HouseBuilder. Something like this:
class DecideColorLaterHouseBuilder:
def __self__(self, wall_maker_algorithm: WallMakerAlgorithm, query_color: Callbale, ???):
self.wall_maker_algorithm = wall_maker_algorithm
self.query_color = query_color
???
def build(self):
self.wall_maker_algorithm.build()
desired_door_color = self.query_color()
# Now I want to build the door. But I don't have the door algorithm because I don't know the color!
Is there some design pattern that deals with this kind of problem? Are my abstractions just messed up in general? I can potentially add color as an argument to build of the DoorMakingAlgorithm but that just messes up my abstractions.
You could use default values for the arguments of your functions.
class DoorMakerAlgorithm(abc.ABC):
def __init__(self, material: str, color: float=0xffffff):
# arguments with default values (optional arguments) must come after mandatory ones
# Here I put white color, but you could put any other color, or even None
# subclasses can override init to add more fancy stuff
self.color = color
self.material = material
Using None as a default value can allow you to through an exception in case the user still hasn't define the color when you build
class DoorMakerAlgorithm(abc.ABC):
def __init__(self, material: str, color: float=None):
self.color = color
self.material = material
def build(self):
if self.color is None:
raise Exception(f"Color hasn't been defined yet for {self}")
# Then proceed to build the wall
You can also use a property so that the user can change the color after instanciation while you still check the input. Note that in python, the user can always access any attribute of a class and modify them. The property allows you to secure the input, but the user can still modify the _color attribute and put anything in there. the _ is a convention for attributes the end user shouldn't mess with.
class DoorMakerAlgorithm(abc.ABC):
def __init__(self, material: str, color: float=None):
self.color = color
self.material = material
# You can use a property to allow the user to change the color later, and ensure input is correct
#property
def color(self):
return self._color
#color.setter
def color(self, color: float=None):
self._color = color
def build(self):
if self.color is None:
raise Exception(f"Color hasn't been defined yet for {self}")
# Then proceed to build the wall
algo = DoorMakerAlgorithm("bricks") # not giving a color
# algo.build() # would through an exception
algo.color = 0xffffff
algo.build() # works

Changing class attributes by reference

I'm relatively new to Python and have problems with immutable variables.
I'm trying to change the value of a class attribute (e.g. car.color). The difficulty is, that I can not use the namespace of car for doing this.
Up to now I did not find a satisvying answer to my questions. In the code below I tried to summarize the possible solutions (workarrounds) I found and their disadvantages:
class Car:
def __init__(self):
self.color = "green"
self.color_list = ["green"]
self.color_attrib = "green"
self.name = "VW Golf"
"""
and many more attributes...
"""
def makesetter(self, attribute):
def set_value(value):
attribute=value
return set_value
def set_color(self, value):
"in this function I directly have access to car.color and can change its value: "
self.color = value
def set_attrib(self, attribute_string, value):
setattr(self,attribute_string,value)
def change_attribute(attribute, value):
"In this function I can not access car.color directly"
attribute=value
def change_attribute_list(attribute, value):
"In this function I can not access car.color directly"
attribute[0] = value
if __name__ == "__main__":
car1 = Car()
change_attribute(car1.color, "red")
print(car1.color) # Color does not change because car1.color is immutable
g = car1.makesetter(car1.color)
g("red")
print(car1.color) # Color does not change because car1.color is immutable
change_attribute_list(car1.color_list, "red")
print(car1.color_list) # Color changes but seems like a workarround
# Disadvantage: in the namespace of car1, the user has to use a list to access a string value "car1.color_list[0]"
car1.set_color("red")
print(car1.color) # Color changes but seems like a workarround
# Disadvantage: Car needs a setter function for each attribute
car1.set_attrib("color_attrib","red")
print(car1.color_attrib) # Color changes but seems like a workarround
# Disadvantage: Attribute has to be passed as string & no auto completion while coding
Actually the function setattr() is internally exactly doing what I want. But it works with a string argument.
I tried to look into this function but it seems to be written in C++.
So do I have to use C++ to solve this problem without a workarround?
Or is there a Pythionic way of doing this?
The problem is you are trying to redefine the value of an instance from outside of the class. Since in __init__ you are defining your variables with self, they are only available for that instance. This is the point of a class - it's what makes them extensible and reusable.
Ideally, you would make a method within the class that would update those attributes, however, if you really need to update the class from an external function, you will have to define it as a class level variable. For instance:
class Car:
def __init__(self):
Car.color = "green"
can now be updated using:
def change_attribute(attribute, value):
"In this function I can not access car.color directly"
Car.color=value
outside of the class because you have not assigned it to one specific instance. Doing this presents a problem, however. Since we don't have a separate instance variable, if we try to re-instantiate the class, we are stuck with what was previously changed, i.e.
if name == "main":
car1 = Car()
car2 = Car()
change_attribute(car1.color, "red")
print(car1.color) # Prints red
print(car2.color) # Prints red
change_attribute(car2.color, "blue")
print(car1.color) # Prints blue
print(car2.color) # Prints blue
This is why classes themselves should be self contained and are meant to be immutable - the instance itself should be changed.

Why doesn't imported class attribute change in Python?

I am trying to understand the mechanism of __import__(fromlist=['MyClass']).
Imagine I have several classes WhiteBox:
class WhiteBox:
def __init__(self):
self.name = "White Box"
self.color = None
def use(self, color):
self.paint(color)
def paint(self, color):
self.color = color
I am importing these classes with __import__(fromlist=['WhiteBox']) statement.
I decide to repaint all boxes with the same color and create a loop:
for box in imported_boxes:
box.WhiteBox().use = "green"
print("REPAINTED:", box.WhiteBox().name, box.WhiteBox().color)
When I try to access box.WhiteBox().color attribute, I still get None.
REPAINTED: WhiteBox None
I expected that __import__ would allow to manipulate the object as if it was instantiated, it appears not true. How do I solve this issue?
Use are using "use" as a property but it it defined as a function:
box = box.WhiteBox() = "green"
#change it to:
box.WhiteBox().use("green")
Next problem:
You are creating WhiteBox again and again so it will always have the initial None Value...
box.WhiteBox().use("green") #created once
print("REPAINTED:", box.WhiteBox().name, box.WhiteBox().color) #two more times...

Using a class instance as a class attribute, descriptors, and properties

I have recently stated trying to use the newer style of classes in Python (those derived from object). As an excersise to familiarise myself with them I am trying to define a class which has a number of class instances as attributes, with each of these class instances describing a different type of data, e.g. 1d lists, 2d arrays, scalars etc. Essentially I wish to be able to write
some_class.data_type.some_variable
where data_type is a class instance describing a collection of variables. Below is my first attempt at implementing this, using just a profiles_1d instance and rather generic names:
class profiles_1d(object):
def __init__(self, x, y1=None, y2=None, y3=None):
self.x = x
self.y1 = y1
self.y2 = y2
self.y3 = y3
class collection(object):
def __init__(self):
self._profiles_1d = None
def get_profiles(self):
return self._profiles_1d
def set_profiles(self, x, *args, **kwargs):
self._profiles_1d = profiles_1d(x, *args, **kwargs)
def del_profiles(self):
self._profiles_1d = None
profiles1d = property(fget=get_profiles, fset=set_profiles, fdel=del_profiles,
doc="One dimensional profiles")
Is the above code roughly an appropriate way of tackling this problem. The examples I have seen of using property just set the value of some variable. Here I require my set method to initialise an instance of some class. If not, any other suggestions of better ways to implement this would be greatly appreciated.
In addition, is the way I am defining my set method ok? Generally the set method, as far as I understand, defines what to do when the user types, in this example,
collection.profiles1d = ...
The only way I can correctly set the attributes of the profiles_1d instance with the above code is to type collection.set_profiles([...], y1=[...], ...), but I think that I shouldn't be directly calling this method. Ideally I would want to type collection.profiles = ([...], y1=[...], ...): is this correct/possible?
Finally, I have seen a decorators mentioned alot with repect to the new style of classes, but this is something I know very little about. Is the use of decorators appropriate here? Is this something I should know more about for this problem?
First, it's good you're learning new-style classes. They've got lots of advantages.
The modern way to make properties in Python is:
class Collection(object):
def __init__(self):
self._profiles_1d = None
#property
def profiles(self):
"""One dimensional profiles"""
return self._profiles_1d
#profiles.setter
def profiles(self, argtuple):
args, kwargs = argtuple
self._profiles_1d = profiles_1d(*args, **kwargs)
#profiles.deleter
def profiles(self):
self._profiles_1d = None
then set profiles by doing
collection = Collection()
collection.profiles = (arg1, arg2, arg3), {'kwarg1':val1, 'kwarg2':val2}
Notice all three methods having the same name.
This is not normally done; either have them pass the attributes to collections constructor or have them create the profiles_1d themselves and then do collections.profiles = myprofiles1d or pass it to the constructor.
When you want the attribute to manage access to itself instead of the class managing access to the attribute, make the attribute a class with a descriptor. Do this if, unlike in the property example above, you actually want the data stored inside the attribute (instead of another, faux-private instance variable). Also, it's good for if you're going to use the same property over and over again -- make it a descriptor and you don't need to write the code multiple times or use a base class.
I actually like the page by #S.Lott -- Building Skills in Python's Attributes, Properties and Descriptors.
When creating propertys (or other descriptors) that need to call other instance methods the naming convention is to prepend an _ to those methods; so your names above would be _get_profiles, _set_profiles, and _del_profiles.
In Python 2.6+ each property is also a decorator, so you don't have to create the (otherwise useless) _name methods:
#property
def test(self):
return self._test
#test.setter
def test(self, newvalue):
# validate newvalue if necessary
self._test = newvalue
#test.deleter
def test(self):
del self._test
It looks like your code is trying to set profiles on the class instead of instances -- if this is so, properties on the class won't work as collections.profiles would be overridden with a profiles_1d object, clobbering the property... if this is really what you want, you'll have to make a metaclass and put the property there instead.
Hopefully you are talking about instances, so the class would look like:
class Collection(object): # notice the capital C in Collection
def __init__(self):
self._profiles_1d = None
#property
def profiles1d(self):
"One dimensional profiles"
return self._profiles_1d
#profiles1d.setter
def profiles1d(self, value):
self._profiles_1d = profiles_1d(*value)
#profiles1d.deleter
def profiles1d(self):
del self._profiles_1d
and then you would do something like:
collection = Collection()
collection.profiles1d = x, y1, y2, y3
A couple things to note: the setter method gets called with only two items: self, and the new value (which is why you were having to call set_profiles1d manually); when doing an assignment, keyword naming is not an option (that only works in function calls, which an assignment is not). If it makes sense for you, you can get fancy and do something like:
collection.profiles1d = (x, dict(y1=y1, y2=y2, y3=y3))
and then change the setter to:
#profiles1d.setter
def profiles1d(self, value):
x, y = value
self._profiles_1d = profiles_1d(x, **y)
which is still fairly readable (although I prefer the x, y1, y2, y3 version myself).

Inheritance : transform a base class instance to a child class instance

I have an instance of a base class, and then I want to make it an instance of a child class of this base class. Maybe I'm taking the problem in a wrong way and there's something important I didn't understand in OOP. Code is only there to illustrate and a very different approach can be suggested. Any help appreciated.
class Car(object):
def __init__(self, color):
self.color = color
def drive(self):
print "Driving at 50 mph"
class FastCar(Car):
def __init__(self, color, max_speed=100):
Car.__init__(self, color)
self.max_speed = max_speed
def drive_fast(self):
print "Driving at %s mph" %self.max_speed
one_car = Car('blue')
# After the instanciation, I discovered that one_car is not just a classic car
# but also a fast one which can drive at 120 mph.
# So I want to make one_car a FastCar instance.
I see a very similar question, but none of the answers suits my problem :
I don't want to make FastCar a wrapper around Car which would know how to drive fast : I really want that FastCar extends Car ;
I don't really want to use the __new__ method in FastCar to make some tests on the arguments and decide if __new__ has to return a new instance of Car or the instance I gave to it (example: def __new__(cls, color, max_speed=100, baseclassinstance=None)).
class FastCar(Car):
def __init__(self, color, max_speed=100):
Car.__init__(self, color)
self.max_speed = max_speed
def drive_fast(self):
print "Driving at %s mph" %self.max_speed
#staticmethod
def fromOtherCar(car):
return FastCar(car.color)
actually_fast = FastCar.fromOtherCar(thought_was_classic)
This is the standard way.
Depending on the real class layout, you may be able to do something like:
classic = Car('blue')
classic.__class__ = FastCar
classic.__dict__.update(FastCar(classic.color).__dict__)
classic.drive_fast()
But I wouldn't recommend it -- it's a hack, it won't always work, and the other way is cleaner.
Edit: Was just about to add basically what #PaulMcGuire's comment says. Follow that advice, he's right.
You can borrow the C++ notion of a "copy constructor" to do something like this.
Allow Car's constructor to take a Car instance, and copy all of its properties. FastCar should then accept either Car instances or FastCar instances.
So then, to convert the car, you would just do one_car = FastCar(one_car). Note that this will not affect references to the original Car object, which will remain pointing to the same Car.
Why not just use one class?
class Car(object):
def __init__(self, color, max_speed = 50):
self.color = color
self.max_speed = max_speed
def drive(self):
print "Driving at %s mph"%self.max_speed
c=Car('blue')
c.max_speed = 100
It is not common in OOP to change type (class) of a living object after instantiation. I know barely two languages that would allow that as a dirty hack. The whole purpose of types (classes) is to know beforehand what operations an object can and can not perform. If you want something like this, you're probably mistaking the idea of OOP.

Categories