Is there are a way in Python to store instantiated class as a class 'template' (aka promote object to a class) to create new objects of same type with same fields values, without relying on using data that was used to create original object again or on copy.deepcopy?
Like, for example I have the dictionary:
valid_date = {"date":"30 february"} # dict could have multiple items
and I have the class:
class AwesomeDate:
def __init__(self, dates_dict):
for key, val in dates_dict.items():
setattr(self, key, val);
I create the instance of the class like:
totally_valid_date = AwesomeDate(valid_date)
print(totally_valid_date.date) # output: 30 february
and now I want to use it to create new instances of the AwesomeDate class using the totally_valid_date instance as a template, i.e. like:
how_make_it_work = totally_valid_date()
print(how_make_it_work.date) # should print: 30 february
Is there are way to do so or no? I need a generic solution, not a solution for this specific example.
I don't really see the benefit of having a class act both as a template to instances, and as the instance itself, both conceptually and coding-wise. In my opinion, you're better off using two different classes - one for the template, one for the objects it is able to create.
You can think about awesome_date as a template class that stores the valid_date attributes upon initialization. Once called, the template returns an instance of a different class that has the expected attributes.
Here's a simple implementation (names have been changed to generalize the idea):
class Thing:
pass
class Template:
def __init__(self, template_attrs):
self.template_attrs = template_attrs
def __call__(self):
instance = Thing()
for key, val in self.template_attrs.items():
setattr(instance, key, val)
return instance
attrs = {'date': '30 february'}
template = Template(template_attrs=attrs)
# Gets instance of Thing
print(template()) # output: <__main__.Thing object at 0x7ffa656f8668>
# Gets another instance of Thing and accesses the date attribute
print(template().date) # output: 30 february
Yes, there are ways to do it -
there could even be some tweaking of inheriting from type and meddling with __call__ to make all instances automatically become derived classes. But I don't think that would be very sane. Python's own enum.Enum does something along this, because it has some use for the enum values - but the price is it became hard to understand beyond the basic usage, even for seasoned Pythonistas.
However, having a custom __init_subclass__ method that can inject some code to run prior to __init__ on the derived class, and then a method that will return a new class bound with the data that the new classes should have, can suffice:
import copy
from functools import wraps
def wrap_init(init):
#wraps(init)
def wrapper(self, *args, **kwargs):
if not getattr(self, "_initalized", False):
self.__dict__.update(self._template_data or {})
self._initialized = True
return init(self, *args, **kwargs)
wrapper._template_wrapper = True
return wrapper
class TemplateBase:
_template_data = None
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
if getattr(cls.__init__, "_template_wraper", False):
return
init = cls.__init__
cls.__init__ = wrap_init(init)
def as_class(self):
cls= self.__class__
new_cls = type(cls.__name__ + "_templated", (cls,), {})
new_cls._template_data = copy.copy(self.__dict__)
return new_cls
And using it:
class AwesomeDate(TemplateBase):
def __init__(self, dates_dict):
for key, val in dates_dict.items():
setattr(self, key, val)
On the REPL we have:
In [34]: x = AwesomeDate({"x":1, "y":2})
In [35]: Y = x.as_class()
In [36]: y = Y({})
In [37]: y.x
Out[37]: 1
Actually, __init_subclass__ itself could be supressed, and decorating __init__ could be done in one shot on the as_class method. This code takes some care so that mixin classes can be used, and it will still work.
It seems like you are going for something along the lines of the prototype design pattern.
What is the prototype design pattern?
From Wikipedia: Prototype pattern
The prototype pattern is a creational design pattern in software development. It is used when the type of objects to create is determined by a prototypical instance, which is cloned to produce new objects. This pattern is used to avoid subclasses of an object creator in the client application, like the factory method pattern does and to avoid the inherent cost of creating a new object in the standard way (e.g., using the 'new' keyword) when it is prohibitively expensive for a given application.
From Refactoring.guru: Prototype
Prototype is a creational design pattern that lets you copy existing objects without making your code dependent on their classes. The Prototype pattern delegates the cloning process to the actual objects that are being cloned. The pattern declares a common interface for all objects that support cloning. This interface lets you clone an object without coupling your code to the class of that object. Usually, such an interface contains just a single clone method.
The implementation of the clone method is very similar in all classes. The method creates an object of the current class and carries over all of the field values of the old object into the new one. You can even copy private fields because most programming languages let objects access private fields of other objects that belong to the same class. An object that supports cloning is called a prototype. When your objects have dozens of fields and hundreds of possible configurations, cloning them might serve as an alternative to subclassing. Here’s how it works: you create a set of objects, configured in various ways. When you need an object like the one you’ve configured, you just clone a prototype instead of constructing a new object from scratch.
Implementing this for your problem, along with your other ideas
From your explanation, it seems like you want to:
Provide a variable containing a dictionary, which will be passed to the __init__ of some class Foo
Instantiate class Foo and pass the variable containing the dictionary as an argument.
Implement __call__ onto class Foo, allowing us to use the function call syntax on an object of class Foo.
The implementation of __call__ will COPY/CLONE the “template” object. We can then do whatever we want with this copied/cloned instance.
The Code (edited)
import copy
class Foo:
def __init__(self, *, template_attrs):
if not isinstance(template_attrs, dict):
raise TypeError("You must pass a dict to instantiate this class.")
self.template_attrs = template_attrs
def __call__(self):
return copy.copy(self)
def __repr__(self):
return f"{self.template_attrs}"
def __setitem__(self, key, value):
self.template_attrs[key] = value
def __getitem__(self, key):
if key not in self.template_attrs:
raise KeyError(f"Key {key} does not exist in '{self.template_attrs=}'.")
return self.template_attrs[key]
err = Foo(template_attrs=1) # Output: TypeError: You must pass a dict to instantiate this class.
# remove err's assignment to have code under it run
base = Foo(template_attrs={1: 2})
print(f"{base=}") # Output: base={1: 2}
base_copy = base()
base_copy["hello"] = "bye"
print(f"{base_copy=}") # Output: base_copy={1: 2, 'hello': 'bye'}
print(f"{base_copy[1]=}") # Output: base_copy[1]=2
print(f"{base_copy[10]=}") # Output: KeyError: "Key 10 does not exist in 'self.template_attrs={1: 2, 'hello': 'bye'}'."
I also added support for subscripting and item assignment through __getitem__ and __setitem__ respectively. I hope that this helped a bit with your problem! Feel free to comment on this if I missed what you were asking.
Reasons for edits (May 16th, 2022 at 8:49 PM CST | Approx. 9 hours after original answer)
Fix code based on suggestions by comment from user jsbueno
Handle, in __getitem__, if an instance of class Foo is subscripted with a key that doesn't exist in the dict.
Handle, in __init__, if the type of template_attrs isn't dict (did this based on the fact that you used a dictionary in the body of your question)
*** 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
I have a ModelManager which keeps track of creating and destroying new objects. Here's an example:
class ModelManager:
MAX_OBJECTS = 10
OBJECTS = {} # hash to model object
NUM_OBJECTS = len(OBJECTS) # how to dynamically calculate this?
Every time an object is created it is added to OBJECTS and everytime it is deleted it gets popped from OBJECTS.
How would I properly do the NUM_OBJECTS here? Ideally it should be a classmethod/property to act as a calculation. For doing something like the above, what would be the best way?
I would like to call it as ModelManager.NUM_OBJECTS
Use a computed property
class ModelManager:
#property
def NUM_OBJECTS(self):
return len(self.OBJECTS)
Also, note that OBJECTS will be shared across your class instances because it is a dictionary initialized at class scope. The NUM_OBJECT property requires initializing the class. If you want NUM_OBJECTS to be a property of the class, use one of the solutions suggested here for class properties.
If you would rather be able to call len around your class (in addition to NUM_OBJECTS) - you can overkill with metaclasses:
class ModelManagerMeta(type):
def __len__(cls):
return len(cls.OBJECTS)
def NUM_OBJECTS(cls):
return len(cls.OBJECTS)
class ModelManager(metaclass=ModelManagerMeta):
MAX_OBJECTS = 10
OBJECTS = {} # hash to model object
...
While researching about python class attribute and instance attribute, I came to know that it's not possible to create object attribute outside object methods (or may be class method). Like code below will generate an "NameError" in python.
class test(object):
def __init__(self):
self.lst = []
self.str = 'xyz'
Why python doesn't allow this? I'm not questioning language creator's decision, but any reason behind this. Like, is it technically incorrect or any other disadvantage of this behavior.
You are defining a class, so there is no instance to point to outside methods. Drop the `self:
class test(object):
def __init__(self):
self.lst = []
str = 'xyz'
self points to the instance, not the class. You either need to create an instance and assign directly to attributes (test().str = 'xyz') or you need to be inside a method (when self can actually refer to an instance).
self is not a special name in python, you could use \
class test(object):
def __init__(foo):
foo.lst = []
If you want. Every method of a class gets the instance explicitly passed to it as the first parameter, you can call it whatever you want. Trying to access a parameter outside the scope of the method obviously won't work.
I'm building my own lightweight orm. I'd like to keep instantiated objects in a class variable (perhaps a dictionary). When I request an object (through a class method) like get(id), I'd like to return the object from the instantiated list, or create one if it does not exist.
Is there a way to prevent the instantiation of an object (if its id already exists in the cls list)?
There are two straightforward ways of doing it - and many other ways,as well. One of them, as you suggest, is to write the __new__ method for your objects, which could return an already existing object or create a new instance.
Another way is to use a factory function for your objects - and call this factory function instead of the class - more or less like this:
class _MyClass(object):
pass
def MyClass(*args, **kw):
if not "my_class_object" in all_objects:
all_objects["my_class_object"] = _MyClass(*args, **kw)
return all_objects["my_class_object"]
Just perform explicit check, it is the cleanest method, I believe:
class OrmContainer(object):
objects = {}
#classmethod
def get(cls, id):
if id not in cls.objects:
cls.objects[id] = SomeOtherClass(id)
return cls.objects[id]