Python API design pattern question - python

I often find I have class instances that are descendants of other class instances, in a tree like fashion. For example say I'm making a CMS platform in Python. I might have a Realm, and under that a Blog, and under that a Post. Each constructor takes it's parent as the first parameter so it knows what it belongs to. It might look like this:
class Realm(object):
def __init__(self, username, password)
class Blog(object):
def __init__(self, realm, name)
class Post(object);
def __init__(self, blog, title, body)
I typically add a create method to the parent class, so the linkage is a bit more automatic. My Realm class might look like this:
class Realm(object):
def __init__(self, username, password):
...
def createBlog(self, name):
return Blog(self, name)
That allows the user of the API to not import every single module, just the top level one. It might be like:
realm = Realm("admin", "FDS$#%")
blog = realm.createBlog("Kittens!")
post = blog.createPost("Cute kitten", "Some HTML blah blah")
The problem is those create methods are redundant and I have to pydoc the same parameters in two places.
I wonder if there's a pattern (perhaps using metaclasses) for linking one class instance to a parent class instance. Some way I could call code like this and have the blog know what it's parent realm is:
realm = Realm("admin", "FDS$#%")
blog = realm.Blog("Kittens!")

You could use a common base class for the containers featuring an add() method
class Container(object):
def __init__(self, parent=None):
self.children = []
self.parent = parent
def add(self, child)
child.parent = self
self.children.append(child)
return child
and make the parent parameter optional in the derived classes
class Blog(Container):
def __init__(self, name, realm=None):
Container.__init__(realm)
self.name = name
Your code above would now read
realm = Realm("admin", "FDS$#%")
blog = realm.add(Blog("Kittens!"))
post = blog.add(Post("Cute kitten", "Some HTML blah blah"))
You wouldn't have any create...() methods any more, so no need to document anything twice.
If setting the parent involves more than just modifying the parent attribute, you could use a property or a setter method.
EDIT: As you pointed out in the comments below, the children should be tied to the parents by the end of the contstructor. The above approach can be modified to support this:
class Container(object):
def __init__(self, parent=None):
self.children = []
self.parent = None
def add(self, cls, *args)
child = cls(self, *args)
self.children.append(child)
return child
class Realm(Container):
def __init__(self, username, password):
...
class Blog(Container):
def __init__(self, realm, name):
...
class Post(Container):
def __init__(self, blog, title, body):
...
realm = Realm("admin", "FDS$#%")
blog = realm.add(Blog, "Kittens!")
post = blog.add(Post, "Cute kitten", "Some HTML blah blah")

What about something like this, just subclassing it. In my Realm constructor:
class Realm(object):
def __init__(self, username, password):
...
parent = self
original_constructor = blog.Blog.__init__
class ScopedBlog(blog.Blog):
def __init__(self, *args):
self.parent = parent
original_constructor(self, *args)
self.Blog = ScopedBlog
Seems to work. And it could be generalized with base classes or meta classes.

Related

Resolving Diamond Inheritance within Python Classes

Consider the following python code:
class Parent(object):
def __init__(self, name, serial_number):
self.name = name
self.serial_number = serial_number
class ChildA(Parent):
def __init__(self, name, serial_number):
self.name = name
self.serial_number = serial_number
super(ChildA, self).__init__(name = self.name, serial_number = self.serial_number)
def speak(self):
print("I am from Child A")
class ChildB(Parent):
def __init__(self, name, serial_number):
self.name = name
self.serial_number = serial_number
super(ChildB, self).__init__(name = self.name, serial_number = self.serial_number)
def speak(self):
print("I am from Child B")
class GrandChild(ChildA, ChildB):
def __init__(self, a_name, b_name, a_serial_number, b_serial_number):
self.a_name = a_name
self.b_name = b_name
self.a_serial_number = a_serial_number
self.b_serial_number = b_serial_number
super(GrandChild, self).__init_( something )
When running the super function in GrandChild, what is the proper way to format the __init__ arguments so that ChildA and ChildB both get the correct arguments?
Also how do you access the two different versions of the speak method (ChildA's version and ChildB's version) from within the GrandChild class?
so, when you call super from the grandchild, ChildA's __init__ method will be called because super follows the __mro__ property (parents left to right then grandparents left-to-right, then great grandparents, ...)
Since ChildA's init also calls super, then all the super calls will be chained, calling child b's __init__ and eventually the parent init.
For that to work, your interface generally needs to be consistent. That is positional arguments need to mean the same things, and be in the order.
In situations where that's not the case, keyword arguments may work better.
class Parent:
def __init__(self, name, serial, **kwargs):
self.name = name
self.serial = serial
class ChildA(Parent):
def __init__(self, a_name, a_serial, **kwargs):
self.a_name = a_name
self.a_serial = a_serial
super().__init__(**kwargs)
class ChildB(Parent):
def __init__(self, b_name, b_serial, **kwargs):
self.b_name = b_name
self.b_serial = b_serial
super().__init__(**kwargs)
class GrandChild(ChildA, ChildB):
def __init__(self):
super().__init__(name = "blah", a_name = "a blah", b_name = "b blah", a_serial = 99, b_serial = 99, serial = 30)
Also note that in your code name and serial are reused as instance properties between all the classes and that's probably not what you want.
In python, you can explicitly call a particular method on (one of) your parent class(es):
ChildA.__init__(self, a_name, a_serial)
ChildB.__init__(self, b_name, b_serial)
Note that you need to put the self in explicitly when calling this way.
You can also – as you did – use the super() way, which will call the "first" parent. The exact order is dynamic, but by default it will do left-to-right, depth-first, pre-order scans of your inheritance hierarchy. Hence, your super() call will only call __init__ on ChildA.

