How to access method from associated class object in python? [closed] - python

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 1 year ago.
Improve this question
I have a following code:
import datetime
class Notebook:
def __init__(self):
self._id = 0
self.notes = []
def new_note(self, memo, tags=""):
self.notes.append(Note(self, memo, tags)) # creating Note object instance, passing Notebook object
def get_id(self):
id = self._id
self._id += 1
return id
class Note:
def __init__(self, notebook, memo="", tags=""):
self.id = notebook.get_id() # accesing Notebook object's get_id method to set a new unique id for a Note object
self.creation_date = datetime.date.today()
self.tags = tags
self.memo = memo
def __repr__(self):
return f"[{self.id}, {self.creation_date}, {self.tags}, {self.memo}]"
n = Notebook()
n.new_note("Lorem ipsum", "Latin")
print(n.notes)
This is an exercise code I created following a book on OOP. As you can see, I had to (as far as I can tell) pass the Notebook object to a Note object during the creation just to be able to get_id for my new Note object from the Notebook object which created it.
Is there a better way to do it? Is there another way to access the attribute from the creator class without passing the whole object? Is passing an object this way necessary a bad thing at all?
The main question is how to use get_id method of Notebook object from the Note object contained within self.notes attribute of the Notebook object, but without passing the whole Notebook object, as shown in the example.
Thanks!

This is not possible, and for good reason. Imagine that your class accepted that the user specifies the type of note when creating to allow for modification, or runs some user-specified code from the note. I could create a MyNote class that replaces the new_note method to record all of the new notes the user makes. That would be problematic.
Obviously this is not an issue because you have this hardcoded, but in a more secure and dynamic environment that lets the user specify, a well-disguised malicious class could access the secure class creating it. Such as if your bank decided to use a module that creates loading bars, where it should not be able to look into the class that instantiated it.
Granted, in a truly secure situation, all the code would be under your control anyways.

Another approach could be inheritance.this is assuming th id would auto-increment based on each notebook added to the notebook class. With inheritance you get to do away with some repetition of logic.
import datetime
class Note:
tags = None
memo = None
def __init__(self):
self.creation_date = datetime.date.today()
self.id = 0
def __repr__(self):
return 'Note created!'
class Notebook(Note):
def new_note(self,memo, tags=""):
self.tags = memo
self.memo = tags
return f"[{self.tok()}, {self.creation_date}, {self.tags}, {self.memo}]"
def gen_id(self):
while True:
self.id +=1
yield self.id
def tok(self):
a = self.gen_id()
return next(a)

Related

