python `getattr` like function that accepts dotted string - python

I would like to implement a function which is similar to getattr but will accept a dotted string and traverse through each attributes.
def getattr_multiple_level(obj, attr_string):
attr_names = attr_string.split('.')
next_level = obj
for attr_name in attr_names:
next_level = getattr(next_level, attr_name)
return next_level
class Test():
def make_name(self, pre, suffix=""):
return str(pre) + "_my_office_" + suffix
p = Test()
p.room = Test()
p.room.office = Test()
attr = getattr_multiple_level(p, 'room.office.make_name')
Is there already a built-in way to do this? Or what improvements can be made in above code to handle all possible exceptions and edge cases?

Yes, there is "build-in way". You can use property decorator. https://docs.python.org/2/library/functions.html#property
class Author(object):
def __init__(self, full_name):
self.full_name = full_name
class Book(object):
def __init__(self, name):
self.name = name
#property
def author(self):
return Author("Philip Kindred Dick")
class Library(object):
#property
def book_ubik(self):
return Book("ubik")
library = Library()
print(library.book_ubik.name)
print(library.book_ubik.author.full_name)
Result:
grzegorz#grzegorz-GA-78LMT-USB3:~/tmp$ python3 propery_test.py
ubik
Philip Kindred Dick

Related

MetaClasses to remove duplication of class definitions

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>} {}

Passing class parameters python

I have 2 classes
class Robot1:
def __init__(self, name):
self.name = name
def sayHi(self):
return "Hi, I am " + self.name
class Robot2:
def __init__(self, name):
self.name = name
def sayHello(self):
return "Hello, I am " + self.name
robot_directory = {1: Robot1(), 2: Robot2()}
def object_creator(robo_id, name):
robot_object = robot_directory[robo_id]
return robot_object
But I don't know how to pass the variable name while instantiating the class on the line robot_object = robot_directory[robo_id]. How can I pass the variable?
You are storing already-created instances in the dictionary. Store the class itself instead:
# ...
robot_directory = {1: Robot1, 2: Robot2}
def object_creator(robo_id, name):
robot_class = robot_directory[robo_id]
# Here, the object is created using the class
return robot_class(name)
Obviously, this requires that all your robot classes have the same __init__ parameters.
Going further, you might want to look into inheritance and use a common base class for your robots.
maybe you can try
class Robot1:
def __init__(self):
pass
def set_name(self, name):
return "Hi, I am " + name
class Robot2:
def __init__(self):
pass
def set_name(self, name):
return "Hello, I am " + name
robot_directory = {1: Robot1(), 2: Robot2()}
def object_creator(robo_id, name):
robot_object = robot_directory[robo_id]
return robot_object.set_name(name)

How to define a method so that return the instance of the current class not of the class where it was inherited in Python?

I am trying Overloading an operator forcing it to return an object of the same instance of the current class not the parent class where the method was overloaded.
class Book:
def __init__(self,name,pages):
self.name=name
self.pages=pages
def __add__(self,other):
return Book(self.name,(self.pages + other.pages))
class Encyclopedia(Book):
def __init__(self,name,pages):
Book.__init__(self,name,pages)
a=Encyclopedia('Omina',234)
b=Encyclopedia('Omnia2',244)
ab=a+b
print ab
Out: <__main__.Book instance at 0x1046dfd88>
For instance in this case I would like to return an Encycolpedia instance (not a Book instance) without overloading another time the operator __add__ with the same line with Encyclopedia instead of Book I have tried:
return self(self.name,(self.pages + other.pages))
But it doesn't work.
What if the Class Enclcopedia has another attribute:
class Encyclopedia(Book):
def __init__(self,name,pages,color):
Book.__init__(self,name,pages)
self.color=color
You could utilize self.__class__ instead of casting to Book. Your original add function should look like:
def __add__(self,other):
return self.__class__(self.name,(self.pages + other.pages))
You would need to do it something like this, which overloads the base class's methods (in this case, generally by calling them first and then doing additional processing on the result — although that's not a requirement):
class Book(object):
def __init__(self, name, pages):
self.name = name
self.pages = pages
def __add__(self, other):
return Book(self.name, self.pages+other.pages)
def __str__(self):
classname = self.__class__.__name__
return '{}({}, {})'.format(classname, self.name, self.pages)
class Encyclopedia(Book):
def __init__(self, name, pages, color):
Book.__init__(self, name, pages)
self.color = color
def __add__(self, other):
tmp = super(Encyclopedia, self).__add__(other)
return Encyclopedia(tmp.name, tmp.pages, self.color+other.color)
def __str__(self):
classname = self.__class__.__name__
return '{}({!r}, {}, {!r})'.format(classname, self.name, self.pages,
self.color)
a = Encyclopedia('Omina', 234, 'grey')
b = Encyclopedia('Omnia2', 244, 'blue')
ab = a+b
print(ab) # -> Encyclopedia('Omina', 478, 'greyblue')

