special method for an object to override tuple expansion? - python

I will provide an example of the problem in question, in case the title was not clear enough.
Let's say that I have a class Point(object) that represent 2d coordinates.
Is it possible to create a "magic" method that will allow the following?
x, y = point
Maybe some hacks with iterators?

you can simply tap into the iterator protocol of the object and accomplish this
class Point(object):
def __init__(self, x,y):
self.x = x
self.y = y
self.points = (x,y)
def __iter__(self):
return iter(self.points)
p = Point(1,5)
x,y = p
print x,y
# 1,5
take a look at http://www.rafekettler.com/magicmethods.html#sequence on more information on how a custom object can be converted into an iterable; or more precisely how one would use an object like an iterable.

Just provide an __iter__ method.
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __iter__(self):
yield self.x
yield self.y
p = Point(1, 2)
x, y = p
assert (1, 2) == (x, y)
Be careful though. This means your class suddenly becomes safe to use in many other places where it might have previously thrown a type error.
eg.
def add_1(x):
return x + 1
l = list(map(add_1, p)) # works, because the point is iterable
Ergo, you may want to provide a method other than __iter__ that provides the iterator.
eg.
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
def coords(self):
yield self.x
yield self.y
p = Point(1, 2)
x, y = p.coords()
assert (1, 2) == (x, y)

Related

Change a self attribute only for one object in a class in Python

I'm stuck on a problem in Python... (i'm an absolute beginner but i need to do a little
environmental science model..)
so the problem is I have:
class C:
def __init__(self, x, y, z):
self.x = x
self.y = self.x * 8
self.z = self.y * 9 + 0.5
self.w = self.z +2
one = C(5,8,12)
two = C(2,12,12)
three = C(1,2,3)
So... i want to change the self.z but only for the object three
(i want it to be self.z = 12 * self.x );
I have to call it in self.w so i can't modify it after my istances...
do you have any suggestion to a beginner?
Thank you so much and have a nice day!
A few notes. First you are not actually using the arguments of y or z that are passed in __init__(self, x, y, z).
To allow on the fly overloading, you may want to break out the individual assignments into their own methods so it is easier to change the behavior you want.
Below you can pass in a custom function that will be applied to the x value when calculating z.
class C:
def __init__(self, x, custum_fn_z=None):
self.x = x
self.y = self.calc_y()
self.z = self.calc_z(custum_fn_z)
self.w = self.calc_w()
def calc_y(self):
return self.x * 8
def calc_z(self, custom_fn_z=None):
if custom_fn_z:
return custom_fn_z(self.x)
return self.y * 9 + 0.5
def calc_w(self):
return self.z +2
to use it:
one = C(5)
two = C(2)
three = C(1, lambda x: 12*x)

Class that implements custom max function