python subclasses have general functions shared across them

I've created a base class and a subclass. I'll be creating more subclasses, however I have some general functions that will be used across all subclasses. Is this the proper way of setting it up? I'm assuming it would be easier to add the def to the base class and then call it within each subclass. Is that possible to do or recommended?
"""
Base class for all main class objects
"""
class Node(object):
def __init__(self, name, attributes, children):
self.name = name
self.attributes = attributes if attributes is not None else {}
self.children = children if children is not None else []
"""
contains the settings for cameras
"""
class Camera(Node):
def __init__(self, name="", attributes=None, children=None, enabled=True):
super(Camera, self).__init__(name=name, attributes=attributes, children=children)
self.enabled = enabled
# defaults
add_node_attributes( nodeObject=self)
# General class related functions
# ------------------------------------------------------------------------------
""" Adds attributes to the supplied nodeObject """
def add_node_attributes(nodeObject=None):
if nodeObject:
nodeObject.attributes.update( { "test" : 5 } )
# create test object
Camera()
You should add the general methods on the base class and call them from the subclass:
class Node(object):
def __init__(self, name, attributes, children):
self.name = name
self.attributes = attributes if attributes is not None else {}
self.children = children if children is not None else []
def add_node_attributes(self):
self.attributes.update( { "test" : 5 } )
This allows you to take maximum advantage of inheritance. Your subclasses will have the method add_node_attributes available to them:
c=Camera()
c.add_node_attributes()
You can also call it from within the child class:
class Camera(Node):
def __init__(self, name="", attributes=None, children=None, enabled=True):
super(Camera, self).__init__(name=name, attributes=attributes, children=children)
self.enabled = enabled
# defaults
self.add_node_attributes()

Python - How can I access variables of an above class within a class within a list?

I'm not sure if this is possible within the language or not, but imagine this:
class Parent(object):
def __init__(self, number):
self.variable_to_access = "I want this"
self.object_list = []
for i in range(number): self.object_list.append(Object_In_List(i))
class Object_In_List(object):
def __init__(self): pass
def my_method(self):
# How can I access variable_to_access
I have over simplified this but I was thinking Object_In_List could inherit Parent but Parent will contain many other items and I am concerned about memory usage.
I want to avoid passing the variable_to_access itself constantly. Is this actually possible to access variable_to_access within my_method()?
Thanks
Here is a slightly more complicated example which behaves like Java inner classes
class Parent(object):
def __init__(self, number):
self.variable_to_access = "I want this"
self.object_list = [] for i in range(number):
# Pass in a reference to the parent class when constructing our "inner class"
self.object_list.append(Object_In_List(self, i))
class Object_In_List(object):
# We need a reference to our parent class
def __init__(self, parent, i):
self.parent = parent
# ... So we can forward attribute lookups on to the parent
def __getattr__(self, name):
return getattr(self.parent, name)
# Now we can treat members of the parent class as if they were our own members (just like Java inner classes)
def my_method(self):
# You probably want to do something other than print here
print(self.variable_to_access)
class Parent(object):
def __init__(self, number):
self.variable_to_access = "I want this"
self.object_list = []
for i in range(number):
self.object_list.append(Object_In_List(self.variable_to_access, i))
class Object_In_List(object):
def __init__(self, parent_var, i):
self.pv = parent_var
def my_method(self):
# How can I access variable_to_access
# self.pv is what you want
Right now it seems the only place that any Object_In_List objects exists are within the Parent attribute self.object_list. If that is the case then you are in the Parent class when accessing the Object_In_List already, so you don't even need my_method in Object_In_List. If these objects exist outside of that list then you would have to pass the Parent object or variable anyway, as shown in the other answers.
If you want to be creative you could play with class attributes of Parent. This would not be as "variable" though, unless your class attribute was a dictionary, but then there is the memory thing.