Proxy class that isn't yet defined

Consider a registry with a dict-like interface. Each key is a string name and each value is a class. Using it in this order works:
registry['foo'] = FooClass
cls = registry['foo']
instance = cls
But in this order it wouldn't of course:
cls = registry['foo']
registry['foo'] = FooClass
instance = cls()
To support the second use-case, I implemented a class constructor wrapper in a function but it "denaturates" the class. I mean that this won't work:
cls = registry['foo']
registry['foo'] = FooClass
issubclass(cls, FooClass)
I'd like to support that third case, so I'm looking for a better way to proxy the class registry items.
Interesting problem, I would try something like this:
from abc import ABCMeta
class Registry(object):
def __init__(self):
self._proxies = {}
self._classes = {}
def resolve(self, name):
try:
return self._classes[name]
except KeyError:
raise KeyError('Cannot resolve "%s".'
' Class not registered yet.' % name)
def __getitem__(self, name):
"""Return a proxy class bound to `name`."""
if name not in self._proxies:
self._proxies[name] = make_proxy(lambda: self.resolve(name))
return self._proxies[name]
def __setitem__(self, name, val):
"""Store a class for `name`."""
self._classes[name] = val
def make_proxy(resolve):
"""
Return a proxy class.
:param resolve: a function that returns the actual class
"""
class ProxyMeta(ABCMeta):
"""
Custom meta class based on ABCMeta that forwards various checks
to the resolved class.
"""
def __eq__(self, y):
return resolve() == y
def __repr__(self):
return repr(resolve())
def __str__(self):
return str(resolve())
class Proxy(object):
"""
The actual proxy class.
"""
__metaclass__ = ProxyMeta
def __new__(cls, *args, **kwargs):
"""Calling this class returns an instance of the resolved class."""
return resolve()(*args, **kwargs)
#classmethod
def __subclasshook__(cls, subclass):
"""issubclass() overwrite."""
return issubclass(resolve(), subclass)
return Proxy
>>> registry = Registry()
>>> List = registry['list']
>>> List
KeyError: 'Cannot resolve "list". Class not registered yet.'
>>> registry['list'] = list
>>> List
<type 'list'>
>>> issubclass(List, List)
True
>>> issubclass(list, List)
True
>>> List == list
True
>>> List()
[]
>>> registry['list'] = tuple
>>> List()
()

Dynamically mixin a base class to an instance in Python

Is it possible to add a base class to an object instance (not a class!) at runtime? Something along the lines of how Object#extend works in Ruby:
class Gentleman(object):
def introduce_self(self):
return "Hello, my name is %s" % self.name
class Person(object):
def __init__(self, name):
self.name = name
p = Person("John")
# how to implement this method?
extend(p, Gentleman)
p.introduce_self() # => "Hello, my name is John"
This dynamically defines a new class GentlePerson, and reassigns p's class to it:
class Gentleman(object):
def introduce_self(self):
return "Hello, my name is %s" % self.name
class Person(object):
def __init__(self, name):
self.name = name
p = Person("John")
p.__class__ = type('GentlePerson',(Person,Gentleman),{})
print(p.introduce_self())
# "Hello, my name is John"
Per your request, this modifies p's bases, but does not alter p's original class Person. Thus, other instances of Person are unaffected (and would raise an AttributeError if introduce_self were called).
Although it was not directly asked in the question, I'll add for googlers and curiosity seekers, that it is also possible to dynamically change a class's bases but (AFAIK) only if the class does not inherit directly from object:
class Gentleman(object):
def introduce_self(self):
return "Hello, my name is %s" % self.name
class Base(object):pass
class Person(Base):
def __init__(self, name):
self.name = name
p = Person("John")
Person.__bases__=(Gentleman,object,)
print(p.introduce_self())
# "Hello, my name is John"
q = Person("Pete")
print(q.introduce_self())
# Hello, my name is Pete
Slightly cleaner version:
def extend_instance(obj, cls):
"""Apply mixins to a class instance after creation"""
base_cls = obj.__class__
base_cls_name = obj.__class__.__name__
obj.__class__ = type(base_cls_name, (base_cls, cls),{})
Although it's already answered, here is a function:
def extend(instance, new_class):
instance.__class__ = type(
'%s_extended_with_%s' % (instance.__class__.__name__, new_class.__name__),
(instance.__class__, new_class),
{},
)

Categories

Resources