I have two Point objects and the code looks like this:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
a = Point(1, 3)
b = Point(4, 2)
max(a, b) # Make this output Point(4, 3)
My question is: "How can I implement a custom max function for the Point class that will return Point(max(self.x, other.x), max(self.y, other.y))?" The max function seems to just look at the __lt__ and return the highest.
max() can't do this, it can only return one of the elements given as input, not produce new instances.
You need to implement your own function:
def max_xy_point(*points):
if not points:
raise ValueError("Need at least 2 points to compare")
if len(points) == 1:
points = points[0]
return Point(
max(p.x for p in points),
max(p.y for p in points)
)
Like the built-in max() function, this can take either a single sequence (max([p1, p2, p3, ...]) or separate arguments (max(p1, p2, p3, ...)).
max(a, b) can return only a or b - it can't create point with new values.
You may add own method to class and use
c = a.max(b)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def max(self, other):
return Point(max(self.x, other.x), max(self.y, other.y))
a = Point(1, 3)
b = Point(4, 2)
c = a.max(b)
print(c.x, c.y)
You can go about it like this, to get desired output:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def max(self, other):
if not isinstance(other, Point):
return NotImplemented
return Point(max(self.x, other.x), max(self.y, other.y))
def __repr__(self):
return f'Point{self.x, self.y}'
a = Point(1, 3)
b = Point(4, 2)
a.max(b)
# Point(4, 3)

Python Classes using __init and __repr__ methods

Iam trying to create a class that should accept two arguments, x and y, and be represented by a string 'Point(x, y)' with appropriate values for x and y
class Point(object):
def __init__(self, x, y):
self.x = 0
self.y = 0
def __repr__(self):
return "Point(%s,%s)"%(self.x, self.y)
Error:
Point(0,0) is not of type 'string'
Failed validating 'type' in schema['items']:
{'type': 'string'}
On instance[0]:
Point(0,0)
"self.x" is the value of the instance of your class. So, if you set "self.x = 0", it means whenever you create an object for that class, the "x" value of that object will always be 0 instead of what you pass in the parameter.
"x" is the value of what you pass in the parameter.
self.x = x
self.y = y
Code:
class MyClass():
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return "Point(%s,%s)"%(self.x, self.y)
thiss = MyClass(0, 0)
print(thiss.__repr__())
thiss = MyClass(20, 20)
print(thiss.__repr__())
Output:
daudn$ python3 point.py
Point(20,20)
daudn$ python3 point.py
Point(0,0)
When declaring your init function, you initialized self.x and self.y to always be 0. If you look at the code I've posted, whatever number you pass to the class will become the values or Point.

How to make two objects have the same id in python?

If I have a class like below:
class Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
And have 2 objects:
a = Point(1,2)
b = Point(1,2)
How can I modify class Point to make id(a) == id(b)?
class Point(object):
__cache = {}
def __new__(cls, x, y):
if (x, y) in Point.__cache:
return Point.__cache[(x, y)]
else:
o = object.__new__(cls)
o.x = x
o.y = y
Point.__cache[(x, y)] = o
return o
>>> Point(1, 2)
<__main__.Point object at 0xb6f5d24c>
>>> id(Point(1, 2)) == id(Point(1,2))
True
When you need a really simple class like Point, always consider collections.namedtuple
from collections import namedtuple
def Point(x, y, _Point=namedtuple('Point', 'x y'), _cache={}):
return _cache.setdefault((x, y), _Point(x, y))
>>> Point(1, 2)
Point(x=1, y=2)
>>> id(Point(1, 2)) == id(Point(1, 2))
True
I used a function alongside namedtuple because it is simpler IMO but you can easily represent it as a class if needed:
class Point(namedtuple('Point', 'x y')):
__cache = {}
def __new__(cls, x, y):
return Point.__cache.setdefault((x, y),
super(cls, Point).__new__(cls, x, y))
As #PetrViktorin noted in his answer you should consider the use of a weakref.WeakValueDictionary so deleted instances of the class (doesn't work with namedtuple apparently) don't remain in memory since they remain referenced in the dictionary itself.
You need to have a global dictionary of objects, and get them through a factory function (or a custom __new__, see the other answers). Additionally, consider using a WeakValueDictionary so that you don't unnecessarily fill up memory with objects that are no longer needed.
from weakref import WeakValueDictionary
class _Point(object):
def __init__(self, x, y):
self.x = x
self.y = y
# Cache of Point objects the program currently uses
_points = WeakValueDictionary()
def Point(x, y):
"""Create a Point object"""
# Note that this is a function (a "factory function")
# You can also override Point.__new__ instead
try:
return _points[x, y]
except KeyError:
_points[x, y] = point = _Point(x, y)
return point
if __name__ == '__main__':
# A basic demo
print Point(1, 2)
print id(Point(1, 2))
print Point(2, 3) == Point(2, 3)
pt_2_3 = Point(2, 3)
# The Point(1, 2) we created earlier is not needed any more.
# In current CPython, it will have been been garbage collected by now
# (but note that Python makes no guarantees about when objects are deleted)
# If we create a new Point(1, 2), it should get a different id
print id(Point(1, 2))
Note that a namedtuple won't work with WeakValueDictionary.
If you need to compare whether your two objects house the same values, you can implement the eq operator:
>>> class Point(object):
... def __init__(self, x, y):
... self.x = x
... self.y = y
... def __eq__(self, other):
... return self.x == other.x and self.y == other.y
...
>>> a = Point(1,2)
>>> b = Point(1,2)
>>> a == b
True
>>> b = Point(2,2)
>>> a == b
False

Is it possible to instantiate an object of one class in two different ways?

Here is an example which creates a point as p=Point(x, y). Assume that I have some array ppp=(x, y) where x and y are numbers and I want to make it of class Point but in the way: p=Point(ppp). I can do either one or another way but not both simultaneously. Is it possible to have both ways?
There are two different ways to acquire the result, the first is to analyse arguments that you pass to __init__ and in dependence of their quantity and type - choose a decision what are you using to instantiate class.
class Point(object):
x = 0
y = 0
def __init__(self, x, y=None):
if y is None:
self.x, self.y = x, x
else:
self.x, self.y = x, y
The other decision is to use classmethods as instantiators:
class Point(object):
x = 0
y = 0
#classmethod
def from_coords(cls, x, y):
inst = cls()
inst.x = x
inst.y = y
return inst
#classmethod
def from_string(cls, x):
inst = cls()
inst.x, inst.y = x, x
return inst
p1 = Point.from_string('1.2 4.6')
p2 = Point.from_coords(1.2, 4.6)
If you know that you have a tuple/list while creating the instance, you can do: p = Point(*ppp), where ppp is the tuple.
class Point:
def __init__(self, x, y=None):
if isinstance(x, tuple):
self.x, self.y = x
else:
self.x = x
self.y = y
Yes:
class Point(object):
def __init__(self, x, y=None):
if y is not None:
self.x, self.y = x, y
else:
self.x, self.y = x
def __str__(self):
return "{}, {}".format(self.x, self.y)
print Point(1,2)
# 1, 2
print Point((1,2))
# 1, 2
I would guess that your looking for a way to overload your constructor, as is common in statically typed languages such as C++ and Java.
This is not possible in Python. What you can do is provide different keyword argument combinations, something like:
class Point(object):
def __init__(self, x=None, y=None, r=None, t=None):
if x is not None and y is not None:
self.x = x
self.y = y
elif r is not None and t is not None:
# set cartesian coordinates from polar ones
Which you would then use as:
p1 = Point(x=1, y=2)
p2 = Point(r=1, t=3.14)

Categories