Split big object into smaller sub objects in instance - python

What is the pythonic way to handle large objects? In my example I could have one big class creating one instance with many attributes or I could group some of them together (See class Car and class Motor):
class Car(object):
color = "red"
def __init__(self, num_wheels):
self.burst = Motor(self)
self.wheels = num_wheels
for i in range(self.wheels):
setattr(self, "wheel{}".format(i), Wheel(self, i))
def _query(self, value):
print "Get Serial Data: {}".format(value)
class Motor(object): # Object is loaded only once in Car instance
def __init__(self, top):
self._top = top
def temperature(self):
return self._top._query("GET MOTOR TEMP")
def rpm(self):
return self._top._query("GET MOTOR RPM")
class Wheel(object): # Object could be loaded many times in Car instance
def __init__(self, top, number):
self._top = top
self._number = number
def temperature(self):
return self._top._query("GET WHEEL TEMP {}".format(self._number))
def rpm(self):
return self._top._query("GET WHEEL RPM {}".format(self._number))
I think this even makes more sense, when the Car has more than one wheel, as I could add more wheels.
But since Motor is never used more than once and never used else where, is it better style to put them into the Car class:
class Car(object):
color = "red"
def __init__(self, num_wheels):
# Add wheels or other stuff
def _query(self, value):
print "Get Serial Data: {}".format(value)
def motor_temperature(self):
return self._query("GET MOTOR TEMP")
def motor_rpm(self):
return self.._query("GET MOTOR RPM")
I will have to access Car._query() from the Wheel and/or Motor class and my real life object contains about 40 attributes and methods I could group in 4-5 sub instances. Couldn't find much on this topic on the web.

If you use large numbers of instances, you can used slots to reduce memory footprint.

Related

How does python3 diamond inheritance works for data fields? How to initialize the inherited fields with super()._init__?

I was checking this problem to understand multiple inheritance and I got stuck.
How can I set the fields of the inherited objects from the last class?
class Vehicle():
def __init__(self, name:str, seats:int):
self.name = name
self.seats = seats
def print_vehicle(self):
print(f'Vehicle {self.name} has {self.seats} seats')
class Boat(Vehicle):
def __init__(self, name:str, seats:int, engine_type:str):
super().__init__(name, seats)
self.engine_type = engine_type
def print_vehicle(self):
print(f'Boat {self.name} has {self.seats} seats and engine {self.engine_type}')
class Car(Vehicle):
def __init__(self, name:str, seats:int, fuel:str):
super().__init__(name, seats)
self.fuel = fuel
def print_vehicle(self):
print(f'Car {self.name} has fuel {self.fuel}')
class AnphibiousCar(Boat, Car):
def __init__(self, name, seats, engine_type, fuel):
super(AnphibiousCar, self).__init__(name, seats, engine_type) # ???
def print_vehicle(self):
print(f'Anphibious car {self.name} has {self.seats} seats and {self.engine_type} - {self.fuel} engine')
ac = AnphibiousCar('name', 4, 'piston', 'gas')
ac.print_vehicle()
The point is that each class should focus only on the stuff which is its direct responsibility; the rest should be delegated to superclasses (and note that, when you deal with such a cooperative inheritance with super(), your methods that call super() should not need to know what exactly are the actual superclasses, in particular the nearest one - as this can change, depending on the actual class of self).
So let's reimplement your classes (with a bunch of explanations in the comments):
class Vehicle:
# Added the `*,` marker to make `name` and `seats` *keyword-only*
# arguments (i.e., arguments that are identified only by their
# *names*, never by their positions in a call's arguments list).
def __init__(self, *, name: str, seats: int):
self.name = name
self.seats = seats
# We abstract out class-specific features into separate methods,
# keeping in the `print_vehicle()` method only the common stuff,
# so that in subclasses we'll need to customize only those methods
# (`list_features()`, `get_type_label()`), *not* `print_vehicle()`.
def print_vehicle(self):
vehicle_type_label = self.get_type_label()
features = ', '.join(self.list_features())
print(f'{vehicle_type_label} {self.name}: {features}.')
# Side note: the `list[str]` type annotation requires Python 3.9
# or newer (for compatibility with older versions you need to
# replace it with `List[str]`, using `from typing import List`).
def list_features(self) -> list[str]:
return [f'has {self.seats} seats']
# This implementation is, in fact, quite generic (so that
# in most subclasses we will *not* need to customize it).
def get_type_label(self) -> str:
return self.__class__.__name__
class Boat(Vehicle):
# Only `Boat`-specific arguments (as keyword-only ones, as above...)
# are declared here explicitly. Any other are treated as a "black
# box", just being passed into superclasses...
def __init__(self, *, engine_type: str, **kwargs):
super().__init__(**kwargs)
self.engine_type = engine_type
# Also here we focus only on this-class-specific stuff, handling
# other stuff as "agnostically" as possible...
def list_features(self) -> list[str]:
return super().list_features() + [f'has {self.engine_type} engine']
class Car(Vehicle):
# And analogously...
def __init__(self, *, fuel: str, **kwargs):
super().__init__(**kwargs)
self.fuel = fuel
def list_features(self) -> list[str]:
return super().list_features() + [f'needs {self.fuel} fuel']
class AmphibiousCar(Boat, Car):
# Note: here we get our `__init__()` and `list_features()`
# for free (!), as the superclasses provide all we need
# when it comes to those two methods.
# The only thing we may want to customize is:
def get_type_label(self) -> str:
return 'Amphibious car'
ac = AmphibiousCar(
name='Julia-III',
seats=4,
engine_type='piston',
fuel='gas')
# "Amphibious car Julia-III: has 4 seats, needs gas fuel, has piston engine."
ac.print_vehicle()
As a further reading, I'd recommend: https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
You have some errors:
super(AnphibiousCar, self).__init__(name, seats, engine_type) could become
Boat.__init__(self, name, seats, engine_type) so calling the class you could
give information about how to initialize it.
there is a missing parameter in Boat where you should give a fuel argument
to the superclass Vehicle, like super().__init__(name, seats, "oil")
As you can note if you use super you don't need to pass self, if you use
the class name you are using it.
My point of view is that, yes, is good to understand, but don't loose to much
time as this kind of multiple inheritance is only theoretical and practically
not used in real coding. This in fact can cause a lot of confusion and add
boilerplate... "new" languages like, for example, Rust do not even provide
inheritance.
Just to say: "Yes, study it, but keep it simple" ^_^