Make Python Class Methods searchable in Mongodb

I have a Mongodb collection and I have created a Python class for documents in the collection. The class has some properties and methods which are not stored with the document. Should I try and store them to make the properties searchable or should I not store them and search the objects in Python?
Here is an example:
# Child
class Child:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
#property
def parent(self):
try:
return Parent(**db.Parents.find_one({'child':self._id}))
except:
return None
# Parent
class Parent:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
#property
def child(self):
try:
return Child(**db.Children.find_one({'parent':self._id}))
except:
return None
In this example, to search for all the children who's parent's name is "foo", I have to do this:
results = [Child(**c) for c in db.Children.find() if c.parent.name == 'foo']
This means I have to pull all the Children documents from Mongodb and search them. Is it smarter to write the Parent data (or a subset of it) to the Child document, so I can use Mongodb to do the searching?? So my Child class could look like this:
# Child
class Child:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
#property
def parent_name(self):
try:
return db.Parents.find_one({'child':self._id})['name']
except:
return None
def _save(self):
# something like this to get and save all the properties
data = {m[0]:getattr(self,m[0]) for m in inspect.getmembers(self)}
db.Children.find_and_modify({'_id':self._id},{'$set':data},upsert=True)
# search
results = [Child(**c) for c in db.Children.find({'parent_name':'foo'})]
So the search is more efficient, but I think having to keep the Child objects updated could be painful and dangerous. If I change the name of a Parent, I have to also rewrite its children. Feels wrong. Any better ideas???
You don’t have to load all Children.
parent_ids = db.Parents.find({'name': 'foo'}).distinct('_id')
children = db.Children.find({'parent': {'$in': parent_ids}})
(Also, why do you have both a child field on a parent and a parent field on a child?)

Subclass not inheriting parent class

I'm having trouble with my code. I'm trying to create a subclass which inherits the parent class's attributes and methods but it doesn't work. Here's what I have so far:
class Employee(object):
def __init__(self, emp, name, seat):
self.emp = emp
self.name = name
self.seat = seat
Something is wrong with the block of code below - the subclass.
Do I have to create the __init__ again? And how do I create a new attribute for the subclass. From reading questions, it sounds like __init__ in the subclass will override the parent class - is that true if I call it to define another attribute?
class Manager(Employee):
def __init__(self, reports):
self.reports = reports
reports = []
reports.append(self.name) #getting an error that name isn't an attribute. Why?
def totalreports(self):
return reports
I want the names from the Employee class to be in the reports list.
For example, if I have:
emp_1 = Employee('345', 'Big Bird', '22 A')
emp_2 = Employee('234', 'Bert Ernie', '21 B')
mgr_3 = Manager('212', 'Count Dracula', '10 C')
print mgr_3.totalreports()
I want reports = ['Big Bird', 'Bert Ernie'] but it doesn't work
You never called the parent class's __init__ function, which is where those attributes are defined:
class Manager(Employee):
def __init__(self, reports):
super(Manager, self).__init__()
self.reports = reports
To do this, you'd have to modify the Employee class's __init__ function and give the parameters default values:
class Employee(object):
def __init__(self, emp=None, name=None, seat=None):
self.emp = emp
self.name = name
self.seat = seat
Also, this code will not work at all:
def totalreports(self):
return reports
reports's scope is only within the __init__ function, so it will be undefined. You'd have to use self.reports instead of reports.
As for your final question, your structure won't really allow you to do this nicely. I would create a third class to handle employees and managers:
class Business(object):
def __init__(self, name):
self.name = name
self.employees = []
self.managers = []
def employee_names(self);
return [employee.name for employee in self.employees]
You'd have to add employees to the business by appending them to the appropriate list objects.
You need to run the superclass's init() in the appropriate place, plus capture the (unknown to the subclass) arguments and pass them up:
class Manager(Employee):
def __init__(self, reports, *args, **kwargs):
self.reports = reports
reports = []
super(Manager, self).__init__(*args, **kwargs)
reports.append(self.name) #getting an error that name isn't an attribute. Why?

Categories