General setter for a class using strings - python

Let's say I have a class called "Employee" which was a bunch of different attributes. I can create a general getter which would basically get every attribute based on a string of its name like this but I don't know how to create a setter of the sort so I wouldn't have to do something like employee1.age = 22 every time. And creating multiple setter for every attribute would be pretty messy.
class Employee:
def __init__(self, name, age):
self.name = name
self.age = age
self.address = "Somewhere"
self.job = None
def getter(self, name):
return getattr(self, name, None)
def setter(self, name, amount):
pass

You can use setattr() and wrap it in your setter method like this:
def setter(self, name, amount):
return setattr(self, name, amount)
So you'd call it like this
e = Employee("Albert", 169)
e.setter("age", 16)
and when you check now for e.age you will see
>>> e.age
16

Thanks to Joe Carboni's answer, I managed to create a function I'd like to call the ultimate_getter(). I'll put it here for you all to use.
def ultimate_getter(obj, name, limit = 12):
if name.count(".") == 0:
return getattr(self, name, None)
else:
count = 0
previous = None
current = None
previous = obj
while name.count(".") > 0 and count < limit:
index = name.find(".")
previous = getattr(previous, name[0:index])
name = name[index + 1:]
count += 1
if name.count(".") == 0:
current = getattr(previous, name)
return current
It's basically a more advanced version of setattr() which can loop through the text so you wouldn't have to use setattr() a bunch of times when wanting to use sort of a nested attribute/method.

Related

Globaly fetch python class variable