Dynamically creating properties of a class

Take a look at this code snippet:
class Face():
pass
class Cube():
def __init__(self):
self.faces = {
'front': Face(1),
...
}
#property
def front(self):
return self.faces['front']
#front.setter
def front(self, f):
pass
I've created getters and setters for all the faces. Is there any way to make this code more compact, maybe by dynamically creating the getters and setters?
The following code assumes that you
have a reason to have the self.faces dict instead of setting attributes like front directly on the instance
and/or want to implement some meaningful getter and setter logic for the keys in self.faces.
Otherwise, this exercise is pretty pointless because as Corentin Limier noted you can simply set self.front = Face(1), and so on.
You can use descriptors, a class variable holding the face names and a class decorator. Think of descriptors as reusable properties.
In the following sample code I added a num instance variable to Face and the face 'side' just for demonstration purposes.
class FaceDescriptor:
def __get__(self, instance, owner):
# your custom getter logic
# dummy implementation
if instance is not None:
return instance.faces[self.face]
def __set__(self, instance, value):
# your custom setter logic
# dummy implementation
instance.faces[self.face] = value
def set_faces(cls):
for face in cls._faces:
desc = FaceDescriptor()
desc.face = face
setattr(cls, face, desc)
return cls
class Face():
def __init__(self, num):
self.num = num
#set_faces
class Cube():
_faces = ['front', 'side']
def __init__(self):
self.faces = {face:Face(i) for i, face in enumerate(self._faces, 1)}
In action:
>>> c = Cube()
>>> c.front.num
1
>>> c.side.num
2
>>> c.front = 'stuff'
>>> c.front
'stuff'
>>> c.faces
{'front': 'stuff', 'side': <__main__.Face at 0x7fd0978f37f0>}
Assuming that's all your class does, you could do something like
class Cube:
...
def __getattr__(self, name):
return self.faces[name]
def __setattr__(self, name, value):
self.faces[name] = value
if you really want to do that you could use __getattr__ and __setattr__:
class Cube:
...
def __getattr__(self, item):
return self.faces[item]
def __setattr__(self, item, value):
self.faces[item] = value
but as you set front in the __init__ methoud you could just as well make it a regular member...
Your code is redundant, since instance attributes are already stored in a dictionary which is the __dict__ property. I recognize that you are focused on writing your code in fewer lines. It is a good challenge to keep yourself growing, but in the long term you should be focused on the clarity of your code instead.
Here is a simpler way to write your code without using properties:
class Face():
pass
class Cube():
def __init__(self):
self.front = Face()
self.rear = Face()
It is a tenet of encapsulation that you should hide your "attributes" behind "properties". Even though this isn't strongly enforced in python, it's not a bad idea to do that. Here's the proper way to do that:
class Face():
pass
class Cube():
def __init__(self):
self._front = Face()
#property
def front(self):
return self._front
#front.setter
def front(self, value):
self._front = value
To answer your question at the end, yes you can dynamically create properties.
https://stackoverflow.com/a/1355444/3368572
But keep in mind that writing dynamic code should be reserved for special cases since it will make it more difficult for your IDE to follow the flow of your program. If you use the conventions as they are intended then your code becomes self-explanatory to people and to your IDE.