Python - init argument not used in self [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 months ago.
Improve this question
I came by a code and wanted to get more understanding on when to use such arrangement.
What would be a good scenario of not using __init__'s argument (name) in self?
class ArgumentNotUsedInSelf:
def __init__(self, name: str):
self.type = "user"
user_one = ArgumentNotUsedInSelf("Mike")
print(user_one.type)
Any explanations from architectural (and not only) point of view are most welcome!
Some of the reasons for this can be:
Historical. The class used to use the argument, but it was changed so it's no longer meaningful. The argument was kept for backward compatibility.
Class hierarchy. This may be a child class of a class that uses the argument, but the child overrides the need for it. The argument is required for compatibility with the parent.
Sort of Barmar's historical example... let's say:
ArgumentNotUsedInSelf used to do something with name, but now doesn't.
But name is still used in SubClass, and changing everything could mess up dependent programs.
class ArgumentNotUsedInSelf:
def __init__(self, name: str):
self.type = "user"
class SubClass(ArgumentNotUsedInSelf):
def __init__(self, name):
super(SubClass, self).__init__(name)
self.name = name
x = SubClass('Mike')
print(x.name, x.type)
Output:
Mike user
This would be helpful if you want to store the field "name" of the created object and re-use it later:
class ArgumentNotUsedInSelf:
def __init__(self, name: str):
self.type = "user"
self.name = name
user_one = ArgumentNotUsedInSelf("Mike")
print(user_one.type)
print(user_one.name)

Using class variables in methods of the same class in Python [closed]

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 1 year ago.
Improve this question
I am currently learning Python at home using the Python for Dummies all in one book. I'm on the chapter about classes and in particular the section on class variables. So far, the code has been running fine, but I'm not sure why it doesn't work the way I expect.
My code is this:
class Crew_Member:
"""A class for creating an individual record"""
is_human = True
def __init__(self, full_name, username, rank="crewmember"):
self.full_name = full_name
self.username = username
self.rank = rank
self.join_date = dt.date.today()
def file_age(self):
return f"{self.full_name}'s file was created on {self.join_date:%d/%m/%y}"
def promote(self, rank):
self.rank = rank
def not_human(self):
if Crew_Member.is_human:
self.rank = "Mech/Scutter"
So my understanding is that is_human is the class variable. The method I'm trying to use it in is not_human. The idea is that each object would be the personel record for a crew member. If that crew member is not human, they automatically get the rank of "Mech/Scutter".
The way I imagined it working is after the object has been called, you change that instance's value of is_human to false, run the method not_human, and that would change their rank accordingly. The first time I tried and got the correct rank, the class variable hadn't changed:
My code (as written above) works fine, but this is what I have to enter to get it to work:
So this is my problem: The for (edit: if) loop in the not_human method says "If class variable is true, then change rank". But the class variable has been changed to false (as illustrated by the first print line) so why does it work?
But the class variable has been changed to false...
No, the line BobScutt.is_human = False turns it into an instance variable. The class variable remains untouched. If you want to change it, you have to manipulate the Crew_Member class directly, not one of its instances.
Observe:
class TestClass:
test_attr = True
foo = TestClass()
foo.test_attr = False
print("Test attribute in foo:", foo.test_attr)
print("Test attribute in TestClass:", TestClass.test_attr)
Output:
Test attribute in foo: False
Test attribute in TestClass: True

Class variable and Instance variable in Python [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 years ago.
Improve this question
I am practicing python classes. I got that classes variables are shared among all the instances, while instance variables belongs to each object, and thus need to be defined for each instance. In the following classes, in reference to the variable raise_amount, if I write the last code line like that: self.pay = int(self.pay * Employee.raise_amount), the behavior is the same.
What is the difference between the two cases, if any?
class Employee:
raise_amount = 1.04
def __init__(self ,first ,last ,pay ):
self.first = first
self.last = last
self.pay = pay
self.email = first+"."+last+"#company.com"
def apply_raise(self): #but this is not an attribute
self.pay = int(self.pay * self.raise_amount)
#or self.pay = int(self.pay * Employee.raise_amount)
I assume your are referring to the self.raise_amount. In the case where you have it as that python must first look for instance variable and if not found it looks for similarly named class variable and creates a copy of it as an instance variable and uses that. if you were to then change the self.raise amount it would only be for the instance and not for any other instance made from the class.
Try creating an instance of the class and use the apply_raise method on it. then try changing the raise_amount class variable to something like 2.0 and call the method on the instance again. what you should see is that the amount only goes up 1.04 times, not 2. that is because it used the instance value it created the first time you ran the method.
Note: original post was edited to change to use the Class name for the raise amount. This post was a reply while it was self.raise_amount.
The lookup for the expression self.raise_amount can be complicated in general, be we can simplify it knowing that there are no methods or descriptors involved.
If self has an instance attribute named raise_amount, the value of that attribute is returned.
Otherwise, we start looking for class attributes, starting with the immediate type of self, here Employee. Since Employee.raise_amount is defined, we get that value...
... but what if Employee.raise_amount hadn't been defined? We would have moved on to the next class in the method resolution order of Employee, namely object. Since object.raise_amount is not defined, an AttributeError would have been raised.

Loop over instances of a class to find attribute that equals a keyword [duplicate]

This question already has answers here:
Printing all instances of a class
(8 answers)
Closed 9 years ago.
Okay, so what I am trying to do is to create a function that will find an object instance based on the value of a specific attribute that all objects of the class share. Essentially, I want Python to search through the specific attribute of each instance of the class, and check it against another value, and if it finds a match to do some stuff. In pseudocode:
for each instance of Class:
if search_keyword is in instance.attribute:
do some stuff
found = True
if found is True:
tell_user("Found instance!")
If more detail is required on the nature of my inquiry, then:
What I am doing is essentially using the object as an extended dictionary. I have multiple attributes attached to the object, and I want to search through them. I am essentially using it as a storage container, which I need to search through.
If there is a better way to store said information other than objects, please share.
I'm essentially using it as a dictionary with multiple keys.
You could do this easily enough by having a class attribute which is a list of all instances of that class ever created, and having the __init__ of each instance of the class add the instance to the list.
class Foo(object):
list_of_all_foos = []
def __init__(self):
Foo.list_of_all_foos.append(self)
Then to search all the Foo instances you've created:
for foo_instance in Foo.list_of_all_foos:
if search_keyword is in foo_instance.attribute:
do some stuff
found = True
if found is True:
tell_user("Found instance!")
Alternatively, to do this with a class method, which might be a little more idiomatic:
class Foo(object):
list_of_all_foos = []
#classmethod
def create_foo_and_add_to_list(cls, *args, **kwargs):
new_foo = cls(*args, **kwargs)
Foo.list_of_all_foos.append(new_foo)
return new_foo

Printing all instances of a class

With a class in Python, how do I define a function to print every single instance of the class in a format defined in the function?
I see two options in this case:
Garbage collector
import gc
for obj in gc.get_objects():
if isinstance(obj, some_class):
dome_something(obj)
This has the disadvantage of being very slow when you have a lot of objects, but works with types over which you have no control.
Use a mixin and weakrefs
from collections import defaultdict
import weakref
class KeepRefs(object):
__refs__ = defaultdict(list)
def __init__(self):
self.__refs__[self.__class__].append(weakref.ref(self))
#classmethod
def get_instances(cls):
for inst_ref in cls.__refs__[cls]:
inst = inst_ref()
if inst is not None:
yield inst
class X(KeepRefs):
def __init__(self, name):
super(X, self).__init__()
self.name = name
x = X("x")
y = X("y")
for r in X.get_instances():
print r.name
del y
for r in X.get_instances():
print r.name
In this case, all the references get stored as a weak reference in a list. If you create and delete a lot of instances frequently, you should clean up the list of weakrefs after iteration, otherwise there's going to be a lot of cruft.
Another problem in this case is that you have to make sure to call the base class constructor. You could also override __new__, but only the __new__ method of the first base class is used on instantiation. This also works only on types that are under your control.
Edit: The method for printing all instances according to a specific format is left as an exercise, but it's basically just a variation on the for-loops.
You'll want to create a static list on your class, and add a weakref to each instance so the garbage collector can clean up your instances when they're no longer needed.
import weakref
class A:
instances = []
def __init__(self, name=None):
self.__class__.instances.append(weakref.proxy(self))
self.name = name
a1 = A('a1')
a2 = A('a2')
a3 = A('a3')
a4 = A('a4')
for instance in A.instances:
print(instance.name)
You don't need to import ANYTHING! Just use "self". Here's how you do this
class A:
instances = []
def __init__(self):
self.__class__.instances.append(self)
print('\n'.join(A.instances)) #this line was suggested by #anvelascos
It's this simple. No modules or libraries imported
Very nice and useful code, but it has a big problem: list is always bigger and it is never cleaned-up, to test it just add print(len(cls.__refs__[cls])) at the end of the get_instances method.
Here a fix for the get_instances method:
__refs__ = defaultdict(list)
#classmethod
def get_instances(cls):
refs = []
for ref in cls.__refs__[cls]:
instance = ref()
if instance is not None:
refs.append(ref)
yield instance
# print(len(refs))
cls.__refs__[cls] = refs
or alternatively it could be done using WeakSet:
from weakref import WeakSet
__refs__ = defaultdict(WeakSet)
#classmethod
def get_instances(cls):
return cls.__refs__[cls]
Same as almost all other OO languages, keep all instances of the class in a collection of some kind.
You can try this kind of thing.
class MyClassFactory( object ):
theWholeList= []
def __call__( self, *args, **kw ):
x= MyClass( *args, **kw )
self.theWholeList.append( x )
return x
Now you can do this.
object= MyClassFactory( args, ... )
print MyClassFactory.theWholeList
Python doesn't have an equivalent to Smallktalk's #allInstances as the architecture doesn't have this type of central object table (although modern smalltalks don't really work like that either).
As the other poster says, you have to explicitly manage a collection. His suggestion of a factory method that maintains a registry is a perfectly reasonable way to do it. You may wish to do something with weak references so you don't have to explicitly keep track of object disposal.
It's not clear if you need to print all class instances at once or when they're initialized, nor if you're talking about a class you have control over vs a class in a 3rd party library.
In any case, I would solve this by writing a class factory using Python metaclass support. If you don't have control over the class, manually update the __metaclass__ for the class or module you're tracking.
See http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html for more information.
In my project, I faced a similar problem and found a simple solution that may also work for you in listing and printing your class instances. The solution worked smoothly in Python version 3.7; gave partial errors in Python version 3.5.
I will copy-paste the relevant code blocks from my recent project.
```
instances = []
class WorkCalendar:
def __init__(self, day, patient, worker):
self.day = day
self.patient = patient
self.worker= worker
def __str__(self):
return f'{self.day} : {self.patient} : {self.worker}'
In Python the __str__ method in the end, determines how the object will be interpreted in its string form. I added the : in between the curly brackets, they are completely my preference for a "Pandas DataFrame" kind of reading. If you apply this small __str__ function, you will not be seeing some machine-readable object type descriptions- which makes no sense for human eyes. After adding this __str__ function you can append your objects to your list and print them as you wish.
appointment= WorkCalendar("01.10.2020", "Jane", "John")
instances.append(appointment)
For printing, your format in __str__ will work as default. But it is also possible to call all attributes separately:
for instance in instances:
print(instance)
print(instance.worker)
print(instance.patient)
For detailed reading, you may look at the source: https://dbader.org/blog/python-repr-vs-str

Categories

Resources