I have a few classes defined as below in Python:
class Item:
def __init__(self, name):
self.name = name
class Group:
def __init__(self, name):
self.name = name
self.items = {}
def __getitem__(self, name):
return self.items[name]
def __setitem__(self, name, item):
self.items[name] = item
class Section:
def __init__(self, name):
self.name = name
self.groups = {}
def __getitem__(self, name):
return self.groups[name]
def __setitem__(self, name, group):
self.groups[name] = group
class List:
def __init__(self, name):
self.name = name
self.sections = {}
def __getitem__(self, name):
return self.sections[name]
def __setitem__(self, name, section):
self.sections[name] = section
The pattern of Group, Section and List is similar. Is there a way in Python using MetaClasses to refactor this to avoid code duplication?
Yes - I'd do it using inheritance as well, but instead of having the specific attribute name defined in __init__, would set it as a class attribute. The base could even be declared as abstract.
class GroupBase():
collection_name = "items"
def __init__(self, name):
self.name = name
setattr(self.collection_name, {})
def __getitem__(self, name):
return getattr(self, self.collection_name)[name]
def __setitem__(self, name, item):
getattr(self, self.collection_name)[name] = item
class Section(GroupBase):
collection_name = "groups"
class List(GroupBase):
collection_name = "sections"
Note that more class attributes could be used at runtime, for example
to specify the item type for each collection, and enforce typing inside __setitem__, if needed.
Or, as you asked, it is possible to literally use a string-template system and just use an "exec" statement inside a metaclass to create new classes.
That would be closer to what "templates" are. The class code itself would live inside a string, and the patterns can use normal strign substitution with .format(). The major difference with C++ templates is that the language runtime itself will do the substitution at runtime - instead of compile (to bytecode) time. The exec function actually causes the text templat to be compiled at this point - yes, it is slower than pre-compiled code, but since it is run just once, at import time, that does not make a difference:
group_class_template = """\
class {name}:
def __init__(self, name):
self.name = name
self.{collection_name} = {{}}
def __getitem__(self, name):
return self.{collection_name}[name]
def __setitem__(self, name, item):
self.{collection_name}[name] = item
"""
class TemplateMeta(type):
def __new__(mcls, name, bases, cls_namespace, template):
# It would be possible to run the template with the module namespace
# where the stub is defined, so that expressions
# in the variables can access the namespace there
# just set the global dictionary where the template
# will be exec-ed to be the same as the stub's globals:
# modulespace = sys._getframe().f_back.f_globals
# Othrwise, keeping it simple, just use an empty dict:
modulespace = {}
cls_namespace["name"] = name
exec(template.format(**cls_namespace), modulespace)
# The class is execed actually with no custom metaclass - type is used.
# just return the created class. It will be added to the modulenamespace,
# but special attributes like "__qualname__" and "__file__" won't be set correctly.
# they can be set here with plain assignemnts, if it matters that they are correct.
return modulespace[name]
class Item:
def __init__(self, name):
self.name = name
class Group(metaclass=TemplateMeta, template=group_class_template):
collection_name = "items"
class Section(metaclass=TemplateMeta, template=group_class_template):
collection_name = "groups"
class List(metaclass=TemplateMeta, template=group_class_template):
collection_name = "sections"
And pasting this in the REPL I can just use the created classes:
In [66]: a = Group("bla")
In [67]: a.items
Out[67]: {}
In [68]: a["x"] = 23
In [69]: a["x"]
Out[69]: 23
In [70]: a.items
Out[70]: {'x': 23}
The major drawback of doing it this way is that the template itself is seem just as a string, and the tooling like linters, static type checkers, auto-complete based in static scannng in IDEs, won't work for the templated classes. The idea could be evolved so that templates would be valid Python code, in ".py" files - they can be read as any other file at import time - one'd just need to specify a templating system other than using the built-in str.format so that templates could be valid code. For example, if one defines that names prefixed and ending with a single underscore are names that will be substituted in the template, regular expressions could be used for the name-replacement insteaf of .format.
You could use inheritance:
class Item:
def __init__(self, name):
self.name = name
class Group(Item):
def __init__(self, name):
super().__init__(name)
self._dict = {}
self.items = self._dict
def __getitem__(self, name):
return self._dict[name]
def __setitem__(self, name, item):
self._dict[name] = item
class Section(Group):
def __init__(self, name):
super().__init__(name)
self.groups = self._dict
class List(Group):
def __init__(self, name):
super().__init__(name)
self.sections = self._dict
Another option that is more similar to a templating method could be to use type to dynamically generate your objects:
def factory(cls_name, collection_name='_data'):
def __init__(self, name):
self.name = name
def __getitem__(self, key):
return eval(f'self.{collection_name}[key]')
def __setitem__(self, key, value):
exec(f'self.{collection_name}[key] = value')
attrs = {
'__setitem__': __setitem__,
'__getitem__': __getitem__,
'__init__': __init__,
collection_name: {}
}
exec(f'{cls_name} = type(cls_name, (), attrs)')
return eval(cls_name)
Item = factory('Item')
Group = factory('Group', 'items')
Section = factory('Section', 'groups')
List = factory('List', 'sections')
g = Group('groupA')
s = Section('section_one')
l = List('list_alpha')
g[1] = 10
s['g'] = g
print(g.items, s.groups, l.sections)
{1: 10} {'g': <main.Group object at 0x7fd87bdfecd0>} {}
Related
I have a class as follows:
class A:
def __init__(self):
pass
def add_attr(self, name):
setattr(self, name, 'something')
How do I define custom setter, getter for self.name? I cannot use __setattr__, __getattribute__ because that will change the behaviour of add_attr too.
EDIT: the users of this class will add arbitrary number of attributes with arbitrary names:
a = A()
a.add_attr('attr1')
a.add_attr('attr2')
I want custom behavior for only these user added attributes.
Building off #Devesh Kumar Singh’s answer, I would implement it in some way like this:
class A:
def __init__(self):
self.attrs = {}
def __setattr__(self, key, value):
if key in self.attrs:
self.set_attr(key, value)
else:
object.__setattr__(self, key, value)
def __getattribute__(self, key):
if key in self.__dict__.get(attrs, {}):
return self.__dict__['get_attr'](self, key)
return object.__getattribute__(self, key)
def get_attr(self, key):
r = self.attrs[key]
# logic
return r
def set_attr(self, key, value):
# logic
self.attrs[key] = value
def add_attr(self, key, value=None):
self.attrs[key] = value
add_attr is only used to initialise the variable the first time. You could also edit __setattr__ to set all new attributes in the self.attrs rather than self.__dict__
Custom getter and setter logic? That's what a property is made for. Usually these are used to magically mask function calls and make them look like attribute access
class MyDoubler(object):
def __init__(self, x):
self._x = x
#property
def x(self):
return x * 2
#x.setter
def x(self, value):
self._x = value
>>> md = MyDoubler(10)
>>> md.x
20
>>> md.x = 20
>>> md.x
40
>>> md._x
20
But there's no rule saying you can't abuse that power to add custom behavior to your getters and setters.
class A(object):
def __init__(self):
pass
#staticmethod
def default_getter_factory(name):
def default_getter(self):
return self.name
return default_getter
#staticmethod
def default_setter_factory(name):
def default_setter(self, value):
setattr(self, name, value)
return default_setter
def add_attr(self, name, getterfactory=None, setterfactory=None):
private_name = f"_{name}"
if getterfactory is None:
getterfactory = self.__class__.default_getter_factory
if setterfactory is None:
setterfactory = self.__class__.default_setter_factory
getter, setter = getterfactory(private_name), setterfactory(private_name)
getter = property(getter)
setattr(self.__class__, name, getter)
setattr(self.__class__, name, getter.setter(setter))
That said this is all a bit silly, and chances are that whatever it is you're trying to do is a thing that shouldn't be done. Dynamic programming is all well and good, but if I were to review code that did this, I would think very long and hard about alternative solutions before approving it. This reeks of technical debt to me.
One possibility I could think of is to have a dictionary of dynamic attributes, and set and get the dynamic attributes using the dictionary
class A:
def __init__(self):
#Dictionary of attributes
self.attrs = {}
#Set attribute
def set_attr(self, name):
self.attrs[name] = 'something'
#Get attribute
def get_attr(self, name):
return self.attrs.get(name)
a = A()
a.set_attr('var')
print(a.get_attr('var'))
The output will be something
Or an alternate is to use property decorator to add arguments explicitly outside the class, as described here
class A:
def __init__(self):
pass
a = A()
#Add attributes via property decorator
a.attr_1 = property(lambda self: self.attr_1)
a.attr_2 = property(lambda self: self.attr_2)
#Assign them values and print them
a.attr_1 = 4
a.attr_2 = 6
print(a.attr_1, a.attr_2)
The output will be 4 6
I am gonna answer my own question just for reference. This is based on others' answers here. The idea is to use default __setattr__ and __getattribute__ on attributes not added through add_attr.
class A:
def __init__(self):
self.attrs = {}
def add_attr(self, name):
self.attrs[name] = 'something'
def __getattribute__(self, name):
try:
object.__getattribute__(self, 'attrs')[name] # valid only if added by user
# custom logic and return
except (KeyError, AttributeError):
return object.__getattribute__(self, name)
def __setattr__(self, name, val):
# similar to __getattribute__
Is it a good style to create classes like that ? I read the PEP8 document but I didn't saw any good example. If not how is it a proper way ? Thanks for any answers.
class Zone:
def __init__(self, index=None, name=None):
self._index = index
self._name = name
#property
def index(self):
return self._index
#property
def name(self):
return self._name
#index.setter
def index(self, index):
self._index = index
#name.setter
def name(self, name):
self._name = name
Your setters and getters don't do anything. With your implementation, the user of this class does this:
z = Zone()
z.name = 'foo'
print(z.name)
Compare to this implementation:
class Zone:
def __init__(self, index=None, name=None):
self.index = index
self.name = name
z = Zone()
z.name = 'foo'
print(z.name)
It works exactly the same with a lot less code.
Unless you do anything in your setters and/or getters, you don't need them.
If what you intend doing is encapsulating your data and setting it with setters and getting it with getters, then what you did will not be helpful. you declared the _name and _index as protected, it does not mean it cannot be accessed by extenal functions, so functions outside the class can easily access and change them, making your getter and setter to be useless.
However, you can declare them as private by using one additional underscore in front, so that your property class will be removed and then the setters class will be useful, it will no longer be accessed by external functions.
class Zone:
def __init__(self,index=None,name=None):
self.__index = index
self.__name = name
def index(self, index):
self.__index = index
def name(self, name):
self.__name = name
def get_name(self):
return self.__name
zone=Zone()
zone.name('ben')
print(zone.get_name())
>>>ben
print(zone.__name)
>>> AttributeError: 'Zone' object has no attribute '__name'
I'm creating an event system which uses the following class for events:
class Event(set):
def __init__(self, name, iterable=()):
super().__init__(iterable)
self.name = name
def __iadd__(self, listener):
self.add(listener)
return self
def __isub__(self, listener):
self.remove(listener)
return self
def fire(self, **eargs):
for listener in self:
listener(**eargs)
Now I'm trying to create some kind of a dict that would automatically create the events in its __init__ like so:
class EventDict(dict):
def __init__(self, prefix, *event_names):
super().__init__({
name: Event('%s.%s' % (prefix, name))
for name in event_names
})
And here's an example of usage:
class Player:
def __init__(self, name):
self._name = name
self.events = EventDict('Player', 'change_name')
#property
def name(self):
returns self._name
#name.setter
def name(self, value):
old_name = self.name
self.name = value
self.events['change_name'].fire(player=self, old_name=old_name)
Now the problem I'm facing is subclassing.
If I were to subclass my Player class to include also health attribute, I can't use the same way of creating an event dict, cause it would override the existing one and I couldn't access change_name anymore.
So I'm trying to find a way where I can just do something like this (ideal solution):
class Player:
events = EventDict('Player', 'change_name')
class Player2(Player):
events = EventDict('Player2', 'attack', 'kill')
p2 = Player2()
p2.events['change_name'] += my_event_listener # Still access Player class's events
Would something like this be possible?
I know I can do:
class Player2(Player):
def __init__(self, name):
super().__init__()
self.events.update(...)
But it's not the same :P
I think what you want is:
class Player:
EVENTS = ('change_name',)
def __init__(self, name):
self._name = name
self.events = EventDict(
self.__class__.__name__,
*self.EVENTS,
)
...
Then all you need in Player2 is:
class Player2(Player):
EVENTS = Player.EVENTS + ('attack', 'kill')
and the inherited __init__ will work fine.
Stop using EventDict.
The class itself has its own dict which supports inheritance like that.
class Player:
def __init__(self, name):
self._name = name
self.change_name_event = Event('Player.change_name')
class Player2(Player):
def __init__(self, name):
super().__init__(name)
self.attack_event = Event('Player2.attack')
self.kill_event = Event('Player2.kill')
All the events from the subclasses will be added no matter what.
I noticed that maybe you wanted to make it obvious that they're events, so I added 'event' to the names of the fields, but you don't need to if you don't want to.
If you wanted it so that the prefix is the same throughout, then you'd change the strings from something like 'Player.change_name' to self.__class__.__name__ + '.change_name'. That way, it always gets whatever the actual class for the object is. This is part of what #jonrsharpe's solution is trying to get at.
If you wanted to make it so others could add more events dynamically, they can simply do a line like playerObj.my_new_event = Event('Player.my_new_event') or you could provide a nice method in the Player class to make their lives easier:
def add_event(self, event_name):
setattr(self, event_name, Event(self.__class__.__name__ + '.' + event_name)
I was recently writing a definition for a pretty basic data class in Python and I came up with the following:
class A:
def __init__(self, **kwargs):
self.__a1 = kwargs.get('some_value', -1)
#property
def a1(self):
return self.__a1
#a1.setter
def a1(self, new_a1):
self.__a1 = new_a1
And it goes on. In this case, the value -1 could be replaced with a variety of "null" values: -1, "", [], etc., and some_value comes from an Enum I defined earlier.
Because the class definition contains several of these property definitions, and they're all very "same-y", I'd like to write a function to do this for me. I'm pretty sure it's possible in Python but I've never tried it so I was hoping for some pointers.
Assuming you want to simplify the repetitive property definitions, you can use a generic descriptor to simplify this significantly:
class ProtectedAttribute(object):
"""Basic descriptor functionality for a protected attribute.
Args:
name (str): The name of the attribute to back the descriptor
(usually the name the descriptor is assigned to with a single
additional leading underscore).
"""
def __init__(self, name, **kwargs):
self.name = name
def __get__(self, obj, typ):
return getattr(obj, self.name)
def __set__(self, obj, value):
setattr(obj, self.name, value)
def __delete__(self, obj):
delattr(obj, self.name)
Now you can just do:
class A(object):
a1 = ProtectedAttribute('__a1')
def __init__(self, **kwargs):
self.a1 = kwargs.get("some_value", -1)
Note also the use of dict.get to simplify __init__.
I have class SomeClass with properties. For example id and name:
class SomeClass(object):
def __init__(self):
self.__id = None
self.__name = None
def get_id(self):
return self.__id
def set_id(self, value):
self.__id = value
def get_name(self):
return self.__name
def set_name(self, value):
self.__name = value
id = property(get_id, set_id)
name = property(get_name, set_name)
What is the easiest way to list properties? I need this for serialization.
property_names=[p for p in dir(SomeClass) if isinstance(getattr(SomeClass,p),property)]
import inspect
def isprop(v):
return isinstance(v, property)
propnames = [name for (name, value) in inspect.getmembers(SomeClass, isprop)]
inspect.getmembers gets inherited members as well (and selects members by a predicate, here we coded isprop because it's not among the many predefined ones in module inspect; you could also use a lambda, of course, if you prefer).