Replacing member objects with subclasses in Python

I have the following problem that I will attempt to illustrate with the following example.
class Brick():
def __init__(self):
self.weight = 1
class House():
def __init__(self, number_bricks):
self.bricks = [Brick() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x,y: x+y, [brick.weight for brick in self.bricks])
But now suppose I create a new kind of Brick, StrongBrick, so that I make a house, a subclass StrongHouse, where StrongBrick plays exactly the same role in StrongHouse as Brick plays in House. How can I do this in a nice way (not just retyping all the class definitions)?
So the basic idea is, how can I change a class which is composed of some objects to the same class but composed of say a subclass of the original member objects?
Thanks very much for any help you can give me.
You could have a factory (a brickyard?) and pass that to House.__init__().
class Brick(object): pass
class StrongBrick(Brick): pass
class House(object):
def __init__(self, brick_factory, num_bricks):
self.bricks = [brick_factory() for i in range(num_bricks)]
house = House(Brick, 10000)
strong_house = House(StrongBrick, 10000)
As you can see, subclassing House isn't even necessary to be able to construct houses from different types of bricks.
There are various ways to do this. You could make the relevant Brick class an attribute of the House class:
class House(object):
brick_class = Brick
def __init__(self, number_bricks):
self.bricks = [self.brick_class() for i in range(number_bricks)]
class StrongHouse(House):
brick_class = StrongBrick
Or, you could pass in the Brick class you want to use when constructing the House:
class House(object):
def __init__(self, brick_class, number_bricks):
self.bricks = [brick_class() for i in range(number_bricks)]
One nice pattern could be this:
class Brick(object):
weight = 1
class StrongBrick(Brick):
weight = 42
class House(object):
brick_type = Brick
def __init__(self, number_bricks):
self.bricks = [self.brick_type() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x, y: x + y, [brick.weight for brick in self.bricks])
class StrongHouse(House):
brick_type = StrongBrick
Another is to make a function making a factory, and using an argument for the brick_type with default value:
class House(object):
def __init__(self, number_bricks, brick_type=Brick):
self.bricks = [brick_type() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x, y: x + y, [brick.weight for brick in self.bricks])
def make_house_factory(brick_type):
def factory(number_bricks):
return House(number_bricks, brick_type)
return factory
StrongHouse = make_house_factory(StrongBrick)
Of course all such objects would be instances of the House only, even though I named StrongHouse here so that it resembles a class name.
But now suppose I create a new kind of Brick, StrongBrick, so that I make a house, a subclass StrongHouse, where StrongBrick plays exactly the same role in StrongHouse as Brick plays in House. How can I do this in a nice way (not just retyping all the class definitions)?
As all of the other answers have explained, you really don't want to create this parallel hierarchy at all. But to answer your direct question: You can create classes dynamically, so you can create a parallel hierarchy without copying and pasting all the class definitions. Classes are, after all, first-class objects.
Again, let me stress that you almost certainly don't want to do this, and I'm just showing that it is possible.
def make_house_class(brick_type):
class NewHouse(House):
def __init__(self, number_bricks):
self.bricks = [brick_type() for i in range(number_bricks)]
return NewHouse
Now, you could statically create all the house types:
StrongHouse = make_house_class(StrongBrick)
CheapHouse = make_house_class(CheapHouse)
# ...
… or maybe build them dynamically from a collection of all of your brick type:
brick_types = (StrongBrick, CheapBrick)
house_types = {brick_type: make_house_class(brick_type) for brick_type in brick_types}
… or even add some hacky introspection to just create a new FooHouse type for every FooBrick type in the current module:
for name, value in globals().items():
if name.endswith('Brick') and name != 'Brick' and isinstance(value, type):
globals()[name.replace('Brick', 'House')] = make_house_class(value)
… or even create them on the fly as needed in the factory-maker:
def make_house_factory(brick_type):
house_type = make_house_class(brick_type)
def factory(number_bricks):
return house_type(number_bricks, brick_type)
return factory
… or even the generated factory:
def make_house_factory(brick_type):
def factory(number_bricks):
return make_house_class(brick_type)(number_bricks, brick_type)
return factory
Add a parameter to the House.__init__ so that you can specify the Brick type:
import functools
class Brick():
def __init__(self):
self.weight = 1
class StrongBrick():
def __init__(self):
self.weight = 10
class House():
def __init__(self, number_bricks,brick_type=Brick):
self.bricks = [brick_type() for i in range(number_bricks)]
def get_weight(self):
return reduce(lambda x,y: x+y, [brick.weight for brick in self.bricks])
#not a new class, but an alias with a different default brick_type
StrongHouse = functools.partial(House,brick_type=StrongBrick)

