Python Classes and Collections - python

This is a best practices question
Let say, I have a class object, like so:
class ClassOfObjects:
def __init__(self, name):
self.name = name
...
Lets say, I instantiate 3 of these objects
a = ClassOfObjects('one')
b = ClassOfObjects('two')
c = ClassOfObjects('three')
Now, I want to create a list of my objects. One obvious way is to create list object
ListOfObjects = [a,b,c]
I find that limiting. Specially when I trying to search find an object with a particular object. Is anyone aware of any best practices.

You can have each instance register itself with the class when it's created:
class K(object):
registry = {}
def __init__(self, name):
self.name = name
self.registry[name] = self
Then K.registry is a dictionary of all the instances you've created, with the name as the key. You don't even need to assign the instance to a variable, since it's accessible through the registry. You can also iterate over the instances easily.
Perhaps if you share more information about your use cases, someone can provide a better alternative.

Related

How to instantiate an object in a new class?

A bit of an odd question, but I'm wondering how to import an object from one class to another. I imagine adding more class methods and attributes as I expand my program, and I still want to be able to use old data. I am thinking something as follows:
class old_obj:
def __init__(self, text):
self.name = text
def set_amount(self, num):
self.amount = num
def introduce_yourself(self):
print("I am {} and I am {} many".format(self.name, self.amount))
oldest = old_obj("myself")
oldest.set_amount(15)
also_old = old_obj("Bach")
class new_obj:
def __init__(self):
#some code
#more code
I want to be able to write something like:
renewed = new_obj(oldest)
also_new = new_obj(also_old)
Here, I want to retain the 15 from oldest.amount, but not complain that also_old.amount is None. In particular, I want to retain any attributes that oldest has, while not requiring that it have all possible attributes. Is there a way for me to copy over instances of a class to a new class?
Edit: edited for clarity
You could copy the object instance dict to the new class.
from copy import deepcopy
class old_obj:
def __init__(self, text):
self.name = text
def set_amount(self, num):
self.amount = num
def introduce_yourself(self):
print("I am {} and I am {} many".format(self.name, self.amount))
oldest = old_obj("myself")
class new_obj:
def __init__(self, my_old_obj):
for var, val in my_old_obj.__dict__.items():
setattr(self, var, deepcopy(val))
#some code
#more code
newest = new_obj(oldest)
I did a deepcopy of the value assuming you want unique values in the new object. But that can also be problematic because not everything can be copied (file objects for instance). There can be other oddities when duplicating attributes such as what you want to do with a generator. And if this is something like a GUI widget, it could get stranger still.
But for a lot of object types, this would work.
Slightly different take:
Your new class has a set of concerns that are probably similar to your old class. This should guide the way you update it and build out the behavior in question. With this in mind...
Provide a class method in your new class to allow construction of the new object from the old object. Don’t make this behavior a part of __init__. Your __init__ should have a more limited responsibility. For the class method, updating the new object’s __dict__ using the old object’s __dict__ would do the job.
Don’t use inheritance to make new versions of classes. Use inheritance to move from general to specific or abstract to concrete. Otherwise, you end up with code that is hard to understand and update. (Imagine several generations down of just sub-classing in order to add some new methods.)
If the number of methods and attributes is growing, you might want to consider whether or not you’re encapsulating data/behaviors that should be split into multiple classes. The guiding principle is that you should encapsulate the data/behaviors that are likely to change together. That is, when you change the way you’re implementing your program, things that don’t need to change should probably be encapsulated separate from things that need changing. If you find that a lot of your static data is bound up with an object class that you’re frequently updating (but wanting to just import the old data unchanged), then you’ve probably got two different sets of concerns, at least.
You can simply initialize the new object by passing it the old one.
class old_obj:
def __init__(self, text):
self.text = text
oldest = old_obj("myself")
class new_obj:
def __init__(self, old_inst):
self.text = old_inst.text
renewed = new_obj(oldest)
print(renewed.text)
First, make your new_obj class inherit from old_obj, so that new_obj has all the methods old_obj had:
class new_obj(olb_obj):
Then, in __init__ method of the new class you can check what is passed as the argument - a string or an object:
def __init__(self, arg):
if isinstance(arg, str):
self.text = arg
elif isinstance(arg, old_obj):
self.text = arg.text
else:
raise TypeError

