![]() |
iterate over class properties? - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: General Coding Help (https://python-forum.io/forum-8.html) +--- Thread: iterate over class properties? (/thread-12462.html) Pages:
1
2
|
iterate over class properties? - NobahdiAtoll - Aug-26-2018 is there a way to iterate over python property descriptors rather than __dict__ attributes?import datetime from datetime import date from datetime import datetime as dt class videofile: def __init__(self): self._date = date.today() self._dateadded = dt.now() self._title = 'New Title' self._plot = '' @property def title(self): return self._title @title.setter def title(self, value): self._title = value @property def plot(self): return self._plot @plot.setter def plot(self, value): self._plot = value @property def dateadded(self): return self._dateadded @dateadded.setter def dateadded(self, value): self._dateadded = value class movie(videofile): def __init__(self): super(movie,self).__init__() self._outline = '' @property def outline(self): return self._outline @outline.setter def outline(self, value): self._outline = value @property def premiered(self): return self._date @premiered.setter def premiered(self, value): self._date = value class tvepisode(videofile): def __init__(self): super(tvepisode,self).__init__() self._season = -1 self._episode = -1 self._absolute = -1 @property def season(self): return self._season @season.setter def season(self, value): self._season = value @property def episode(self): return self._episode @episode.setter def episode(self, value): self._episode = value @property def absolute(self): return self._absolute @absolute.setter def absolute(self, value): self._absolute = value @property def aired(self): return self._date @aired.setter def aired(self, value): self._date = valueI want to iterate over the user values (to eventually shove them into an xml) which i know i can do by iterating over the instance's dictionary movieitem = movie() movieitem.title = 'Avengers: Infinity War' movieitem.premiered = date(2018,4,27) movieitem.plot = 'The Avengers and their allies must be willing to sacrifice all in an attempt to defeat the powerful Thanos before his blitz of devastation and ruin puts an end to the universe.' for attribName, attribValue in movieitem.__dict__.items(): if isinstance(attribValue, str): print(attribName + ': ',attribValue ) elif isinstance(attribValue,date): print(attribName + ': ',attribValue.strftime('%Y-%m-%d')) elif isinstance(attribValue,dt): print(attribName + ': ',attribValue.strftime('%Y-%m-%d %H:%M:%S')) else: print(attribName + ': ', str(attribValue))the code above produces: .the problem is that some of the user values are reused for different property names which is how i want them defined in the output... so is there a way to iterate over only properties instead of __dict__ attributes for instance, the self._date is used to for different properties in movies (premiered) and tvepisodes (aired)
RE: iterate over class properties? - Gribouillis - Aug-26-2018 (Aug-26-2018, 10:44 AM)NobahdiAtoll Wrote: is there a way to iterate over only properties instead of __dict__ attributesThere are ways of course, I give you a solution below. Before I do that, let me say that it is not a good way to handle your problem because properties are an implementation detail of your code and this should not be mixed with the problem's real world model. I mean that the list of items that you want to output should not be defined by the details of the implementation. That said, the code below defines a function add_property_names() which adds a property_names() class method to a class and its subclasses. This method allows you to list the names of the class' properties.In your example, you would add the code add_property_names(videofile)Then you could do for name in movieitem.property_names(): print(name, getattr(movieitem, name))Here is the code import inspect def add_property_names(cls): def get_names(cls): for tp in inspect.getmro(cls): for name, value in vars(tp).items(): if isinstance(value, property): yield name def property_names(cls): register = add_property_names.register if not cls in register: register[cls] = sorted(set(get_names(cls))) return register[cls] cls.property_names = classmethod(property_names) return cls add_property_names.register = {} if __name__ == '__main__': class A: pass class B(A): @property def foo(self): return 0 class C(B): @property def bar(self): return self._bar @bar.setter def bar(self, value): self._bar = value add_property_names(A) a = A() print(a.property_names()) c = C() print(c.property_names()) print(C.property_names()) RE: iterate over class properties? - NobahdiAtoll - Aug-26-2018 (Aug-26-2018, 03:21 PM)Gribouillis Wrote: That said, the code below defines a function I appreciate you pointing me to inspect that opens up a lot of opportunities.for the record i went with a slight variation of your method that yields a tuple of all properties and their values I use this function globally import inspect def getproperties(t, obj): for tp in inspect.getmro(t): for name, value in vars(tp).items(): if isinstance(value,property): yield name, getattr(obj, name)so now i can just do this for attribName, attribValue in getproperties(movie,movieitem): if isinstance(attribValue, str): print(attribName + ':',attribValue ) elif isinstance(attribValue, dt): print(attribName + ':', attribValue.strftime('%Y-%m-%d %H:%M:%S')) elif isinstance(attribValue,date): print(attribName + ':',attribValue.strftime('%Y-%m-%d')) else: print(attribName + ':', str(attribValue))which outputs it exactley what i want for any class i provide for the record i'm not using this for "real world" output I'm eventually going to use it for building xml from objects and its easier to iterate over properties, for me right now, cause i don't know all the tricks of python yet.
RE: iterate over class properties? - Gribouillis - Aug-26-2018 (Aug-26-2018, 06:20 PM)NobahdiAtoll Wrote: I use this function globallyIt works. The drawback is that you're going to recompute the list of properties for each instance for which you're calling this function. I only attempted to compute the list only once for each class. RE: iterate over class properties? - NobahdiAtoll - Aug-26-2018 (Aug-26-2018, 07:02 PM)Gribouillis Wrote: It works. The drawback is that you're going to recompute the list of properties for each instance for which you're calling this function. I only attempted to compute the list only once for each class. So does your way import the function into every class? If so your way is better. Is there a way to extend classes globally so that you can add functions to any class you inport into your code? I only did it my way because I didn't understand what is going on completely with yours, if you can help me understand it better (exactley what's going on) i'd like to have a variation of my function available within every class, but I'm a bit squemish about not exactley knowing what's going on in my own code. I'd rather you teach me how to fish than give me a fish. I find the documentation for python isn't exactly targeted towards people like me who need a bit more explaining to know what's going on, it tends to use terminology that i don't understand and when trying to understand the terminology it references stuff i don't know much or anything about so i have to look that up and i get frustrated. Now there are sites that walk you hand in hand through python coding with easily understandable examples and dumbed-down terminology.. but only for the very simple parts of python. If you would have not pointed me to inspect, I'd have never found it. RE: iterate over class properties? - Gribouillis - Aug-26-2018 It would perhaps be better to unpack my function a little. I don't claim it is the best way to do this, but I'm going to explain the following code import inspect def _get_names(cls): for tp in inspect.getmro(cls): for name, value in vars(tp).items(): if isinstance(value, property): yield name _register = {} def property_names(cls): if cls not in _register: _register[cls] = sorted(set(_get_names(cls))) return _register[cls] def add_property_names(cls): cls.property_names = classmethod(property_names) return cls if __name__ == '__main__': class A: pass class B(A): @property def foo(self): return 0 class C(B): @property def bar(self): return self._bar @bar.setter def bar(self, value): self._bar = value add_property_names(A) a = A() print(a.property_names()) c = C() print(c.property_names()) cc = C() print(cc.property_names()) RE: iterate over class properties? - NobahdiAtoll - Aug-26-2018 well hell i just noticed your function functions inside a function... i didn't even know you dould do that RE: iterate over class properties? - Gribouillis - Aug-26-2018 The most important call is add_property_names(A) if you look at what it does in line 18, it adds a new classmethod named property_names to class A. Everything happens as if we had writtenclass A: @classmethod def property_names(cls): ...It means that we can now call A.property_names() or A().property_names() or even C.property_names() because C is a subclass of A.Now look at this method at line 11. It looks in a global dictionary _register if we have already stored a list of property names _register[A] for class A. If not it computes this list by calling _get_names(A) . This way the list of properties is only computed once for each class.Last subtlety: add_property_names(cls) returns cls . This allows us to use it as a class decorator and write@add_property_names class A: passso that we don't need to call explicitly add_property_names after the definition of class A.
RE: iterate over class properties? - NobahdiAtoll - Aug-26-2018 that is so much easier to understand. so this registers a class method... if i understand class methods they don't need an instance to work right? if i wanted to add my method to return a tuple of property_name (using your function and property_value which would require an instance, would that be practical to write into the class or would i need to do the same with this function and register them together?
RE: iterate over class properties? - Gribouillis - Aug-26-2018 You could add this to my code def property_summary(self): return {k: getattr(self, k) for k in self.property_names()} def add_property_summary(cls): add_property_names(cls) cls.property_summary = property_summary return clsThen instead of decorating videofile with add_property_names , decorate it with add_property_summary if __name__ == '__main__': @add_property_summary class videofile: ...you can then get a dict with movieitem.property_summary() |