Python: showing attributes assigned to a class object in the class code

One of my classes does a lot of aggregate calculating on a collection of objects, then assigns an attribute and value appropriate to the specific object: I.e.
class Team(object):
def __init__(self, name): # updated for typo in code, added self
self.name = name
class LeagueDetails(object):
def __init__(self): # added for clarity, corrected another typo
self.team_list = [Team('name'), ...]
self.calculate_league_standings() # added for clarity
def calculate_league_standings(self):
# calculate standings as a team_place_dict
for team in self.team_list:
team.place = team_place_dict[team.name] # a new team attribute
I know, as long as the calculate_league_standings has been run, every team has team.place. What I would like to be able to do is to scan the code for class Team(object) and read all the attributes, both created by class methods and also created by external methods which operate on class objects. I am getting a little sick of typing for p in dir(team): print p just to see what the attribute names are. I could define a bunch of blank attributes in the Team __init__. E.g.
class Team(object):
def __init__(self, name): # updated for typo in code, added self
self.name = name
self.place = None # dummy attribute, but recognizable when the code is scanned
It seems redundant to have calculate_league_standings return team._place and then add
#property
def place(self): return self._place
I know I could comment a list of attributes at the top class Team, which is the obvious solution, but I feel like there has to be a best practice here, something pythonic and elegant here.
If I half understand your question, you want to keep track of which attributes of an instance have been added after initialization. If this is the case, you could use something like this:
#! /usr/bin/python3.2
def trackable (cls):
cls._tracked = {}
oSetter = cls.__setattr__
def setter (self, k, v):
try: self.initialized
except: return oSetter (self, k, v)
try: self.k
except:
if not self in self.__class__._tracked:
self.__class__._tracked [self] = []
self.__class__._tracked [self].append (k)
return oSetter (self, k, v)
cls.__setattr__ = setter
oInit = cls.__init__
def init (self, *args, **kwargs):
o = oInit (self, *args, **kwargs)
self.initialized = 42
return o
cls.__init__ = init
oGetter = cls.__getattribute__
def getter (self, k):
if k == 'tracked': return self.__class__._tracked [self]
return oGetter (self, k)
cls.__getattribute__ = getter
return cls
#trackable
class Team:
def __init__ (self, name, region):
self.name = name
self.region = region
#set name and region during initialization
t = Team ('A', 'EU')
#set rank and ELO outside (hence trackable)
#in your "aggregate" functions
t.rank = 4 # a new team attribute
t.ELO = 14 # a new team attribute
#see witch attributes have been created after initialization
print (t.tracked)
If I did not understand the question, please do specify which part I got wrong.
Due to Python's dynamic nature, I don't believe there is a general answer to your question. An attribute of an instance can be set in many ways, including pure assignment, setattr(), and writes to __dict__ . Writing a tool to statically analyze Python code and correctly determine all possible attributes of an class by analyzing all these methods would be very difficult.
In your specific case, as the programmer you know that class Team will have a place attribute in many instances, so you can decide to be explicit and write its constructor like so:
class Team(object):
def __init__(name ,place=None):
self.name = name
self.place = place
I would say there is no need to define a property of a simple attribute, unless you wanted side effects or derivations to happen at read or write time.