Python, how to convert an existing parent class object to child class object

*** To give a reason why I wanna do the following, I am trying to wrap the third-party tool and do more with it.
I am using a third-party tool, it returns an object as a result. (e.g. An object of class Person, which contains MANY attributes).
I want to declare a new class, e.g. Driver(Person). Which contain everything that is in Person class, and more. I am NOT ALLOWED to modify the Person class.
I already have an existing Person object(returned from the third-party tool), how do I cast it into a Driver object? Not by initializing a new empty Driver object and copying everything one by one into it please... In my case, there's over 30 attributes and properties in the Person class, and Person class is not the only class I need to do this with. I have to do this with about 20 classes. This is why it is not practical for me to copy from scratch one by one for each class unless there's no other way.
class Person:
def __init__(self):
self._name = None
#property
def name(self):
return self._name
class Driver(Person):
def __init__(self):
super().__init__()
self._gender = None
#property
def gender(self):
return self._gender
def __str__(self):
return f'{self.name} {self.gender}'
Given an instance of Person, cast it into a Driver. but imagine there's way more attributes in both classes.
Currently, I basically just loaded the Person instance into a new Driver instance as an attribute. But I think there's gotta be a smarter way to do this.
Basically all attributes from a class live inside the magic __dict__ attribute. This dictionary-like object contains all the attributes defined for the object itself.
So a way to copy all attributes at once will be to copy the __dict__ attribute from a class to another either inside a method or from an instance like this:
a = Person()
b = Driver()
b.__dict__.update(a.__dict__)
Please note that if there are repeated attributes those will be overwritten so be careful

Python - cannot access instances of a class

I am stuck with a problem that is probably quite simple, yet I cannot figure it out. I am trying to develop a desktop application for creating timetables. I am using Tkinter and Python 3.6. I have a Teacher class, so the user can create new instances with various attributes.
class Teacher:
"""The properties of the teachers"""
allTeachers = []
def __init__(self, name, lessons, timeframe):
self.name = name
Teacher.allTeachers.append(self.name)
self.lessons = lessons # number of lessons
self.timeframe = timeframe
Once a new instance is created I check that it exists:
for obj in gc.get_objects():
if isinstance(obj, Teacher):
print(obj.name)
However, when the user adds another teacher, the above code says that the Teacher class still has only one instance (the latest one). Moreover, when I run the same code from another module (in the same directory), Python tells me that the Teacher class has no instances. In spite of this the class variable (allTeachers) keeps track of all the teachers that have been added.
Am I missing something basic about accessing objects?
Python frees memory, if you do not hold any reference to your instances. You are only storing the Teacher's names - not the instances of it. So if you happen to just create them like this:
Teacher("Hugo", None, "8am to 2pm")
Teacher("Claas", "some", "9am to 10am")
there is no reference to the actual instance and they get garbage collected.
Additionally information can be read here: Python garbage collector documentation
If you want to look up things, lists are bad if you have more then around 4 items, they got a Lookup of O(n). Use a set or dict for O(1) instead. If you want to lookup Teacher by name, dict would be convenient:
class Teacher:
"""The properties of the teachers"""
allTeachers = {} # dict as lookup is faster then list for 4+ teachers
def __init__(self, name, lessons, timeframe):
self.name = name
Teacher.allTeachers[self.name] = self # store the instance under its name
self.lessons = lessons # number of lessons
self.timeframe = timeframe
#classmethod
def hasTeacher(cls,name):
return name in Teacher.allTeachers
Teacher("Hugo", None, "8am to 2pm")
Teacher("Claas", "some", "9am to 10am")
import gc
print(Teacher.hasTeacher("Judith"))
print(Teacher.hasTeacher("Claas"))
for obj in gc.get_objects():
if isinstance(obj, Teacher):
print(obj.name)
Output:
False # no teacher called Judith
True # a teacher called Claas
# all the Teacher instances
Hugo
Claas
If you store Teacher instances this way, you probably should provide a way to remove them by name from the class variable instance as well - and maybe return the Teacher-instance by name from it

Understanding data encapsulation in Python

