I have a class successors that contains a list of successors. [1, 2, 3, 4]
node_successors = Successors()
When the class is assigned to a variable, for example:
neigbour = node_successors
I want neighbour to be set to the list I have stored within the class (without having to call get_successor_list() for example)
Similar to:
def __str__(self):
return "This class can be used as a string now"
Maybe will be useful for you to read this section in Python3 reference docs: Emulating container types
With your example it seems you want to get a list from your object (not the properties), you would do:
If you want to extract a list from your object, you can implement the iterator protocol (__iter__()). Then you can do list(yourInstance)
Alternatively you can implement bound indexing (__len__() and __getitem__()) either.
neighbour = node_successors.get_successor_list() if this function returns a list.
you can make the list inside the class global.
class SomeClass:
global some_list
some_list = []
some_list.append('x')
print(some_list)
Related
I'm trying to extend the Python built-in list object to include metadata. I would like to be able to assign by reference to the base class object in some cases for efficiency.
For example:
class meta(list):
def __init__(self, data=None, metadata=None):
if data is not None:
super().__init__(data) # performs a copy of data
else:
super().__init__()
self.metadata = metadata
def foo(self):
new_data = [ i for i in range(10) ]
return meta(new_data, "my meta data")
This works as expected. The call to foo returns a new meta object with the values 0-9 in it. However, the list created and assigned to new_data in the list comprehension is copied inside the initializer of meta due to the call to the base class initializer. This additional copy is unnecessary as it could simply assign the reference of new_data to the inherited list object as no other references to it could exist after exiting foo.
What I'm trying to describe is something like this:
class meta(list):
def __init__(self, data=None, metadata=None):
if data is not None:
super().__init__(data) # performs a copy of data
else:
super().__init__()
self.metadata = metadata
def foo(self):
result = meta(None, "my meta data") # an efficient initialization
new_data = [ i for i in range(10) ]
super(list, result) = new_data # just assign the reference instead of copying
return result
But I know this is not correct syntactically. However, it does describe what I'm trying to accomplish. The intent is the new_data list object would simply be referred to by the new meta object via a reference to it being assigned to the underlying list object.
I know I could use a list member object instead of inheriting from list but that causes other inefficiencies because now all of the list attributes have to be defined in the meta class and would get wrapped in another layer of function calls.
So…my questions are:
Is there a way to do this at all?
Can I access the underlying object as an independent object from the subclass?
Can it be implemented cleanly without creating more overhead than I'm trying to remove?
Is there some obscure __assign__ method available that isn't an undocumented 'feature' of the language?
The instance of meta is, necessarily, a new instance, and you assign to names, not objects. You can't simply replace the new instance with an instance of list. That is, the new instance of meta doesn't contain a reference to another list instance; it is the list instance, just with a __class__ attribute that refers to meta, not list.
If you don't want to make a copy of the argument, don't make a list argument in the first place.
class meta(list):
def __init__(self, data=None, metadata=None):
if data is not None:
super().__init__(data) # performs a copy of data
else:
super().__init__()
self.metadata = metadata
def foo(self):
new_data = range(10)
return meta(new_data, "my meta data")
list.__init__ isn't exepecting a list; it's just expecting an iterable, references to the elements of which can be added to the just-constructed list.
You would probably want to override __new__ anyway, since list.__new__ already contains the logic that decides if there is an iterable available to pass to __init__.
class meta(list):
def __new__(cls, *args, metadata=None, **kwargs):
new_list = super().__new__(cls, *args, **kwargs)
new_list.metadata = metadata
return new_list
def foo(self):
return meta(range(10), metadata="my meta data")
(And finally, foo should probably be a static method or a class method, since it makes no use of the meta instance that invokes it.)
Although #chepner didn't answer my question directly, I got some encouragement from them and came up with the following solution.
Consider having two meta objects and we want to add the underlying list objects together element by element.
The most straight forward answer is similar to my original post. I'll leave the error checking to the reader to implement.
class meta(list):
def __init__(self, data, metadata):
super().__init__(data)
self.metadata = metadata
def __add__(self, other):
return meta([self[i] + other[i] for i in range(len(self))], self.__metadata)
a = meta([1, 2, 3, 4, 5], "meta data")
b = meta([6, 7, 8, 9, 0], "more data")
a + b
[7, 9, 11, 13, 5]
Note: The meta data is just there to complete the example and I know I left out checks for making sure the b list isn't shorter than the a list. However, the operation works as expected. It's also easy to see the list created by the list comprehension is later copied completely in meta.__init__ at the call to list.__init__ via the super().__init__(...) call. This second copy is what I wanted to avoid.
Thanks to #chepner's identification of list.__init__ taking an iterable, I came up with the following solution.
class meta_add_iterable:
def __init__(self, *data):
self.data = data # copies only the references to the tuple of arguments passed in
self.current = -1
def __iter__(self):
return self
def __next__(self):
self.current += 1
if self.current < len(self.data[0]):
return sum([ i[self.current] for i in self.data ])
raise StopIteration
class meta(list):
def __init__(self, data, metadata):
super().__init__(data)
self.metadata = metadata
def __add__(self, other):
return meta(meta_add_iterable(self, other), self.metadata)
a = meta([1, 2, 3, 4, 5], "meta data")
b = meta([6, 7, 8, 9, 0], "more data")
a + b
[7, 9, 11, 13, 5]
In the above implementation, there is no interim list created via list comprehension in the meta.__add__ method. Instead, we simply pass the two meta objects (self and other) to the iterator object. Here the only copy that is done is to copy the references to the original meta objects so the iterator can refer to them. Then the call to meta.__init__ passes the iterator in instead of an already created list. The list.__init__ method simply creates the list from this iterator which means each referred to meta object is accessed only once to retrieve its data and the result is only written once in the list.__init__ method. The secondary copy is completely elided in this implementation because the add operation is actually deferred until the iterators __next__ method is called in list.__init__.
The best part is we don't need to check if we are initializing from a list object or an iterator!
I know there are plenty of things that can go wrong as it stands. I left out all of the error checking and such so just the process was visible to the reader.
I'm not sure if implementing it using the __new__ method would still be better, as suggested by #chepner. Personally, I can't see the benefit. Maybe #chepner can expand on why that may still be the better solution. Either way, this seems to have answered my question and I'm hopeful it may help others.
I have a class (list of dicts) and I want it to sort itself:
class Table(list):
…
def sort (self, in_col_name):
self = Table(sorted(self, key=lambda x: x[in_col_name]))
but it doesn't work at all. Why? How to avoid it? Except for sorting it externally, like:
new_table = Table(sorted(old_table, key=lambda x: x['col_name'])
Isn't it possible to manipulate the object itself? It's more meaningful to have:
class Table(list):
pass
than:
class Table(object):
l = []
…
def sort (self, in_col_name):
self.l = sorted(self.l, key=lambda x: x[in_col_name])
which, I think, works.
And in general, isn't there any way in Python which an object is able to change itself (not only an instance variable)?
You can't re-assign to self from within a method and expect it to change external references to the object.
self is just an argument that is passed to your function. It's a name that points to the instance the method was called on. "Assigning to self" is equivalent to:
def fn(a):
a = 2
a = 1
fn(a)
# a is still equal to 1
Assigning to self changes what the self name points to (from one Table instance to a new Table instance here). But that's it. It just changes the name (in the scope of your method), and does affect not the underlying object, nor other names (references) that point to it.
Just sort in place using list.sort:
def sort(self, in_col_name):
super(Table, self).sort(key=lambda x: x[in_col_name])
Python is pass by value, always. This means that assigning to a parameter will never have an effect on the outside of the function. self is just the name you chose for one of the parameters.
I was intrigued by this question because I had never thought about this. I looked for the list.sort code, to see how it's done there, but apparently it's in C. I think I see where you're getting at; what if there is no super method to invoke? Then you can do something like this:
class Table(list):
def pop_n(self, n):
for _ in range(n):
self.pop()
>>> a = Table(range(10))
>>> a.pop_n(3)
>>> print a
[0, 1, 2, 3, 4, 5, 6]
You can call self's methods, do index assignments to self and whatever else is implemented in its class (or that you implement yourself).
I have a python list that contains a set of objects (a class that has it's own
properties and functions and variables). I would like to extract some of the list elements and create a new list.
My question is: Are the new list elments going to remain same or they will be considered new instances of the class when I extract them from list?
For example: List layer = [myclass1, myclass2, myclass3]
I want new list layernew = [myclass1] or any such combination. How can I extract the items. When I print directly layer and new layer (by using newlayer=layer[0:1]) I see that it gives me the same instance reference of the object.
Are the new list elments going to remain same or they will be considered new instances of the class when I extract them from list.
They'll be the same. Demo:
class Widget:
def __init__(self, value):
self.value = value
a = [Widget(4), Widget(8), Widget(15)]
b = a[0:1]
print a[0] is b[0]
The output is True, so a[0] and b[0] are references to the same object.
One way to change this behavior is to use the copy module's deepcopy method. This will attempt to duplicate the object you give it and return a referentially distinct object with identical values.
import copy
class Widget:
def __init__(self, value):
self.value = value
a = [Widget(4), Widget(8), Widget(15)]
b = copy.deepcopy(a[0:1])
print a[0] is b[0]
#result: False
They are exactly the same objects. You can do identity tests with is in python.
layernew[0] is layer[0] # True
Question
In python 2.7, I want to create a custom list that extends the python list by prefilling it with some static elements. I also want to extent the python list by adding some custom methods (i.e. filtering, re-initialization, etc...).
For example:
my_list = FitFuctionsList()
should give me a list already filled with some fixed elements.
I tried to inherit from the python list:
class FitFuctionsList(list):
def __init__(self):
list.__init__(['some', 'fixed', 'list'])
but the initialization fails (an empty list is returned).
Suggestions on alternative approaches are also welcome.
Solution summary
nachshon provided a working solution even though he does not explain why it works (and why the previous example did not):
class FitFuctionsList(list):
def __init__(self):
super(FitFuctionsList, self).__init__(['some', 'fixed', 'list'])
If the need is only to initialize a list with fixed values (no custom methods), Claudiu provided a clever way of using an helper function with an attribute to initialize the list.
This methods is elegant and robust since it avoids using globals:
def FitFuctionsList():
return list(FitFuctionsList.prefilled_elements)
FitFuctionsList.prefilled_elements = ["a", "b", "rofl"]
You can inherit from list like this:
class FitFunctionsList(list):
def __init__(self, *args, **kwargs):
super(FitFunctionsList, self).__init__(['some','default','values'])
def my_custom_filter(self, criteria):
pass`
that will initialize the list with default values, you can treat it as a list and you can add custom methods.
In response to #user2304916 comment on this answer:
list.__init__(self, ['some','default','values'])
should work but super returns an object that will allow you to access all inherited stuff, in the case of multiple inheritence it will give you access to all metods in the correct order. it also does not require you specify which class that you inherit from you call a parent methos.
It seems the simplest solution here would be a helper function, something like...
prefilled_elements = ["a", "b", "rofl"]
def FitFuctionsList():
return list(prefilled_elements)
EDIT: and you could do the following if you didn't want prefilled_elements to be a global:
def FitFuctionsList():
return list(FitFuctionsList.prefilled_elements)
FitFuctionsList.prefilled_elements = ["a", "b", "rofl"]
But as to your updated question of having custom methods, you'll have to subclass list as nachson shows.
You could do something along these lines:
class FitFunctionList(list):
def __init__(self, **kwargs):
if 'func' in kwargs:
self.func=kwargs['func']
else:
self.func=range
if 'args' in kwargs:
self.args=kwargs['args']
else:
self.args=10
super(FitFunctionList, self).__init__(self.func(self.args))
def test_func(arg):
return [i*i for i in range(arg)]
print FitFunctionList()
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print FitFunctionList(func=test_func, args=6)
# [0, 1, 4, 9, 16, 25]
How can I iterate over an object and assign all it properties to a list
From
a = []
class A(object):
def __init__(self):
self.myinstatt1 = 'one'
self.myinstatt2 = 'two'
to
a =['one','two']
Don't create a full fledged class if you just want to store a bunch of attributes and return a list so that your API can consume it. Use a namedtuple instead. Here is an example.
>>> import collections
>>> Point = collections.namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
If your API just expects a sequence (not specifically a list), you can pass p directly. If it needs a list specifically, it is trivial to convert the Point object to a list.
>>> list(p)
[1, 2]
You can even subclass the newly created Point class and add more methods (documentation has details). If namedtuple doesn't meet your needs, consider sub-classing abc.Sequence Abstract Base Class or using it as a mixin.
One approach is to make your class behave like a list by implementing some or all of the container API. Depending on how the external API you're using works, you might only need to implement __iter__. If it needs more, you could always pass it list(a), which will build a list using an iterator.
Here's an example of how easy it can be to add an __iter__ method:
class A(object):
def __init__(self):
self.myAttr1 = "one"
self.myAttr2 = "two"
def __iter__(self):
yield self.myAttr1
yield self.myAttr2