How to retrieve attributes? - python

class character():
class personalized:
def __init__(self, name, age, height, width, hair_color, eye_color):
# I want to do this
for attr in personalized:
personalized.attr = attr
#instead of this
personalized.name = name
personalized.age = age
personalized.height = height
If I am using classes with a lot of attributes I don't want to have to set it equal to a variable every time because it will take up a lot of space. Is there a way to write it like I did above but actually works. In essence I don't know how to retrieve attributes from the __init__ function.

I would recommend using dataclasses for this. In your case you would just add:
from dataclasses import dataclass
#dataclass
class personalized:
name: str
age: int
height: int
width: int
hair_color: str
eye_color: str
This will auto-construct a init for you with self-assigned attributes

You could use the __set__(..) Function (https://docs.python.org/3/howto/descriptor.html), but I do not suggest this because:
From a readability perspective this will harder to maintain over the long term (code is more often read than written)
everytime you want to access such an entry you first of all have to check if the descriptor/attribute is availible, thus making your down the road code worse.
see:
How to know if an object has an attribute in Python

Using vars() and setattr() can do what you want, but I recommend dataclasses as in the other answer if using Python 3.7+.
vars() returns a dictionary of local variables. At the top of __init__ dictionary contains self and any parameters as keys and along with their values.
setattr() will set an attribute value on an object.
class Personalized:
def __init__(self, name, age, height, width, hair_color, eye_color):
for key,value in vars().items():
if key != 'self':
setattr(self, key, value)
p = Personalized('name',5,10,12,'black','blue')
print(p.name,p.age,p.height,p.width,p.hair_color,p.eye_color)
Output:
name 5 10 12 black blue

Related

What is the purpose of sort_index of a Dataclass in Python?

I am learning about Dataclasses but I am confused on the purpose of sort_index and how it actually works.
I can't seem to find any valuable information on it. The official Python documentation doesn't mention it, which is mind boggling.
Here is an example:
#dataclass(order=True)
class Person:
sort_index: int = field(init=False, repr=False)
name: str
age: int
weight: int = 190
def __post_init__(self):
self.sort_index = self.weight
So, what is the purpose of sort_index? What is it used for? When do I use it?
Thanks again for taking the time to answer my question. I am new to Python.
Setting a sort_index attribute (or indeed, any identifier—the name is irrelevant) in the __post_init__ method appears to be the value on which comparisons are performed.
There is an implicit setting of the comparison methods (__lt__, __gt__, etc--read about dunder methods if unfamiliar), using the attributes provided in the __post_init__ method first, and if required, the remaining attributes for resolution.
Class constructor
from dataclasses import dataclass, field
#dataclass(order=True)
class Person:
sort_index: int = field(init=False)
age: int
def __post_init__(self):
self.sort_index = self.age
first example—attribute age is equal:
>>> p1 = Person(age=10)
>>> p2 = Person(age=10)
>>> p1 == p2
True
Second example—age is greater:
>>> p1 = Person(age=10)
>>> p2 = Person(age=20)
>>> p2 > p1
True
More complex example:
from dataclasses import dataclass, field
#dataclass(order=True)
class Person:
foo: int = field(init=False, repr=False)
bar: int = field(init=False, repr=False)
name: str
age: int
weight: int = 190
def __post_init__(self):
self.foo = self.weight
self.bar = self.age
>>> p1 = Person('p1', 10)
>>> p2 = Person('p1', 11)
>>> p2 > p2
True
Reason
foo (weight) is equal for both instances, so comparison is done on bar (age)
Conclusion
The comparisons can be arbitrarily complex, and identifiers are not important.
I highly recommend this video on dataclasses, by ArjanCodes.
Apart from the video, here's a github link to example dataclass code (from the same video).
Hope this helped—I just learned about dataclasses myself.
Finally I've found the simple truth about that.
First, 'sort_index' or whatever you want to call this attribute, in not usefull unless you need to sort the class depending on an attribute defined after the init is done (then defined in the post_init).
All the tricky behaviour comes from how #dataclasse(order=True) works.
It is not intended to make direct comparisons like var1 > var2, but it is used to sort your objects, if, lets say you store them into an iterable that you can sort.
And this sorting is done like that (objects must be instances from the same class of course):
compare the first attribute to sort the objects
in case of equality -> compare with the second attribute, etc...
So, the order the attributes are wrote matters. And that is why one may use a 'sort_index' simply to put this attribute in the first place even though it is not defined in the init, but after the init.
(I've found a good explanation in this video)
#dataclass(order=True)
class Person:
sort_index: int = field(init=False) # <- not defined yet
age: int
name: str
def __post_init__(self):
self.sort_index = self.age # <- definition's here
# if you try this:
print(person_1 == person_2)
# and get 'True', it means that all the values of the attributes of person_1
# and person_2 are strictly the same, not only 'sort_index'
In this example, the first sorting attribute is sort_index which is also the age, it is not a very good example. A better attribute could be an autogenerated id given after the init of the object, but even then, it would be easier to do:
#dataclass(order=True)
class Person:
id: int = field(init=False, default_factory=get_an_id_function)
age: int
name: str
# Where get_an_id_function is a function that returns an id

How to retain uniqueness of elements inside Set of user-defined objects after changing attribute of object

How can I retain Uniqueness feature of Set for modifying attributes of user-defined instance after adding them into the Set?
like in the code below:
both Person "Jack" and "John" are different in term of equality "Name" . So they both are added into the set
but if I change Person "Jack" name to "John, then the 2 instance jack and john will be equal
however my Set doesn't reflect that. They still consider those 2 instances are different
Note: this leads to potential bug when someone accidentally modifies the user-defined instances after they have been added into the set
Do we have a way to refresh the Set or how i can avoid this issue?
class Person:
def __init__(self, name):
self.name = name
def __eq__(self, other):
return self.name == other.name
def __hash__(self):
return hash(self.name)
jack = Person("Jack")
john = Person("John")
set1 = {jack, john}
jack.name = "John"
print(set1) // return 2 instance instead of 1. This is undesired behavior because now both jack & john are equal
You should only use sets of immutable objects or references. See Python docs:
Having a __hash__() implies that instances of the class are immutable.
The Person objects in your set are mutable but you have implemented your own hash and equality functions that get around this, bypassing safety, as you have pointed out.
I think it's fine to define custom hash and equality functions but they should always return the same thing no matter what you do to the things they reference: e.g., using an ID or memory address to hash.
I suggest one of two options, with a strong preference on the first:
Option A: Immutable Person
Make Person immutable when constructed. My favourite way of doing this is with a dataclass:
from dataclasses import dataclass
#dataclass(frozen=True)
class Person:
name: str
jack = Person("Jack")
john = Person("John")
# Note you don't need to define your own hash method.
set1 = {jack, john}
# This will fail:
jack.name = "Jaques"
# Consider the need for this. But if you have, say, a lot of different
# fields on the Person and want to just change one or a few, try:
import dataclasses
jaques = dataclasses.replace(jack, {"name": "Jaques"})
# But note this is a different object. The set is still the same as before.
# You need to remove "jack" from the set and add "jaques" to it.
Option B: Recalculate the Set
I should note that I don't think this is a good idea, but you could simply run:
set1 = {jack, john}
...again, and it will recalculate the set.
You created two different object and if you print set1 you'll get something like:{<__main__.Person object at 0x7f8dfbfc5e10>, <__main__.Person object at 0x7f8dfbfe2a10>}
Even though their attribute names are different, they're still two different objects saved in different memory spaces. That's why you have the unexpected behavior of still having both of them when you put them into a set!
When you do jack.name = "John" you're only changing the attribute self.name.
In order to get the outcome you wanted you have to do: set1 = {jack.name, john.name}
It'll return you {'John'}

Adding fields to NamedTuple from previous fields

Let's say I want to store some information about a conference schedule with a presentation time and a pause time. I can do this in a NamedTuple.
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
However, if I also want to store how much each block would take such that t_each = t_pause + t_present, I can't just add it as an attribute:
class BlockTime(NamedTuple):
t_present: float
t_pause: float
# this causes an error
t_each = t_present + t_pause
What is the correct way to do this in Python? If I make an __init__(self) method and store it as an instance variable there, but it would then be mutable.
In case it would be okay that it's not really stored but calculated dynamically you could use a simple property for it.
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
#property
def t_each(self):
return self.t_present + self.t_pause
>>> b = BlockTime(10, 20)
>>> b.t_each # only available as property, not in the representation nor by indexing or iterating
30
That has the advantage that you can never (not even accidentally) store a wrong value for it. However at the expense of not actually storing it at all. So in order to appear as if it were stored you'd have to at least override __getitem__, __iter__, __repr__ which is likely too much trouble.
For example the NamedTuple approach given by Patrick Haugh has the downside that it's still possible to create inconsistent BlockTimes or lose parts of the namedtuple convenience:
>>> b = BlockTime.factory(1.0, 2.0)
>>> b._replace(t_present=20)
BlockTime(t_present=20, t_pause=2.0, t_each=3.0)
>>> b._make([1, 2])
TypeError: Expected 3 arguments, got 2
The fact that you actually have a "computed" field that has to be in sync with other fields already indicates that you probably shouldn't store it at all to avoid inconsistent state.
You can make a classmethod that builds BlockTime objects
class BlockTime(NamedTuple):
t_present: float
t_pause: float
t_each: float
#classmethod
def factory(cls, present, pause):
return cls(present, pause, present+pause)
print(BlockTime.factory(1.0, 2.0))
# BlockTime(t_present=1.0, t_pause=2.0, t_each=3.0)
EDIT:
Here's a solution using the new Python 3.7 dataclass
from dataclasses import dataclass, field
#dataclass(frozen=True)
class BlockTime:
t_present: float
t_pause: float
t_each: float = field(init=False)
def __post_init__(self):
object.__setattr__(self, 't_each', self.t_present + self.t_pause)
Frozen dataclasses aren't totally immutable but they're pretty close, and this lets you have natural looking instance creation BlockTime(1.0, 2.0)
Well.. You cant override __new__ or __init__ of a class whose parent is NamedTuple. But you can overide __new__ of a class, inherited from another class whose parent is NamedTuple.
So you can do something like this
from typing import NamedTuple
class BlockTimeParent(NamedTuple):
t_present: float
t_pause: float
t_each: float
class BlockTime(BlockTimeParent):
def __new__(cls, t_present, t_pause):
return super().__new__(cls, t_present, t_pause, t_present+ t_pause)
b = BlockTime(1,2)
print (b)
# BlockTime(t_present=1, t_pause=2, t_each=3)

Calling classmethods through a dictionary

I'm working on a class describing an object that can be expressed in several "units", I'll say, to keep things simple. Let's say we're talking about length. (It's actually something more complicated.) What I would like is for the user to be able to input 1 and "inch", for example, and automatically get member variables in feet, meters, furlongs, what have you as well. I want the user to be able to input any of the units I am dealing in, and get member variables in all the other units. My thought was to do something like this:
class length:
#classmethod
def inch_to_foot(cls,inch):
# etc.
#classmethod
def inch_to_meter(cls,inch):
# etc.
I guess you get the idea. Then I would define a dictionary in the class:
from_to={'inch':{'foot':inch_to_foot,'meter':inch_to_meter, ...},
'furlong':{'foot':furlong_to_foot, ...},
#etc
}
So then I think I can write an __init__ method
def __init__(self,num,unit):
cls = self.__class__
setattr(self,unit,num)
for k in cls.from_to[unit].keys:
setattr(self,k,cls.from_to[unit][k](num)
But no go. I get the error "class method not callable". Any ideas how I can make this work? Any ideas for scrapping the whole thing and trying a different approach? Thanks.
If you move the from_to variable into __init__ and modify it to something like:
cls.from_to={'inch':{'foot':cls.inch_to_foot,'meter':cls.inch_to_meter, }}
then I think it works as you expect.
Unfortunately I can't answer why because i haven't used classmethods much myself, but I think it is something to do with bound vs unbound methods. Anyway, if you print the functions stored in to_from in your code vs the ones with my modification you'll see they are different (mine are bound, yours are classmethod objects)
Hope that helps somewhat!
EDIT: I've thought about it a bit more, I think the problem is because you are storing a reference to the functions before they have been bound to the class (not surprising that the binding happens once the rest of the class has been parsed). My advice would be to forget about storing a dictionary of function references, but to store (in some representation of your choice) strings that indicate the units you can change between. For instance you might choose a similar format, such as:
from_to = {'inch':['foot','meter']}
and then look up the functions during __init__ using getattr
E.G.:
class length:
from_to = {'inch':['foot','meter']}
def __init__(self,num,unit):
if unit not in self.from_to:
raise RuntimeError('unit %s not supported'%unit)
cls = self.__class__
setattr(self,unit,num)
for k in cls.from_to[unit]:
f = getattr(cls,'%s_to_%s'%(unit,k))
setattr(self,k,f(num))
#classmethod
def inch_to_foot(cls,inch):
return inch/12.0
#classmethod
def inch_to_meter(cls,inch):
return inch*2.54/100
a = length(3,'inches')
print a.meter
print a.foot
print length.inch_to_foot(3)
I don't think doing with an __init__() method would be a good idea. I once saw an interesting way to do it in the Overriding the __new__ method section of in the classic document titled Unifying types and classes in Python 2.2 by Guido van Rossum.
Here's some examples:
class inch_to_foot(float):
"Convert from inch to feet"
def __new__(cls, arg=0.0):
return float.__new__(cls, float(arg)/12)
class inch_to_meter(float):
"Convert from inch to meter"
def __new__(cls, arg=0.0):
return float.__new__(cls, arg*0.0254)
print inch_to_meter(5) # 0.127
Here's a completely different answer that uses a metaclass and requires the conversion functions to bestaticmethodsrather thanclassmethods-- which it turns into properties based on the target unit's name. If searches for the names of any conversion functions itself, eliminating the need to manually definefrom_totype tables.
One thing about this approach is that the conversion functions aren't even called unless indirect references are made to the units associated with them. Another is that they're dynamic in the sense that the results returned will reflect the current value of the instance (unlike instances of three_pineapples'lengthclass, which stores the results of calling them on the numeric value of the instance when it's initially constructed).
You've never said what version of Python you're using, so the following code is for Python 2.2 - 2.x.
import re
class MetaUnit(type):
def __new__(metaclass, classname, bases, classdict):
cls = type.__new__(metaclass, classname, bases, classdict)
# add a constructor
setattr(cls, '__init__',
lambda self, value=0: setattr(self, '_value', value))
# add a property for getting and setting the underlying value
setattr(cls, 'value',
property(lambda self: self._value,
lambda self, value: setattr(self, '_value', value)))
# add an identity property the just returns the value unchanged
unitname = classname.lower() # lowercase classname becomes name of unit
setattr(cls, unitname, property(lambda self: self._value))
# find conversion methods and create properties that use them
matcher = re.compile(unitname + r'''_to_(?P<target_unitname>\w+)''')
for name in cls.__dict__.keys():
match = matcher.match(name)
if match:
target_unitname = match.group('target_unitname').lower()
fget = (lambda self, conversion_method=getattr(cls, name):
conversion_method(self._value))
setattr(cls, target_unitname, property(fget))
return cls
Sample usage:
scalar_conversion_staticmethod = (
lambda scale_factor: staticmethod(lambda value: value * scale_factor))
class Inch(object):
__metaclass__ = MetaUnit
inch_to_foot = scalar_conversion_staticmethod(1./12.)
inch_to_meter = scalar_conversion_staticmethod(0.0254)
a = Inch(3)
print a.inch # 3
print a.meter # 0.0762
print a.foot # 0.25
a.value = 6
print a.inch # 6
print a.meter # 0.1524
print a.foot # 0.5

creating class instances from a list

Using python.....I have a list that contain names. I want to use each item in the list to create instances of a class. I can't use these items in their current condition (they're strings). Does anyone know how to do this in a loop.
class trap(movevariables):
def __init__(self):
movevariables.__init__(self)
if self.X==0:
self.X=input('Move Distance(mm) ')
if self.Vmax==0:
self.Vmax=input('Max Velocity? (mm/s) ')
if self.A==0:
percentg=input('Acceleration as decimal percent of g' )
self.A=percentg*9806.65
self.Xmin=((self.Vmax**2)/(2*self.A))
self.calc()
def calc(self):
if (self.X/2)>self.Xmin:
self.ta=2*((self.Vmax)/self.A) # to reach maximum velocity, the move is a symetrical trapezoid and the (acceleration time*2) is used
self.halfta=self.ta/2. # to calculate the total amount of time consumed by acceleration and deceleration
self.xa=.5*self.A*(self.halfta)**2
else: # If the move is not a trap, MaxV is not reached and the acceleration time is set to zero for subsequent calculations
self.ta=0
if (self.X/2)<self.Xmin:
self.tva=(self.X/self.A)**.5
self.halftva=self.tva/2
self.Vtriang=self.A*self.halftva
else:
self.tva=0
if (self.X/2)>self.Xmin:
self.tvc=(self.X-2*self.Xmin)/(self.Vmax) # calculate the Constant velocity time if you DO get to it
else:
self.tvc=0
self.t=(self.ta+self.tva+self.tvc)
print self
I'm a mechanical engineer. The trap class describes a motion profile that is common throughout the design of our machinery. There are many independent axes (trap classes) in our equipment so I need to distinguish between them by creating unique instances. The trap class inherits from movevariables many getter/setter functions structured as properties. In this way I can edit the variables by using the instance names. I'm thinking that I can initialize many machine axes at once by looping through the list instead of typing each one.
You could use a dict, like:
classes = {"foo" : foo, "bar" : bar}
then you could do:
myvar = classes[somestring]()
this way you'll have to initialize and keep the dict, but will have control on which classes can be created.
The getattr approach seems right, a bit more detail:
def forname(modname, classname):
''' Returns a class of "classname" from module "modname". '''
module = __import__(modname)
classobj = getattr(module, classname)
return classobj
From a blog post by Ben Snider.
If it a list of classes in a string form you can:
classes = ['foo', 'bar']
for class in classes:
obj = eval(class)
and to create an instance you simply do this:
instance = obj(arg1, arg2, arg3)
EDIT
If you want to create several instances of the class trap, here is what to do:
namelist=['lane1', 'lane2']
traps = dict((name, trap()) for name in namelist)
That will create a dictionary that maps each name to the instance.
Then to access each instance by name you do:
traps['lane1'].Vmax
you're probably looking for getattr.

Categories

Resources