Managing Instances in Python

I am new to Python and this is my first time asking a stackOverflow question, but a long time reader. I am working on a simple card based game but am having trouble managing instances of my Hand class. If you look below you can see that the hand class is a simple container for cards(which are just int values) and each Player class contains a hand class. However, whenever I create multiple instances of my Player class they all seem to manipulate a single instance of the Hand class. From my experience in C and Java it seems that I am somehow making my Hand class static. If anyone could help with this problem I would appreciate it greatly.
Thank you,
Thad
To clarify: An example of this situation would be
p = player.Player()
p1 = player.Player()
p.recieveCard(15)
p1.recieveCard(21)
p.viewHand()
which would result in:
[15,21]
even though only one card was added to p
Hand class:
class Hand:
index = 0
cards = [] #Collections of cards
#Constructor
def __init__(self):
self.index
self.cards
def addCard(self, card):
"""Adds a card to current hand"""
self.cards.append(card)
return card
def discardCard(self, card):
"""Discards a card from current hand"""
self.cards.remove(card)
return card
def viewCards(self):
"""Returns a collection of cards"""
return self.cards
def fold(self):
"""Folds the current hand"""
temp = self.cards
self.cards = []
return temp
Player Class
import hand
class Player:
name = ""
position = 0
chips = 0
dealer = 0
pHand = []
def __init__ (self, nm, pos, buyIn, deal):
self.name = nm
self.position = pos
self.chips = buyIn
self.dealer = deal
self.pHand = hand.Hand()
return
def recieveCard(self, card):
"""Recieve card from the dealer"""
self.pHand.addCard(card)
return card
def discardCard(self, card):
"""Throw away a card"""
self.pHand.discardCard(card)
return card
def viewHand(self):
"""View the players hand"""
return self.pHand.viewCards()
def getChips(self):
"""Get the number of chips the player currently holds"""
return self.chips
def setChips(self, chip):
"""Sets the number of chips the player holds"""
self.chips = chip
return
def makeDealer(self):
"""Makes this player the dealer"""
self.dealer = 1
return
def notDealer(self):
"""Makes this player not the dealer"""
self.dealer = 0
return
def isDealer(self):
"""Returns flag wether this player is the dealer"""
return self.dealer
def getPosition(self):
"""Returns position of the player"""
return self.position
def getName(self):
"""Returns name of the player"""
return self.name
From my experience in C and Java it seems that I am somehow making my Hand class static.
Actually, that is basically what you're doing. Well, not really making the class static, but making the variable static.
When you write declarations like this:
class Hand:
cards = []
that variable (cards) is associated with the class, not with the instance. To make an analogy to Java, every statement in a Python class that isn't part of a method of that class basically runs in a static initializer. You could almost think of it like this:
class Hand {
static {
cards = new object[];
}
}
(merely a rough analogy, of course)
To create an instance variable in Python, you have to set it as an attribute of the instance, which requires you to wait until you have a reference to the instance. In practice, this means you initialize it in the constructor, like so:
class Hand:
def __init__(self):
self.cards = []
Your problem is quite simple
if you assign lists to the body of python classes, when you append items to it, they will be store at Class level, not at instance level.
you can solve this problem by adding the line:
def __init__(self):
self.cards = []
this is a very known case of python pitfall, and I recommend you the reading:
http://zephyrfalcon.org/labs/python_pitfalls.html
As other answers noted, you were confused about class variables vs. instance variables. I suggest you review the basics of how Python classes work. Here is an answer I wrote for another question; reading this might help you.
How to define a class in Python

Categories

Resources