Change variable attribute, evaluate other attributes accordingly - python

I am trying to find a way for getting all attributes to evaluate after one attribute change within the class, without calling a function outside the class.
class Students:
def __init__(self, name, mylist):
self.name = name
self.subjects = mylist
self.credits = len(self.subjects) * 2
def credits_calc(self):
self.credits = len(self.subjects) * 2
return self.credits
john = Students("John", ["Maths", "English"])
print(john.subjects)
print(john.credits)
john.subjects.append("History")
print(john.subjects) # --> subjects attribute updated.
print(john.credits) # --> obviously not updated. Still returns initial value.
I have to call the function outside the class to to have the other attributes updated
john.credits_calc() # I know I can take the returned value.
print(john.credits) # --> updated after calling the function.
So my question is how to get the other attributes to evaluate if one attribute is changed without the need to manually call the function later.

What you are looking for is the property decorator. There are additional methods you can add on to this, particularly the fset and fdel logic of this attribute, the code below simply defines the fget behavior.
class Students:
def __init__(self, name, mylist):
self.name = name
self.subjects = mylist
#property
def credits(self):
return len(self.subjects) * 2
john = Students("John", ["Maths", "English"])
print(john.credits) # 4
john.subjects.append("History")
print(john.credits) # 6

Related

How to overwrite self after reading yaml? [duplicate]

