adding properties dynamically creates unwanted aliases [duplicate] - python

This question already has answers here:
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
Closed 6 months ago.
I want to add properties dynamically to my class as follows, however I end up creating aliases. How can I prevent this?
class A:
def __init__(self, a, b):
self._a = a
self._b = b
for attr in ('a', 'b'):
f = lambda self: getattr(self, '_'+attr)
setattr(A, attr, property(f, None))
a = A(0,1)
print(a.a)
print(a.b)
However this yields:
1
1
Edit:
The comment on closure scoping is relevant, however that leaves the question whether one can generate properties dynamically that reference some attribute of self open.
Specifically with respect to the example above: how, if at all, can I set the property such that a.a returns 0 instead of 1? If I simply try to pass the attribute argument to the lambda function, this attribute will need to be passed and thus this won't work.

In order to get your desired result, you'd need to wrap your lambda in another function, like so:
def make_fget(attr):
return lambda self: getattr(self, '_' + attr)
This way, when you call make_fget, the local name attr is bound to the argument passed.
You can call this in a loop as in your original code:
for attr in ('a', 'b'):
setattr(A, attr, property(make_fget(attr), None))
The difference here is that in your original version, the loop essentially reassigns attr every iteration, and the lambda is only looking at attr in the outer scope (the loop) at the time it is called, and it will end up with whatever was assigned in the last iteration.
By wrapping in another function, in every loop iteration you essentially create a fresh outer scope (the function call), with the name attr bound to the passed argument, for the returned lambda.

Related

Automatic method extensions? [duplicate]

This question already has answers here:
Local variables in nested functions
(4 answers)
Closed 2 years ago.
I have a Python class MyObject (a subclass of tuple) and another class for a set of these objects, MyObjectSet (a subclass of set). I’d like that, for any non-builtin method that I define for MyObject, a method of the same name be defined for MyObjectSet with value equal to the sum of the method over the contents of the MyObjectSet.
I had thought that something like the code below would work, but the result doesn’t match my intended outcome. In practice MyObject and MyObjectSet have a lot more to them and are justified.
class MyObject(tuple):
def stat_1(self):
return len(self)
def stat_2(self):
return sum(self)
class MyObjectSet(set):
pass
for stat_name in dir(MyObject):
if not stat_name.startswith("__"):
stat_func = getattr(MyObject, stat_name)
if callable(stat_func):
setattr(MyObjectSet, stat_name, lambda S: sum(stat_func(p) for p in S))
if __name__ == "__main__":
S = MyObjectSet(MyObject(t) for t in [(1,2), (3,4)])
result, expected = S.stat_1(), sum(p.stat_1() for p in S)
print(f"S.size() = {result}, expected {expected}")
result, expected = S.stat_2(), sum(p.stat_2() for p in S)
print(f"S.sum() = {result}, expected {expected}")
Is there any way to achieve this functionality?
replace your lambda with this:
lambda S, f=stat_func: sum(f(p) for p in S)
It copies the stat_func into f, instead of capturing a reference to it, which was what happened in your original code (so all stat_funcs inside your different lambdas ended up being the last value assigned to the stat_func in the for loop.
You can simply override __getattr__ to treat any possible method call as a summing wrapper around the object's method of the same name. This simple example will just raise an AttributeError if the underlying method doesn't exist; you may want to catch the exception and raise another error of your own.
class MyObjectSet(set):
def __getattr__(self, mn):
return lambda: sum(methodcaller(mn)(x) for x in self)

Python method changing self value (dict-inherited class) [duplicate]

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).

Creating classes and variable assignment

I'm trying to understand how classes work a bit better "under the hood" of python.
If I create a class Foo like so
class Foo:
bar = True
Foo is then directly accessible, such as print(Foo) or print(Foo.bar)
However, if I dynamically create create a class and don't set it to a variable like so
type('Foo',(),{'bar':True})
If done in the interpreter it shows <class '__main__.Foo'>. However, when I try to print Foo it's undefined...NameError: name 'Foo' is not defined
Does this mean that when a class is created the "traditional" way (the first Foo class above), that python automatically sets a variable for the class of the same name? Sort of like this
# I realize this is not valid, just to convey the idea
Foo = class Foo:
bar = True
If so, then why doesn't python also create a variable named Foo set to class Foo when using type() to create it?
let's compare your problem with function statements and lambdas (because they play the same role here), consider this function f :
def f ():
return 1
the above snippet of code is not an expression at all, it is a python statement that creates a function named f returning 1 upon calling it.
let's now do the same thing, but in a different way :
f = lambda : 1
the above snippet of code is a python expression (an assignment) that assigns the symbol f to the lambda expression (which is our function) lambda : 1. if we didn't do the assignment, the lambda expression would be lost, it is the same as writing >>> 1 in the python REPL and then trying after that to reference it.
Using type with 3 argument is analogous to using the lambda to create a function. Without assignment the evaluated expression is garbage collected.
However, just you can still create an instance of the class, just like you can immediately call a lambda function.
>>> lambda x: True
<function <lambda> at 0x0000022FF95AB598>
>>> type('Test', (), {'x': True})
<class '__main__.Test'>
You can also create an instance of the class, just like you can immediately call a function
>>> t = type('Test', (), {'x': True})()
>>> t.x
True
>>> type('Test2', (), {'y': 123})().y
123
>>> (lambda x: True)(1000) # any input returns True
True
From documentation
class type(name, bases, dict)
With three arguments, return a new type object. This is essentially a dynamic form of the class statement. The name string is the class name and becomes the name attribute; the bases tuple itemizes the base classes and becomes the bases attribute; and the dict dictionary is the namespace containing definitions for class body and becomes the dict attribute. For example, the following two statements create identical type objects:
class X(object):
a = 1
X = type('X', (object,), dict(a=1))
So yes, I think you have the right idea. type() does create a class but a dynamic form.
I think you're making this too complicated. If you don't assign a value / object to a symbol, it is always "lost". Doesn't matter if the value / object is a class or something else. Example:
x = 2 + 2
That assigns the value 4 to the symbol x. Compare to:
2 + 2
The operation is carried out but the result 4 isn't assigned to a symbol.
Exact situation you have with classes.

