I would like to implement something that would work like this:
memo = Note("memo",5)
report = Note("report",20)
notebook = Notebook(memo,report)
print str(notebook.memo) # 5 expected
print str(notebook.report) # 20 expected
Inspired by:
http://znasibov.info/blog/html/2010/03/10/python-classes-dynamic-properties.html
and
How to implement property() with dynamic name (in python)
, I implemented the following code:
class Note:
def __init__(self,name,size):
self.name = name
self.size = size
class Notebook(object):
def __new__(cls,*notes):
notebook = object.__new__(cls)
setattr(notebook,'_notes',{note.name : note.size for note in notes})
functions = [lambda notebook : notebook._notes[note.name] for note in notes]
for note,function in zip(notes,functions) :
#version 1
setattr(notebook.__class__, note.name, property(function))
#version 2 -- note : __class__ removed
#setattr(notebook, note.name, property(function))
return notebook
note: I know for this minimal code use of __new__ instead of __init__ is not justified, but this will be required later on when I use subclasses of Notebook
If I use version 1:
1. instead of having 5 and 20 being printed, it prints 20 and 20. I do not get why. Printing the functions shows an array of functions with different addresses.
2. I used __class__ inspired by the blog entry given above, but I am not sure what it does. It makes the property a class property ? (which would be real bad in my case)
If I use version 2:
prints something like property object at 0x7fb86a9d9b50.
This seems to make sense, but I am not sure I understand why it does not print the same thing for version 1.
Is there a way to fix this, using either version (or another completely different approach) ?
Edit
An interesting answer for solving the issue was proposed. Here the corresponding code:
class Note:
def __init__(self,name,value):
self.name = name
self.size = value
def _get_size(self,notebook_class=None): return self.size+1
class Notebook(object):
def __new__(cls,*notes):
notebook = object.__new__(cls)
notebook._notes = {note.name : note.size for note in notes}
for note in notes : setattr(notebook.__class__, note.name, property(note._get_size))
return notebook
Issue is : now this test code is not giving the desired output:
memo1 = Note("memo",5)
report1 = Note("report",20)
notebook1 = Notebook(memo1,report1)
print str(notebook1.memo) # print 6 as expected (function of Note return size+1)
print str(notebook1.report) # print 21 as expected
memo2 = Note("memo",35)
report2 = Note("report",40)
notebook2 = Notebook(memo2,report2)
print str(notebook2.memo) # print 36 as expected
print str(notebook2.report) # print 41 expected
print str(notebook1.memo) # does not print 6 but 36 !
print str(notebook1.report) # does not print 21 but 41 !
I guess this was to be expected as the property was added to the class ....
Anyway to overcome this issue ?
Some more food for though. To simply obtain what you want to do in your first set of code, you can do that without all the extra tricks.
The simplest way to do it is set the attributes to the desired one directly. (code consolidated in improper manors simply to save space)
class Note:
def __init__(self, name, value): self.name, self._size = name, value
size = property(lambda x: x._size+1)
class Notebook(object):
def __new__(cls, *notes):
notebook = object.__new__(cls)
notebook._notes = {note.name: note.size for note in notes}
for note in notes: setattr(notebook, note.name, note.size)
return notebook
memo1, report1 = Note("memo", 5), Note("report", 20)
notebook1 = Notebook(memo1, report1)
print(notebook1.memo, notebook1.report) # 6 21
memo2, report2 = Note("memo", 35), Note("report", 40)
notebook2 = Notebook(memo2,report2)
print(notebook2.memo, notebook2.report) # 36 41
print(notebook1.memo, notebook1.report) # 6 21
notebook1.memo += 5
print(notebook1.memo) # 11
print(memo1.size) # 6
memo1.size += 5 # AttributeError: can't set attribute
The second way would be to have the notebook literally be a container for all the notes you pass to it. This way it would simply update the original class objects, and is basically just a holder for them.
class Note2(object):
def __init__(self, name, value): self.name, self._size = name, value
def _set_size(self, value): self._size = value
size = property(lambda x: x._size+1, _set_size)
def __repr__(self): return str(self.size) #simple trick to gain visual access to .size
class Notebook2(object):
def __new__(cls, *notes):
notebook = object.__new__(cls)
notebook._notes = {note.name: note.size for note in notes}
for note in notes: setattr(notebook, note.name, note)
return notebook
memo1, report1 = Note2("memo", 5), Note2("report", 20)
notebook1 = Notebook2(memo1, report1)
print(notebook1.memo, notebook1.report) # 6 21
memo2, report2 = Note2("memo", 35), Note2("report", 40)
notebook2 = Notebook2(memo2, report2)
print( notebook2.memo, notebook2.report) # 36 41
print(notebook1.memo, notebook1.report) # 6 21
notebook1.memo.size += 16
print(notebook1.memo) # 23
print(memo1) # 23, Notice this will also set the original objects value to the new value as well
notebook1.memo += 15 # TypeError: unsupported operand type(s) for +=: 'Note2' and 'int' - It is true without making it as a property does make it less effective to work with
It should also be possible to do as in your provided link suggests to make each Note class a member of Notebook with a leading underscore (i.e. notebook._memo) and then make a property for Notebook which would link Note name to size (i.e. notebook.memo would be a link to notebook._memo.size). Hope these examples help.
Original answer.
Interesting idea, to simply get it working here is a hack of your original version:
class Note(object):
def __init__(self,name, size):
self.name = name
self._size = size
def _get_size(self, notebook_class=None):
return self._size
def _set_size(self, notebook_class=None, size=0):
self._size = size
class Notebook(object):
def __new__(cls,*notes):
notebook = object.__new__(cls)
for note in notes:
setattr(notebook.__class__, note.name, property(note._get_size, note._set_size))
return notebook
However you seem to be removing each Note class when you ingest them into Notebook anyways so you could do something much easier:
class Note(object):
def __init__(self, name, size):
self.name = name
self.size = size
class Notebook(object):
def __new__(cls, *notes):
notebook = object.__new__(cls)
for note in notes:
setattr(notebook.__class__, note.name, note.size)
return notebook
To be any more helpful I would really need to know the goal or a general idea of where you want to take this. It seems confusing to set the properties in such an odd way, yet only do it once at the creation of the class as opposed to the examples of being able to dynamical add and remove them.
Hope this helped
Creating functions in a loop is tricky:
>>> lambdas = [(lambda: i) for i in range(5)]
>>> for lamb in lambdas:
... print(lamb())
...
4
4
4
4
4
Note that all lambdas refer to the value that i assumed in the last iteration.
When you create a function python associates a closure to it, which tells the interpreter which non local variables the function should use:
>>> lambdas[0].__closure__[0]
<cell at 0x7f675ab2dc90: int object at 0x9451e0>
However it refers to the variable, not the actual object contained when the function was defined. This would require a more complicated handling of the function frames.
this means that following iterations change the value contained in this cell, and in the end only the last iteration is significant:
>>> lambdas[0].__closure__[0].cell_contents
4
If you want to refer to previous values you can use a default value for an argument:
>>> lambdas = [(lambda i=i: i) for i in range(5)]
>>> for lamb in lambdas:
... print(lamb())
...
0
1
2
3
4
Concerning the second version. property is implemented as a descriptor (see also this answer) and hence it must be set in the class in order for it to work properly. The same is true for other decorators such as staticmethod and classmethod. Putting them in an instance will just return the property object, as you observed.
The line:
setattr(notebook,'_notes',{note.name : note.size for note in notes})
Can be safely changed to the simpler and more readable:
notebook._notes = {note.name : note.size for note in notes}
Related
I have a class in Python that initializes the attributes of an environment. I am attempting to grab the topographyRegistry attribute list of my Environment class in a separate function, which when called, should take in the parameters of 'self' and the topography to be added. When this function is called, it should simply take an argument such as addTopographyToEnvironment(self, "Mountains") and append it to the topographyRegistry of the Environment class.
When implementing what I mentioned above, I ran into an error regarding the 'self' method not being defined. Hence, whenever I call the above line, it gives me:
print (Environment.addTopographyToEnvironment(self, "Mountains"))
^^^^
NameError: name 'self' is not defined
This leads me to believe that I am unaware of and missing a step in my implementation, but I am unsure of what that is exactly.
Here is the relevant code:
class EnvironmentInfo:
def __init__(self, perceivableFood, perceivableCreatures, regionTopography, lightVisibility):
self.perceivableFood = perceivableFood
self.perceivableCreatures = perceivableCreatures
self.regionTopography = regionTopography
self.lightVisibility = lightVisibility
class Environment:
def __init__(self, creatureRegistry, foodRegistry, topographyRegistery, lightVisibility):
logging.info("Creating new environment")
self.creatureRegistry = []
self.foodRegistry = []
self.topographyRegistery = []
self.lightVisibility = True
def displayEnvironment():
creatureRegistry = []
foodRegistry = []
topographyRegistery = ['Grasslands']
lightVisibility = True
print (f"Creatures: {creatureRegistry} Food Available: {foodRegistry} Topography: {topographyRegistery} Contains Light: {lightVisibility}")
def addTopographyToEnvironment(self, topographyRegistery):
logging.info(
f"Registering {topographyRegistery} as a region in the Environment")
self.topographyRegistery.append(topographyRegistery)
def getRegisteredEnvironment(self):
return self.topographyRegistry
if __name__ == "__main__":
print (Environment.displayEnvironment()) #Display hardcoded attributes
print (Environment.addTopographyToEnvironment(self, "Mountains"))#NameError
print (Environment.getRegisteredEnvironment(self)) #NameError
What am I doing wrong or not understanding when using 'self'?
Edit: In regard to omitting 'self' from the print statement, it still gives me an error indicating a TypeError:
print (Environment.addTopographyToEnvironment("Mountains"))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: Environment.addTopographyToEnvironment() missing 1 required positional argument: 'topographyRegistery'
Comments
Despite having def getRegisteredEnvironment(self): it wasn't indented, so it's not recognized as a class method.
self is a keyword used in conjunction with classes (class methods or attributes) - not functions. self is implied to be the instantiated object (eg a = Environment(...) -> self would refer to a) or the module's (I can't think of the proper term) class.
You didn't have your addTopographyToEnvironment class method defined.
In terms of your Environment class, you aren't using the variables you are passing to the class, so I made that change as well - I don't know if that was intentional or not.
As per your comment from the other answer, if you had def my_class_method(self) and you try to invoke it through an object with additional parameters, like so a = my_object(); a.my_class_method("Mountains"), you should get an error of the sorts, "2 positional arguments passed, expected 1.".
Your main problem is that you are doing Environment.class_method() and not creating an object from the class. Do a = Environment(whatever arguments here) to create an object from the class, then do a.addTopographyToEnvironment("Mountains") to do what you were going to do with "Mountains" and that object. What you have currently may be right, its just is missing the proper implementation, but the below article does a great job explaining the differences between all of them (Class Methods vs Static Methods vs Instance Methods), and is definitely worth the read.
class EnvironmentInfo:
def __init__(self, perceivableFood, perceivableCreatures, regionTopography, lightVisibility):
self.perceivableFood = perceivableFood
self.perceivableCreatures = perceivableCreatures
self.regionTopography = regionTopography
self.lightVisibility = lightVisibility
class Environment:
def __init__(self, creatureRegistry, foodRegistry, topographyRegistery, lightVisibility):
logging.info("Creating new environment")
self.creatureRegistry = creatureRegistry
self.foodRegistry = foodRegistry
self.topographyRegistery = topographyRegistery
self.lightVisibility = lightVisibility
def displayEnvironment(self):
creatureRegistry = []
foodRegistry = []
topographyRegistery = ['Grasslands']
lightVisibility = True
print (f"Creatures: {creatureRegistry} Food Available: {foodRegistry} Topography: {topographyRegistery} Contains Light: {lightVisibility}")
def addTopographyToEnvironment(self, environment):
return "Whatever this is supposed to return." + environment
def getRegisteredEnvironment(self):
return self.topographyRegistry
if __name__ == "__main__":
print (Environment.displayEnvironment()) #Display hardcoded attributes
print (Environment.addTopographyToEnvironment("Mountains"))#NameError
print (Environment.getRegisteredEnvironment()) #NameError
Object Instantiation In Python
With all that out of the way, I will answer the question as is posed, "Is there a way to grab list attributes that have been initialized using self and append data to them in Python?". I am assuming you mean the contents of the list and not the attributes of it, the attributes would be "got" or at least printed with dir()
As a simple example:
class MyClass:
def __init__(self, my_list):
self.my_list = my_list
if __name__ == "__main__":
a = MyClass([1, 2, 3, 4, 5])
print(a.my_list)
# will print [1, 2, 3, 4, 5]
a.my_list.append(6)
print(a.my_list)
# will print [1, 2, 3, 4, 5, 6]
print(dir(a.my_list))
# will print all object methods and object attributes for the list associated with object "a".
Sub Classing In Python
Given what you have above, it looks like you should be using method sub classing - this is done with the keyword super. From what I can guess, it would look like you'd implement that kind of like this:
class EnvironmentInfo:
def __init__(self, perceivableFood, perceivableCreatures, regionTopography, lightVisibility):
self.perceivableFood = perceivableFood
self.perceivableCreatures = perceivableCreatures
self.regionTopography = regionTopography
self.lightVisibility = lightVisibility
class Environment(EnvironmentInfo):
def __init__(self, creatureRegistry, foodRegistry, topographyRegistery, lightVisibility, someOtherThingAvailableToEnvironmentButNotEnvironmentInfo):
logging.info("Creating new environment")
super.__init__(foodRegistry, creatureRegistry, topographyRegistery, lightVisibility)
self.my_var1 = someOtherThingAvailableToEnvironmentButNotEnvironmentInfo
def displayEnvironment(self):
creatureRegistry = []
foodRegistry = []
topographyRegistery = ['Grasslands']
lightVisibility = True
print (f"Creatures: {creatureRegistry} Food Available: {foodRegistry} Topography: {topographyRegistery} Contains Light: {lightVisibility}")
def addTopographyToEnvironment(self, environment):
return "Whatever this is supposed to return." + environment
def getRegisteredEnvironment(self):
return self.topographyRegistry
def methodAvailableToSubClassButNotSuper(self)
return self.my_var1
if __name__ == "__main__":
a = Environment([], [], [], True, "Only accessible to the sub class")
print(a.methodAvailableToSubClassButNotSuper())
as the article describes when talking about super(), methods and attributes from the super class are available to the sub class.
Extra Resources
Class Methods vs Static Methods vs Instance Methods - "Difference #2: Method Defination" gives an example that would be helpful I think.
What is sub classing in Python? - Just glanced at it; probably an okay read.
Self represents the instance of the class and you don't have access to it outside of the class, by the way when you are calling object methods of a class you don't need to pass self cause it automatically be passed to the method you just need to pass the parameters after self so if you want to call an object method like addTopographyToEnvironment(self, newVal) you should do it like:
Environment.addTopographyToEnvironment("Mountains")
and it should work fine
I was tinkering on a little project idea in jupyter notbook when I stumbled across some weird behaviour...
The following code is abstracted from the original.
class MyClass:
Instances = []
def __init__(self,name=None):
self.id = len(MyClass.Instances)
MyClass.Instances.append(self)
if name is None:
self.name = 'Class %s' % self.id
else:
self.name = name
def show(self):
print('Name: %s\nId: %s' % (self.name, self.id))
def instance_at(i : int):
if i >= len(MyClass.Instances):
raise ValueError("Instance does not exist")
return MyClass.Instances[i]
(I hope the code is self-explanatory)
I ran the cell and tested the code and it work fine:
In [24] : m = MyClass()
m.show()
Out [25] : Name: Class 0
Id: 0
The Twist:
I didn't like that the first instance, with no name given, would be called 'Class 0' so I thought to myself: 'Why not add an object which will act as a placeholder for the index 0' (Don't ask why I did this, 'twas a brainfart).
So I changed line 2 to be Instances = [MyClass(name='id'). This also worked but when I tried to receive the instance at index 0, it's id value was different to what I expected.
In [24] : m = MyClass()
m.show()
MyClass.instance_at(0).show()
Out [25] : Name: Class 0
Id: 1
Name: id
Id: 1
This was the point where I decided to write a more generalized version of my code (the one shown here) in another notebook. I wrote it whole before running the cell, including Instances = [MyClass(name='id')].
This time I got this:
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-1-60020e1975a4> in <module>
----> 1 class MyClass:
2 Instances = [MyClass(name='id')]
3 #Instances = []
4
5 def instance_at(i : int):
<ipython-input-1-60020e1975a4> in MyClass()
1 class MyClass:
----> 2 Instances = [MyClass(name='id')]
3 #Instances = []
4
5 def instance_at(i : int):
NameError: name 'MyClass' is not defined
So now I have a piece of code which runs in one notebook but not in another. At least when you simply copy and paste it. In jupyter notebook this is fixable by changing line 2 to Instances = [], running the cell and changing it back.
I am fairly certain that this is because the class and the class variable Instances already existed before I created the ambiguous line of code.
In hindsight, this does make sense and I suspect the unexpected id value comes from a constructor call being discovered in the list when the instance m is being created.
Am I wrong? Can anybody elaborate?
Please let me know if a post like this is inappropriate here.
A simpler example:
class Example:
def __init__(self):
print("Creating an instance of the OLD class")
class Example: # redefining like this does **not** cause an error
Instances = [Example()]
def __init__(self):
print("Creating an instance of the NEW class")
# the OLD message is printed immediately
# because `Instances = [Example()]` uses the previous definition
# because it **cannot** use the current one; it hasn't been created yet.
# You **do** get an error **without** the old definition, because then
# there isn't a definition at all.
x = Example() # the NEW message is printed
# the OLD class **still exists**, but cannot easily be accessed.
# As long as we can think of a way to get at an instance,
# we can use the `__class__` of the instance to create more;
# and we can rename that to make it easily usable:
Old_Example = Example.Instances[0].__class__
y = Old_Example()
I would like to simply make a list of kinds of coffe, but get an error stating that the list is not defined. Do I have to use self in the constructor when referencing to a classvariable?
I have tried changing the return statement to return self.coffelist.append(name), but then get another error: 'Function' object has no attribute 'append'.
class coffe:
coffelist = []
def __init__(self,name,origin,price):
self.name = name
self.origin = origin
self.price = price
return (self.coffelist.append(self.name))
def coffelist(self):
print(coffelist)
c1=coffe("blackcoffe","tanz",55)
c2=coffe("fineroasted","ken",60)
This is because you named one of your methods as coffelist.
I think this shows how to do what you want. I also modified your code to follow the PEP 8 - Style Guide for Python Code and corrected some misspelled words.
class Coffee: # Class names should Capitalized.
coffeelist = [] # Class attribute to track instance names.
def __init__(self,name,origin,price):
self.name = name
self.origin = origin
self.price = price
self.coffeelist.append(self.name)
def print_coffeelist(self):
print(self.coffeelist)
c1 = Coffee("blackcoffee", "tanz", 55)
c1.print_coffeelist() # -> ['blackcoffee']
c2 = Coffee("fineroasted", "ken", 60)
c1.print_coffeelist() # -> ['blackcoffee', 'fineroasted']
# Can also access attribute directly through the class:
print(Coffee.coffeelist) # -> ['blackcoffee', 'fineroasted']
yes thanks that's exactly what I wanted!
I wasnt sure.. I thought you could do 2 things simultaneously in the return statement, both return append. I guess allot of times python is very flexible and sometimes not. thanks
I have a simple class that stores simple data. The class is as follows.
class DataFormater:
def __init__(self, N, P, K, price):
self.N = N
self.P = P
self.K = K
self.price = price
The code that calls this class is
from DataFormater import DataFormater
#global variables
ObjectList = [0,1,2,3,4,5,6,7,8,9,10,
11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,
31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50]
ObjectListCounter = 0
# main
print "enter you N-P-K values, followed by a coma, then the price"
print "example ----> 5 5 5 %50 "
print "return as many values as you want to sort, then enter, 'done!' when done."
while True:
RawData = raw_input()
if RawData == 'done!':
break
else:
ObjectList[ObjectListCounter] = DataFormater
ObjectList[ObjectListCounter].N = int(RawData[0])
# very simple test way of putting first indice in ObjectList[ObjectListCounter].N
ObjectListCounter += 1
print ObjectList[0].N
print ObjectList[1].N
My idea is that ObjectList[0] would create that object '1' that I could call with 1.N
But, when I call these, it seems that I have overwritten the previous instances.
this is what prints...
return as many values as you want to sort, then enter, 'done!' when done.
12
1
done!
1
1
Thanks so much! And I know that my post is messy, I don't exactly know how to make it more "pretty"
So, it looks like you are assigning the actual class (instead of an instance of the class) in your loop. Where you do this:
ObjectList[ObjectListCounter] = DataFormater
I think what you actually want is this
ObjectList[ObjectListCounter] = DataFormater(...insert args here....)
EDIT to address the comments:
Your class init method looks like this:
def __init__(self, N, P, K, price):
That means that to create an instance of your class, it would look like this:
my_formater = DataFormater(1, 2, 3, 4)
You would then be able to access my_formater.N which would have a value of 1.
What you are trying to do instead is access a CLASS level attribute, DataFormater.N. This is generally used in situations where you have a constant variable that does not change between instances of the class. For example:
class DataFormater():
CONSTANT_THING = 'my thing that is always the same for every instance'
You would then be able to access that variable directly from the class, like this:
DataFormater.CONSTANT_THING
I hope that clears things up.
If I import a class and rename it by subclassing, it's fairly simple to find the new class name:
>>> from timeit import Timer
>>> class Test(Timer):
... pass
...
>>> test = Test()
>>> test.__class__.__name__
'Test'
However, if I alias the class as I import it, it retains the name from its host module:
>>> from timeit import Timer as Test2
>>> test2 = Test2()
>>> test2.__class__.__name__
'Timer'
Later, I want to provide user-facing output which is aware of the name they've given the class in their namespace. Consider:
def report_stats(timer):
print("Runtime statistics for %s:" % timer.__class__.__name__)
...
Is there a way to get a string reading "Test2", short of iterating over variables in the namespace to test for an exact match?
There's a really terrible answer to my own question; I won't be accepting this since it's probably pretty fragile (I only tested for a limited set of call circumstances). I mostly just hunted this down for the challenge; I will most likely be using something more durable for my actual use case.
This assumes we have access to the init function of the class we're trying to import as blah, and some sort of persistent external data store, at least for more complicated edge cases:
import inspect, dis
class Idiom(object):
description = None
alias = None
def __init__(self, desc):
global data_ob
self.description = desc
if self.__class__.__name__ == 'Idiom':
#cheat like hell to figure out who called us
self.alias = data_ob.name_idiom(inspect.currentframe().f_back)
else:
self.alias = self.__class__.__name__
class DataOb(object):
code = None
locations = {}
LOAD_NAME = 101
codelen = None
def name_idiom(self, frame):
if not self.code:
self.code = frame.f_code
self.codelen = len(self.code.co_code)
self.locations = {y:x for x, y in dis.findlinestarts(self.code)}
target_line = frame.f_lineno
addr_index = self.locations[target_line]+1
name_index = self.code.co_code[addr_index]
# there's a chance we'll get called again this line,
# so we want to seek to the next LOAD_NAME instance(101)
addr_index += 1
while addr_index < self.codelen:
if self.code.co_code[addr_index] == self.LOAD_NAME:
self.locations[target_line] = addr_index
break
addr_index += 1
return self.code.co_names[name_index]
The short explanation of how this works is:
we look up the previous frame from the init function
obtain the code object
find bytecode locations for the start of every line in the code
use the line-number from the frame to grab the bytecode location for the start of that line
locate a LOAD_NAME indicator in the bytecode for this line (I don't really follow this; my code assumes it'll be there)
look in the next bytecode position for an index which indicates which position in the code.co_names tuple contains the "name" of the LOAD_NAME call
From here we can do something like:
>>> from rabbit_hole import Idiom as timer_bob
>>> with timer_bob("down the rabbit hole"):
... waste_some_time = list(range(50000))
...
timer_bob: down the rabbit hole
runtime: 0:00:00.001909, children: 0:00:00, overhead: 0:00:00.001909