I would like to replace an object instance by another instance inside a method like this:
class A:
def method1(self):
self = func(self)
The object is retrieved from a database.
It is unlikely that replacing the 'self' variable will accomplish whatever you're trying to do, that couldn't just be accomplished by storing the result of func(self) in a different variable. 'self' is effectively a local variable only defined for the duration of the method call, used to pass in the instance of the class which is being operated upon. Replacing self will not actually replace references to the original instance of the class held by other objects, nor will it create a lasting reference to the new instance which was assigned to it.
As far as I understand, If you are trying to replace the current object with another object of same type (assuming func won't change the object type) from an member function. I think this will achieve that:
class A:
def method1(self):
newObj = func(self)
self.__dict__.update(newObj.__dict__)
It is not a direct answer to the question, but in the posts below there's a solution for what amirouche tried to do:
Python object conversion
Can I dynamically convert an instance of one class to another?
And here's working code sample (Python 3.2.5).
class Men:
def __init__(self, name):
self.name = name
def who_are_you(self):
print("I'm a men! My name is " + self.name)
def cast_to(self, sex, name):
self.__class__ = sex
self.name = name
def method_unique_to_men(self):
print('I made The Matrix')
class Women:
def __init__(self, name):
self.name = name
def who_are_you(self):
print("I'm a women! My name is " + self.name)
def cast_to(self, sex, name):
self.__class__ = sex
self.name = name
def method_unique_to_women(self):
print('I made Cloud Atlas')
men = Men('Larry')
men.who_are_you()
#>>> I'm a men! My name is Larry
men.method_unique_to_men()
#>>> I made The Matrix
men.cast_to(Women, 'Lana')
men.who_are_you()
#>>> I'm a women! My name is Lana
men.method_unique_to_women()
#>>> I made Cloud Atlas
Note the self.__class__ and not self.__class__.__name__. I.e. this technique not only replaces class name, but actually converts an instance of a class (at least both of them have same id()). Also, 1) I don't know whether it is "safe to replace a self object by another object of the same type in [an object own] method"; 2) it works with different types of objects, not only with ones that are of the same type; 3) it works not exactly like amirouche wanted: you can't init class like Class(args), only Class() (I'm not a pro and can't answer why it's like this).
Yes, all that will happen is that you won't be able to reference the current instance of your class A (unless you set another variable to self before you change it.) I wouldn't recommend it though, it makes for less readable code.
Note that you're only changing a variable, just like any other. Doing self = 123 is the same as doing abc = 123. self is only a reference to the current instance within the method. You can't change your instance by setting self.
What func(self) should do is to change the variables of your instance:
def func(obj):
obj.var_a = 123
obj.var_b = 'abc'
Then do this:
class A:
def method1(self):
func(self) # No need to assign self here
In many cases, a good way to achieve what you want is to call __init__ again. For example:
class MyList(list):
def trim(self,n):
self.__init__(self[:-n])
x = MyList([1,2,3,4])
x.trim(2)
assert type(x) == MyList
assert x == [1,2]
Note that this comes with a few assumptions such as the all that you want to change about the object being set in __init__. Also beware that this could cause problems with inheriting classes that redefine __init__ in an incompatible manner.
Yes, there is nothing wrong with this. Haters gonna hate. (Looking at you Pycharm with your in most cases imaginable, there's no point in such reassignment and it indicates an error).
A situation where you could do this is:
some_method(self, ...):
...
if(some_condition):
self = self.some_other_method()
...
return ...
Sure, you could start the method body by reassigning self to some other variable, but if you wouldn't normally do that with other parametres, why do it with self?
One can use the self assignment in a method, to change the class of instance to a derived class.
Of course one could assign it to a new object, but then the use of the new object ripples through the rest of code in the method. Reassiging it to self, leaves the rest of the method untouched.
class aclass:
def methodA(self):
...
if condition:
self = replace_by_derived(self)
# self is now referencing to an instance of a derived class
# with probably the same values for its data attributes
# all code here remains untouched
...
self.methodB() # calls the methodB of derivedclass is condition is True
...
def methodB(self):
# methodB of class aclass
...
class derivedclass(aclass):
def methodB(self):
#methodB of class derivedclass
...
But apart from such a special use case, I don't see any advantages to replace self.
You can make the instance a singleton element of the class
and mark the methods with #classmethod.
from enum import IntEnum
from collections import namedtuple
class kind(IntEnum):
circle = 1
square = 2
def attr(y): return [getattr(y, x) for x in 'k l b u r'.split()]
class Shape(namedtuple('Shape', 'k,l,b,u,r')):
self = None
#classmethod
def __repr__(cls):
return "<Shape({},{},{},{},{}) object at {}>".format(
*(attr(cls.self)+[id(cls.self)]))
#classmethod
def transform(cls, func):
cls.self = cls.self._replace(**func(cls.self))
Shape.self = Shape(k=1, l=2, b=3, u=4, r=5)
s = Shape.self
def nextkind(self):
return {'k': self.k+1}
print(repr(s)) # <Shape(1,2,3,4,5) object at 139766656561792>
s.transform(nextkind)
print(repr(s)) # <Shape(2,2,3,4,5) object at 139766656561888>

How to get object attributes to update dynamically in Python

I'd like to create a class that has 2 input attributes and 1 output attribute such that whenever one of the input attributes are modified the output attribute is modified automatically
I've tried defining the attributes as instance variables within and outside the constructor function but in either case, after instantiating the object, the output attribute remains fixed at the value set at the moment of instantiation
class Example():
def __init__(self,n):
self.name=n
inA=1
inB=1
if inA==1 and inB==1:
outA=1
else:
outA=0
when instantiated outA is set to 1 as expected
but if I try to update:
object.inA=0
object.outA remains 1 whereas I need it to be updated to 0
Trying to avoid the use of functions if possible. New to python and OOP so sorry if this question is nonsensical or has an obvious answer
If you want instance attributes that depend on other instance attributes, properties are the way to go.
class Example:
def __init__(self, n):
self.name = n
self.inA = 1
self.inB = 1
#property
def outA(self):
return self.inA and self.inB
You access outA like a regular instance attribute, obj.outA.
>>> my_obj = Example("example")
>>> my_obj.outA
1
Changing the attributes inA and inB affect outA.
>>> my_obj.inA = 0
>>> my_obj.outA
0
You can create a function in the class and some other minor changes:
class Example():
def __init__(self,n):
self.name=n
self.inA=1
self.inB=1
def f(self):
if self.inA==1 and self.inB==1:
self.outA=1
else:
self.outA=0
To call it:
a = Example('foo')
a.inA = 0
a.f()
print(a.outA)
Output:
0
As you can see, taking out:
a.f()
line would make it give an error:
AttributeError: 'Example' object has no attribute 'outA'
Do you want it to return your output?
Expanding on U9-Forward's answer:
class Example():
def __init__(self,n):
self.name = n
self.inA = 1
self.inB = 1
def f(self):
return self.inA and self.inB

Will a class variable in python be declared again when a new instance is created?

I'm practicing the following code:
class MITPerson(Person): # a subclass of class Person
nextIdNum = 0 # identification number
def __init__(self, name):
Person.__init__(self, name)
self.idNum = MITPerson.nextIdNum
MITPerson.nextIdNum += 1
def getIdNum(self):
return self.idNum
For a reference, here is the superclass:
class Person(object): # superclass
def __init__(self, name):
"""Create a person"""
self.name = name
I thought that I've already known the answer of this question since I try the following instances:
p1 = MITPerson('Mark Guttag')
p2 = MITPerson('Billy Bob Beaver')
p3 = MITPerson('Billy Bob Beaver')
Not surprisingly, when I type these into console:
In[12]: p1.getIdNum()
Out[12]: 0
In[13]: p3.getIdNum()
Out[13]: 2
I've read this post and checked all the excellent answers here:
Static class variables in Python
I saw that nextIdNum is assigned to 0 when the first instance p1 is created.
What I feel weird is that why not p2 and p3 also bind nextIdNum to 0 again?
In my imagination, the class variable should be reassigned to 0 once a class MITPerson is created without calling the method.
Did I miss something?
By the way, I've also go through the tutorial here:
https://docs.python.org/3.6/tutorial/classes.html#class-objects
However, I'm afraid it does not give out the answer :(
I saw that nextIdNum is assigned to 0 when the first instance p1 is created.
This is wrong. nextIdNum is assigned to 0 when the class is defined.
A class statement is a kind of wrapper around a single call to type. The statements in the body are evaluated, then added to a dict that is passed to type.
Yours is roughly equivalent to
def _init_(self, name):
Person.__init__(self, name)
self.idNum = MITPerson.nextIdNum
MITPerson.nextIDNum += 1
def _getIdNum(self):
return self.idNum
MITPerson = type(
'MITPerson',
(Person,),
{
'__init__': _init,
'getIdNum': _getIdNum,
'nextIdNum': 0
}
)
del _init, _getIdNum
You can see that nextIdNum gets initialized to 0 immediately, before any instances of MITPerson are created, just like __init__ and getIdNum.
When you create an instance, the following steps occur:
MITPerson('Mark Guttag') resolves to type.__call__(MITPerson, 'Mark Guttag').
__call__ invokes MITPerson.__new__('Mark Guttag'), which creates a new instance of MITPerson.
The new value is passed to MITPerson.__init__, at which point the current value of MITPerson.nextIdNum is used before incrementing the class variable.
Once __init__ returns, __new__ returns that value, at which point it is assigned to p1.
Notice none of the code in the body of the class statement is executed again, although the function __init__ defined there is.

How to reference an existing class object with no defined variable?

I'm trying to use a function of a class object to create a new class object and running into problems. Here's the code I have so far:
class Room(object):
def __init__(self, name):
self.name = name
self.N = None
self.E = None
self.S = None
self.W = None
'''relevant code'''
def north(self,room):
self.N = Room(room)
self.N.S = self
def south(self,room):
self.S = Room(room)
self.S.N = self
So I want at least one of these print statements
room1 = Room('room1')
room1.north('room2')
print(room2.S)
print(Room(room2).S)
print(Room('room2').S)
to spit out 'room1', but the first two don't work because room2 as a variable is yet to be defined, and the last one doesn't work because it seems to be creating a new object instead of referencing the existing one, so it just prints the default 'None'.
Does there actually exist a way to reference an existing object with no variable set to it? Or is my only option to do something like this?
def north(self,room):
roomDict[room] = Room(room)
self.N = roomDict[room]
self.N.S = self
Edit: I realize I should probably be calling the new Room's south() function instead of directly changing the S variable, but that seems intuitively like it would cause a loop so I haven't touched it yet.
* Edited based on OP's clarification *
If you have a large number of objects you want to refer to without binding them to variables, dict is the way to go.
You can use #Berci's solution. But note that with that solution, if you already have a room named foo, you can't overwrite it by simply calling Room('foo') again -- doing that will just return the original foo room. To overwrite an existing room you must first do del Room.roomDict['foo'], and then call Room('foo'). This may be something you want, but maybe not.
The implementation below is less fanciful and doesn't require __new__ (in fact, Berci's solution doesn't need __new__ either and can be all done in __init__):
class Room:
registry = {}
def __init__(self, name):
self.registry[name] = self
# the rest of your __init__ code
If you want rooms to be non-overwritable, as they are in Berci's solution, just add two lines:
class Room:
registry = {}
def __init__(self, name):
if name in self.registry:
raise ValueError('room named "{}" already exists'.format(name))
self.registry[name] = self
It's not necessary to nest registry inside Room. You can make it an external dict if you want. The advantage of having the registry as a class attribute is that your Room object can access it as self.registry without knowing its global name. The (slight) disadvantage is that you need to type Room.registry or someroom.registry instead of just, say, registry, every time you access it.
Your dict solution can be brought to work. Use a class level roomDict and a new constructor not to create an already existing object referred by its name:
class Room(object):
roomDict = {}
def __new__(cls, name):
if name in cls.roomDict:
return cls.roomDict[name]
self = object.__new__(cls, name) # here the object is created
cls.roomDict[name] = self
return self
def __init__(self, name):
...
So that you can refer to room2 as Room('room2') afterwards.

Delaying / Calling Class Attributes Set Equal to Methods

Question
Is it possible to instantiate an object, set an attribute of that object equal to a class method but delay the calling of that method while enabling access to that attribute (obj.name) without having to call it as a method (obj.name())
Background
I have a class that instantiates an object. Part of that instantiation is setting an attribute equal to a database object, which requires a lookup. This lookup, when instantiating many objects (several hundred), can be slow.
I would like to somehow delay that lookup until that information is needed. However, I don't want to have to call a method on the object to do that lookup, I would like to simply access the attribute (object.attribute)
Simple Example / What I've Tried So Far
class Article(object):
def __init__(self, id, author):
self.id = id
# Note the lack of () after lookup_author below
self.author = self.lookup_author
# Temporary holding place for author data
self.__author = author
def lookup_author(self):
# A lookup that would be nice to delay / run as needed
# Would be something like Author.objects.get(author=self.__author)
# but set to something simple for this example
return '<Author: John Doe>'
article1 = Article(1, 'John Doe')
# Returns the bound method
# E.g. <bound method Article.lookup_author of <__main__.Article object at 0x100498950>>
print article1.author
# Calls the method properly, however, you have to use the method calling
# notation of .state() versus .state which is more natural and expected
# for attributes
# Returns <Author: John Doe>
print article1.author()
Using properties, you can have article1.author actually call self.lookup_author and return it.
output:
John Doe
<Author: John Doe>
bob
<Author: bob>
code:
class Article(object):
def __init__(self, id, author):
self.id = id
self.__author = None
def lookup_author(self):
return "John Doe"
def __str__(self):
return "<Author: {}>".format(self.author)
#property
def author(self):
if self.__author is None:
self.__author = self.lookup_author()
return self.__author
#author.setter
def author(self,name):
self.__author = name
article1 = Article(1, 'John Doe')
print "\n", article1.author
print article1
article1.author = 'bob'
print "\n", article1.author
print article1
For some reason, if needed, __author doesn't have to even exist until the getter is used. You can do that using exceptions.

Categories