passing in self data in python

Can you please clarify how it is that self.add(x) below works the same way as self.data.append(x)?
That is, how does self.add(x) know to append to the list because we have not explicitly stated self.data.add(x)? When we state y.addtwice('cat'), 'cat' is added to 'self', not self.data.
class Bag:
def __init__(self):
self.data=[]
def add(self,x):
self.data.append(x)
return self.data
def addtwice(self,x):
self.add(x)
self.add(x)
return self.data
>>> y = Bag()
>>> y.add('dog')
['dog']
>>> y.addtwice('cat')
['dog', 'cat', 'cat']
Because addtwice calls methods which are defined on self, and because self.data is a "mutable type", addtwice's call to add will end up appending the value of self.data. add, in turn calls self.data.append
When calling a function in a computer program, you can think of the process as being a series of substitutions like this:
# -> means (substitution for)
# <= means "return"
y = Bag()
y.add('dog') ->
y.data.append(x) ->
#(machine code)
<= y.data
# at this point, at the command propmt, python will just print what was returned.
y.addtwice('cat')->
y.add('cat')->
y.data.append(x) ->
#(machine code)
<= y.data
#nothing cares about this return
y.add('cat')->
y.data.append(x) ->
#(machine code)
<= y.data
#nothing cares about this return either
<= y.data
# at this point, at the command propmt, python will just print what was returned.
self, itself, is never really appended in any of those cases though. self.data is.
self.add(x) calls the instance method add which in turn calls self.data.append(x)
When we state y.addtwice('cat'), 'cat' is added to 'self', not self.data
This is incorrect. cat is in fact added to self.data. Why would you think it was added to self?
y.add('dog') is the same as doing Bag.add(y, 'dog'). So add is really doing y.data.append('dog'), it's customary to use the name self instead.
y.addtwice('cat') is the same as doing Bag.addtwice(y, 'cat'). So addtwice is really doing y.add('cat') twice, which is the same as doing Bag.add(y, 'cat') twice. So addtwice is really doing y.data.append('cat') twice.
The self in each instance method is just an automatically added variable pointing to the instance it's called on, in this case y.
Let look at function add(self, x) from class Bag.
When that function is called, one of the parameter is self, which is the object itself, in this case, the same instance of Bag whose add function is called.
Therefore, in function add, calling self.data.append(x) is basically calling function append on data list of Bag, thus, adding the element x into the list.
Same thing for function addtwice. By calling function add twice, two elements are added into data list of Bag.
Both functions return the data list.
add(self, x) is just a function that you want to call.
append is a built in function that adds an element to the list.
so your add function basically uses append to add the element you want to the list and return the list you named data
self.addtwice will call self.add exactly two times and so will add the element twice.

Why are these lists the same? [duplicate]

This question already has answers here:
Creating functions (or lambdas) in a loop (or comprehension)
(6 answers)
Closed 6 months ago.
I can't understand how x and y are the same list. I've been trying to debug it using print statements and import code; code.interact(local=locals()) to drop into various points, but I can't figure out what on earth is going on :-(
from collections import namedtuple, OrderedDict
coordinates_2d=["x","y"]
def virtual_container(virtual_container, objects_type):
"""Used to create a virtual object given a the type of container and what it holds.
The object_type needs to only have normal values."""
if issubclass(virtual_container, list):
class my_virtual_container_class:
"""This singleton class represents the container"""
def __init__(self):
#Define the default values
__vals__=OrderedDict([(key,list()) for key in objects_type])
print(id(__vals__["x"]), id(__vals__["y"]))#ids are different: 12911896 12911968
#Then functions to access them
d={key: lambda self: self.__vals__[key] for key in objects_type}
d["__vals__"]=__vals__
#Construct a named tuple from this
self.attr=type('attr_cl',(), d)()
print(id(self.attr.x()), id(self.attr.y()))#ids are same: 32904544 32904544
#TODO: Define the operators __del__, setitem, getitem. Also append
return my_virtual_container_class()
#Nice method of handling coordinates
coordinates=virtual_container(list, coordinates_2d)
x=coordinates.attr.x()
y=coordinates.attr.y()
x.append(1)
y.append(2)
print(x, y)#Prints [1, 2] [1, 2]
The problem is with this line:
d={key: lambda self: self.__vals__[key] for key in objects_type}
The lambda uses the value of the variable key, but that value has changed by the time the lambda is called - so all lambdas will actually use the same value for the key.
This can be fixed with a little trick: Pass the key as a default parameter value to the lambda:
... lambda self, key=key: self.__vals__[key] ...
This makes sure that the value of key is bound to the one it had at the time the lambda was created.
I think the following line should look like this (but unfortunately I can't test because I don't have Python 3 available):
# Then functions to access them
d = dict((key, lambda self: self.__vals__[key]) for key in objects_type)

Categories

Resources