Class Information:
def __init__(self):
self.name = 200
def ajust(self):
self.name = 250
Class Jesus:
def __init__(self):
self.age = 10
def ajust(self):
self.age = 20
Printdata = Information()
print(Printdata.name)
________________________
Result: >>> 200
This print is printing out the first name value. how can i get it to print the value after calling ajust? i want it to print 250, not 200. i have looked all over stackoverflow to find a solution. Keep in mind this is not my actual code, just a simple example to showcase my issue.
You need to call ajust() first
Printdata = Information()
Printdata.ajust()
print(Printdata.name)
However once you call it name will always be 250, I suggest you use property instead to allow more changes
class Information:
def __init__(self):
self.__name = 200
#property
def name(self):
return self.__name
#name.setter
def name(self, value):
self.__name = value
information = Information()
print(information.name) # 200
information.name = 250
print(information.name) # 250
Let's take a look at what's going on here behind the scenes.
Your class information has a constructor(in python these are defined by the method __init__(self).
When you call Information(), in reality you are calling the constructor, which returns an instance of your object, in this case an Information object, it also sets name to 200.
So when you print name on a newly built Information, name will be 200.
You have an ajust method. But you'll need to call it in order to update the name variable inside the class.
So your order of events should be: Build the class, call ajust, print name

How to give different attributes to new objects' and how __init__ exactly works?

All the objects get different random name variables but all of them have same random sex.
How this could happen?
How to make all the objects to have different random sex variables?
Am I using __init__ correctly and what for I have to add to my class methods?
I am making a game with a population (list of objects) of people (objects from villager class). I want them to get random sex and Class gives each object random name from names list based on sex.
But something went wrong and sex is constant from the beginning. However, names are different!
Could you please help to solve this problem and suggest more relevant literature?
import random
mnames = ['Ivan', 'Oleg', 'Kirill']
fnames = ['Katya', 'Masha', 'Olga']
population = [1,1,1,1,1]
class villager:
age = 0
sex = random.randint(0,1)
name = ''
def __init__(self, sex = 10):
self.sex = random.randint(0,1)
def __init__(self, name = ''):
if self.sex == 0:
self.name = mnames[random.randint(0, len(mnames)-1)]
if self.sex == 1:
self.name = fnames[random.randint(0, len(fnames)-1)]
populsize = len(population)
for i in range(populsize):
population[i] = villager()
for i in range(len(population)):
print(population[i].sex, population[i].name )
I expect to see something like this:
1 Katya
0 Ivan
0 Ivan
0 Kirill
1 Olga
Actual result are (always same sex, first number ):
0 Kirill
0 Ivan
0 Oleg
0 Oleg
0 Oleg
or
1 Olga
1 Katya
1 Masha
1 Olga
1 Katya
Python doesn't allow you to overload __init__ like you would in C++. The second definition of __init__ in the class overwrites the first definition.
In your code, the class level variable for sex never gets changed. These class-level definitions aren't needed.
class villager:
#these aren't needed, better to set in __init__()
#age = 0
#sex = random.randint(0,1)
#name = ''
#this definition is overwritten by the next definition of __init__()
def __init__(self, sex = 10):
self.sex = random.randint(0,1)
The proper way to define your class is:
class villager:
def __init__(self, sex=10, name=''):
self.sex = random.randint(0,1)
if self.sex == 0:
self.name = mnames[random.randint(0, len(mnames)-1)]
if self.sex == 1:
self.name = fnames[random.randint(0, len(fnames)-1)]
Since both sex and name aren't used by your code, you don't need to specify them in the __init__ method. If you do want the option of setting them when you instantiate a villager, but also want them to be optional, you could code it like this:
class villager:
def __init__(self, sex=None, name=None):
if sex is not None: # must be specific because you define sex as 0,1
self.sex = sex
else:
self.sex = random.randint(0,1)
if name:
self.name = name
else:
if self.sex == 0:
self.name = mnames[random.randint(0, len(mnames)-1)]
if self.sex == 1:
self.name = fnames[random.randint(0, len(fnames)-1)]
Firstly, you have defined a class level attribute
class villager:
sex = random.randint(0,1)
This ensures that all instances of villager will have a .sex attribute that is assigned with a single default value randomly at its definition.
You then define a __init__ method that specifies sex as a parameter (which is ignored) but then assigns a random number regardless.
def __init__(self, sex = 10):
Then it is redefined again thus shadowing over the first definition:
def __init__(self, name = ''):
The second definition doesn't assign a self.sex attribute, thus every new instance of this class will fall back to the class level attribute (which was assigned once). This resulted in exactly what you were seeing, where all the villagers have the same sex assigned.
In Python, you cannot have multiple definitions of the same method constructed in this manner (it kind of can through the use of a specific decorator, but you don't really need it here). The quickest fix is to combine the two separate __init__ into a single definition, and also implement what your intention might be:
class villager:
def __init__(self, sex=None, name=None):
if sex is None:
self.sex = random.randint(0,1)
else:
self.sex = sex
if name is not None:
self.name = name
else:
if self.sex == 0:
self.name = mnames[random.randint(0, len(mnames)-1)]
elif self.sex == 1:
self.name = fnames[random.randint(0, len(fnames)-1)]
So for this case, your default constructor should work
>>> v = villager()
>>> v.sex
1
>>> v.name
'Olga'
You can now also assign specific values in your constructor:
>>> v1 = villager(name='Gangut')
>>> v1.name
'Gangut'
>>> v1.sex
1
Or directly with both arguments
>>> v2 = villager(name='Elena', sex=1)
>>> v2.name
'Elena'
>>> v2.sex
1

How does Python resolve when an attribute is a descriptor?

So take the following code:
class Quantity:
__counter = 0
def __init__(self):
cls = self.__class__
prefix = cls.__name__
index = cls.__counter
self.storage_name = f'_{prefix}#{index}'
cls.__counter += 1
def __get__(self, instance, owner):
if instance is None:
return self
return getattr(instance, self.storage_name)
def __set__(self, instance, value):
if value > 0:
setattr(instance, self.storage_name, value)
else:
raise ValueError('value must be > 0')
class LineItem:
weight = Quantity()
price = Quantity()
def __init__(self, description, weight, price):
self.description = description
self.weight = weight
self.price = price
def subtotal(self):
return self.weight * self.price
if __name__ == '__main__':
l = LineItem('cocoa', 15, 2.70)
print(vars(l))
>>> {'description': 'cocoa', '_Quantity#0': 15, '_Quantity#1': 2.7}
How does Python know to not shadow the class attributes price and weight with the instance attributes like it would typically do? I'm getting confused trying to understand the order in which Python evaluates all of this.
Quoting the documentation:
The default behavior for attribute access is to get, set, or delete the attribute from an object’s dictionary. For instance, a.x has a lookup chain starting with a.__dict__['x'], then type(a).__dict__['x'], and continuing through the base classes of type(a) excluding metaclasses.
However, if the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined and how they were called.
[...]
Instance Binding
If binding to an object instance, a.x is transformed into the call: type(a).__dict__['x'].__get__(a, type(a)).
When you define the class:
class LineItem:
weight = Quantity()
price = Quantity()
Python will fist see the descriptors.
So, when you instanciate your class:
l = LineItem('cocoa', 15, 2.70)
The class is built with the descriptors and the __init__ is called.
self.weight = weight
Will call:
LineItem.weight.__set__(l, weight)

create python factory method

I have the following simple example:
class CatZoo(object):
def __init__(self):
raise NotImplemented
#classmethod
def make_zoo_cat(cls, name, age, gender, location):
cls._names = name
cls._ages = age
cls._genders = gender
cls._location = location
return cls
#classmethod
def make_zoo_cats(cls, names, ages, genders, location):
cls._names = names
cls._ages = ages
cls._genders = genders
cls._location = location
return cls
#property
def location(self):
return self._location
#property
def names(self):
return self._names
def age(self, name):
if name in self._names:
return self._ages[self._names.index(name)]
else:
return None
def gender(self, name):
if name in self._names:
return self._genders[self._names.index(name)]
else:
return None
#property
def meow(self):
return "meow!"
And I am trying to create an object of this class by using the following:
cat_zoo = CatZoo.make_zoo_cat('squeakers', 12, 'M', 'KC')
print "The name is {}".format(cat_zoo.names)
This is just an example, I am just trying to make my factory methods work (make_zoo_cat, make_zoo_cats). The first will be passed one name, age, gender and location where the second would be passed a list of names, ages and genders and one location. If I run this code, I get the following output:
The name is <property object at 0x7fe313b02838>
Thanks,
Remove the NotImplemented initializer and actually create instances of your class, instead of mutating the class itself:
class CatZoo(object):
def __init__(self, name, age, gender, location):
self._names = name
self._ages = age
self._genders = gender
self._location = location
#classmethod
def make_zoo_cat(cls, name, ages, genders, location):
return cls.mak_zoo_cats([name], age, gender, location)
#classmethod
def make_zoo_cats(cls, names, ages, genders, location):
return CatZoo(names, age, gender, location)
#property
def location(self):
return self._location
#property
def names(self):
return self._names
def age(self, name):
if name in self._names:
return self._ages[self._names.index(name)]
else:
return None
def gender(self, name):
if name in self._names:
return self._genders[self._names.index(name)]
else:
return None
#property
def meow(self):
return "meow!"
Note that there was no real difference other than the method name between make_zoo_cat and make_zoo_cats, the difference in argument names doesn't change the functionality here.
Instead, I presumed that ._names should always be a list and that make_zoo_cat (singular) should create a CatZoo with one cat name in it.
Just remember that Python is not Java; you really don't need all those property objects, not where you could just access the attribute directly.
You didn't create any object in your code.
In your make_zoo_cats you return cls, so you still have a class not an instance of this class.
This code will print the yes
if CatZoo.make_zoo_cat('squeakers', 12, 'M', 'KC') == CatZoo:
print 'yes'
You agree than you can't do that, since name its a property it will only exist if you have an instance of that class.
CatZoo.names
to be able to use the property you need on instance of that class
something like that (this will raise in your code):
cat = CatZoo()
cat.names # I can do this now
An other point in your make_zoo_cat you create Class variables, those variables are accessible from the class (no need to have an instance on that class) but are "common" to all.
c1 = CatZoo.make_zoo_cat('squeakers', 12, 'M', 'KC')
print c1._names
print c1._ages
print c1._genders
print c1._location
print '*'*10
print CatZoo._names
print CatZoo._ages
print CatZoo._genders
print CatZoo._location
print '*'*10
c2 = CatZoo.make_zoo_cat('other', 42, 'F', 'FR')
print c2._names
print c2._ages
print c2._genders
print c2._location
print '*'*10
print CatZoo._names
print CatZoo._ages
print CatZoo._genders
print CatZoo._location
print '*'*10
print c1._names
print c1._ages
print c1._genders
print c1._location
the result will be someting like that:
squeakers
12
M
KC
**********
squeakers
12
M
KC
**********
other
42
F
FR
**********
other
42
F
FR
**********
other
42
F
FR
The first two give me the same result, and the last three as well, this is because they are class variables and you always have the same class so modifying one of those variable will affect the other

I want to add an object to a list from a different file

I have 3 files. The first is a Runners file which is abstract. The other two are CharityRunner and ProfessionalRunners. In these I can create runners.
Runners:
class Runner(object):
def __init__ (self, runnerid, name):
self._runnerid = runnerid
self._name = name
#property
def runnerid(self):
return self._runnerid
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
def get_fee(self, basicfee, moneyraised):
raise NotImplementedError("AbstractMethod")
CharityRunners:
from Runner import *
class CharityRunner(Runner):
def __init__ (self, runnerid, name, charityname):
super().__init__(runnerid, name)
self._charityname = charityname
#property
def charityname(self):
return self._charityname
#charityname.setter
def charityname(self, charityname):
self._charityname = charityname
def get_fee(self, basicfee, moneyraised):
if moneyraised >= 100:
basicfee = basicfee * 0.25
elif moneyraised >= 50 and moneyraised < 100:
basicfee = basicfee * 0.5
else:
basicfee = basicfee
return basicfee
ProfessionalRunners:
from Runner import *
class ProfessionalRunner(Runner):
def __init__ (self, runnerid, name, sponsor):
super().__init__(runnerid, name)
self._sponsor = sponsor
#property
def sponsor(self):
return self._sponsor
#sponsor.setter
def sponsor(self, sponsor):
self._sponsor = sponsor
def get_fee(self, basicfee):
basicfee = basicfee * 2
return basicfee
Now I have also created a club object that has a club id and club name. There is also a list called self._runners = []. I'm trying to get a add function that will add the runners created in the list. But it must make sure that the runner is not already in the list.
The object printing method should be in the format of:
Club: <club id> <club name>
Runner: <runner id 1> <runner name 1>
Runner: <runner id 2> <runner name 2>
At the moment I only have this for the club object:
from Runner import *
class Club (object):
def __init__(self, clubid, name):
self._clubid = clubid
self._name = name
self._runners = []
#property
def clubid(self):
return self._clubid
#property
def name(self):
return self._name
#name.setter
def name(self, name):
self._name = name
def add_runner(self):
self._runner.append(Runner)
I'm guessing the part you're missing is:
im trying to get a add function that will add the runners created in the list.
Your existing code does this:
def add_runner(self):
self._runner.append(Runner)
This has multiple problems.
First, you're trying to modify self._runner, which doesn't exist, instead of self._runners.
Next, you're appending the Runner class, when you almost certainly want an instance of it, not the class itself.
In fact, you almost certainly want an instance of one of its subclasses.
And I'm willing to bet you want a specific instance, that someone will pass to the add_runner function, not just some random instance.
So, what you want is probably:
def add_runner(self, runner):
self._runners.append(runner)
And now that you posted the UML diagram, it says that explicitly: add_runner(Runner: runner). In Python, you write that as:
def add_runner(self, runner):
Or, if you really want:
def add_runner(self, runner: Runner):
… but that will probably mislead you into thinking that this is a Java-style definition that requires an instance of Runner or some subclass thereof and checks it statically, and that it can be overloaded with different parameter types, etc., none of which is true.
To use it, just do this:
doe_club = Club(42, "Doe Family Club")
john_doe = CharityRunner(23, "John Doe", "Toys for John Doe")
doe_club.add_runner(john_doe)
Next:
But it must make sure that the runner is not already in the list.
You can translate that almost directly from English to Python:
def add_runner(self, runner):
if runner not in self._runners:
self._runners.append(runner)
However, this does a linear search through the list for each new runner. If you used an appropriate data structure, like a set, this wouldn't be a problem. You could use the same code (but with add instead of append)… but you don't even need to do the checking with a set, because it already takes care of duplicates for you. So, if you set self._runners = {}, you just need:
def add_runner(self, runner):
self._runners.add(runner)

Categories

Resources