I am reading up on how we ensure data encapsulation in python.One of the blog says
"Data Encapsulation means, that we should only be able to access private attributes via getters and setters"
Consider the following snippets from the blog:
class Robot:
def __init__(self, name=None, build_year=None):
self.name = name
self.build_year = build_year
Now, if i create the object of the class as below:
obj1=Robot()
obj1.name('Robo1")
obj1.build_year("1978")
Currently, i can access the attributes directly as i have defined them public(without the __notation)
Now to ensure data encapsulation, i need to define the attributes as privates
using the __ notation and access private attributes via getters and setters.
So the new class definition is as follows:
class Robot:
def __init__(self, name=None, build_year=2000):
self.__name = name
self.__build_year = build_year
def set_name(self, name):
self.__name = name
def get_name(self):
return self.__name
def set_build_year(self, by):
self.__build_year = by
def get_build_year(self):
return self.__build_year
Now i instantiate the class as below:
x = Robot("Marvin", 1979)
x.set_build_year(1993)
This way, i achive data encapsulation as private data members are no longer accessed directly and they can only be accessed via the class methods.
Q1:Why are we doing this? Who are we protecting the code from? Who is outside world?Anyone who has the source code can tweak it as per their requirement, so why at all do we add extra methods(get/set) to modify/tweak the attributes?
Q2:Is the above example considered data encapsulation?
Data encapsulation is slightly more general than access protection. name and build_year are encapsulated by the class Robot regardless of how you define the attributes. Python takes the position that getters and setters that do nothing more than access or assign to the underlying attribute are unnecessary.
Even using the double-underscore prefix is just advisory, and is more concerned with preventing name collisions in subclasses. If you really wanted to get to the __build_year attribute directly, you still could with
# Prefix attribute name with _Robot
x._Robot__build_year = 1993
A better design in Python is to use a property, which causes Python to invoke a defined getter and/or setter whenever an attribute is defined directly. For example:
class Robot(object):
def __init__(self, name, by):
self.name = name
self.build_year = by
#property
def name(self):
return self._name
#name.setter
def name(self, newname):
self._name = newname
#property
def build_year(self):
return self._build_year
#build_year.setter
def build_year(self, newby):
self._build_year = newby
You wouldn't actually define these property functions so simply, but a big benefit is that you can start by allowing direct access to a name attribute, and if you decide later that there should be more logic involved in getting/setting the value and you want to switch to properties, you can do so without affecting existing code. Code like
x = Robot("bob", 1993)
x.build_year = 1993
will work the same whether or not x.build_year = 1993 assigns to build_year directly or if it really triggers a call to the property setter.
About source code: sometimes you supply others with compiled python files that does not present the source, and you don't want people to get in mess with direct attribute assignments.
Now, consider data encapsulation as safe guards, last point before assigning or supplying values:
You may want to validate or process assignments using the sets, to make sure the assignment is valid for your needs or enters to the variable in the right format, (e.g. you want to check that attribute __build_year is higher than 1800, or that the name is a string). Very important in dynamic languages like python where a variable is not declared with a specific type.
Same goes for gets. You might want to return the year as a decimal, but use it as an integer in the class.
Yes, your example is a basic data encapsulation.

How should I expose read-only fields from Python classes?

I have many different small classes which have a few fields each, e.g. this:
class Article:
def __init__(self, name, available):
self.name = name
self.available = available
What's the easiest and/or most idiomatic way to make the name field read only, so that
a = Article("Pineapple", True)
a.name = "Banana" # <-- should not be possible
is not possible anymore?
Here's what I considered so far:
Use a getter (ugh!).
class Article:
def __init__(self, name, available):
self._name = name
self.available = available
def name(self):
return self._name
Ugly, non-pythonic - and a lot of boilerplate code to write (especially if I have multiple fields to make read-only). However, it does the job and it's easy to see why that is.
Use __setattr__:
class Article:
def __init__(self, name, available):
self.name = name
self.available = available
def __setattr__(self, name, value):
if name == "name":
raise Exception("%s property is read-only" % name)
self.__dict__[name] = value
Looks pretty on the caller side, seems to be the idiomatic way to do the job - but unfortunately I have many classes with only a few fields to make read only each. So I'd need to add a __setattr__ implementation to all of them. Or use some sort of mixin maybe? In any case, I'd need to make up my mind how to behave in case a client attempts to assign a value to a read-only field. Yield some exception, I guess - but which?
Use a utility function to define properties (and optionally getters) automatically. This is basically the same idea as (1) except that I don't write the getters explicitely but rather do something like
class Article:
def __init__(self, name, available):
# This function would somehow give a '_name' field to self
# and a 'name()' getter to the 'Article' class object (if
# necessary); the getter simply returns self._name
defineField(self, "name")
self.available = available
The downside of this is that I don't even know if this is possible (or how to implement it) since I'm not familiar with runtime code generation in Python. :-)
So far, (2) appears to be most promising to me except for the fact that I'll need __setattr__ definitions to all my classes. I wish there was a way to 'annotate' fields so that this happens automatically. Does anybody have a better idea?
For what it's worth, I'mu sing Python 2.6.
UPDATE:
Thanks for all the interesting responses! By now, I have this:
def ro_property(o, name, value):
setattr(o.__class__, name, property(lambda o: o.__dict__["_" + name]))
setattr(o, "_" + name, value)
class Article(object):
def __init__(self, name, available):
ro_property(self, "name", name)
self.available = available
This seems to work quite nicely. The only changes needed to the original class are
I need to inherit object (which is not such a stupid thing anyway, I guess)
I need to change self._name = name to ro_property(self, "name", name).
This looks quite neat to me - can anybody see a downside with it?
I would use property as a decorator to manage your getter for name (see the example for the class Parrot in the documentation). Use, for example, something like:
class Article(object):
def __init__(self, name, available):
self._name = name
self.available = available
#property
def name(self):
return self._name
If you do not define the setter for the name property (using the decorator x.setter around a function) this throws an AttributeError when you try and reset name.
Note: You have to use Python's new-style classes (i.e. in Python 2.6 you have to inherit from object) for properties to work correctly. This is not the case according to #SvenMarnach.
As pointed out in other answers, using a property is the way to go for read-only attributes. The solution in Chris' answer is the cleanest one: It uses the property() built-in in a straight-forward, simple way. Everyone familiar with Python will recognize this pattern, and there's no domain-specific voodoo happening.
If you don't like that every property needs three lines to define, here's another straight-forward way:
from operator import attrgetter
class Article(object):
def __init__(self, name, available):
self._name = name
self.available = available
name = property(attrgetter("_name"))
Generally, I don't like defining domain-specific functions to do something that can be done easily enough with standard tools. Reading code is so much easier if you don't have to get used to all the project-specific stuff first.
Based in the Chris answer, but arguably more pythonic:
def ro_property(field):
return property(lambda self : self.__dict__[field])
class Article(object):
name = ro_property('_name')
def __init__(self):
self._name = "banana"
If trying to modify the property it will raise an AttributeError.
a = Article()
print a.name # -> 'banana'
a.name = 'apple' # -> AttributeError: can't set attribute
UPDATE: About your updated answer, the (little) problem I see is that you are modifying the definition of the property in the class every time you create an instance. And I don't think that is such a good idea. That's why I put the ro_property call outside of the __init__ function
What about?:
def ro_property(name):
def ro_property_decorator(c):
setattr(c, name, property(lambda o: o.__dict__["_" + name]))
return c
return ro_property_decorator
#ro_property('name')
#ro_property('other')
class Article(object):
def __init__(self, name):
self._name = name
self._other = "foo"
a = Article("banana")
print a.name # -> 'banana'
a.name = 'apple' # -> AttributeError: can't set attribute
Class decorators are fancy!
It should be noted that it's always possible to modify attributes of an object in Python - there are no truly private variables in Python. It's just that some approaches make it a bit harder. But a determined coder can always lookup and modify the value of an attribute. For example, I can always modify your __setattr__ if I want to...
For more information, see Section 9.6 of The Python Tutorial. Python uses name mangling when attributes are prefixed with __ so the actual name at runtime is different but you could still derive what that name at runtime is (and thus modify the attribute).
I would stick with your option 1 but refined it to use Python property:
class Article
def get_name(self):
return self.__name
name = property(get_name)

Categories