Pythonic way to control an attribute over another attribute - python

There is a pythonic way to control attributes?
I wan't that the init method is clear, trying to avoid to use a function like:
self.a = check_a_greater_than_b(a)
and avoid to do controls in the init method
def __init__(self, a, b):
if b > a:
raise AttributeError('A should be > B')
else:
self.a = a
self.b = b
I was think to use the setter or settattr, can you post me an example please?

You can do something like this :
class Object():
def __init__(self, a, b):
self.b = b
self.a = a
#property
def a(self):
return self.__a
#a.setter
def a(self, a):
assert a > self.b, "A should be > B"
self.__a = a
Then you will have :
o = Object(3,2)
o.a = 1
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 12, in a
AssertionError: A should be > B

Related

Using getter and setter with three attributes

Please, explain what I do not understand about getters and setters, that this code catch the exception already when I'm trying to instance the Test class?
The same, as it seems to me, have worked here.
My goal is to update c depending on a and b, where all these properties should be accessible from outside of the class, i.e. public fields, as far as I understand.
class Test:
def __init__(self, p1=50, p2=20):
self.a = p1
self.b = p2
#property
def a(self):
return self._a
#a.setter
def a(self, val):
self._a = val
self._c = self.b - val // 5
#property
def b(self):
return self._b
#b.setter
def b(self, val):
self._b = val
#property
def c(self):
return self._c
>>> c = Test()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "...\getter_and_setter.py", line 3, in __init__
self.a = p1
File "...\getter_and_setter.py", line 12, in a
self._c = self.b - val // 5
File "...\getter_and_setter.py", line 16, in b
return self._b
AttributeError: 'Test' object has no attribute '_b'
There is flaw in your implementation.
Setting a depends on b being already set. If you swap the 2 assignment statements in the __init__ it will solve your current problem. However note there is big flaw in your implementation. If you change b that change will not be reflected in c.
There is no need to use getters and setters for a and b.
class Test:
def __init__(self, p1=50, p2=20):
self.a = p1
self.b = p2
#property
def c(self):
return self.b - self.a // 5

Purpose of return self python

I have a problem with return self
class Fib:
def __init__(self, max):
self.max = max
def __iter__(self):
self.a = 0
self.b = 1
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
I have already seen this question return self problem but I can't understand what the benefit is of return self?
Returning self from a method simply means that your method returns a reference to the instance object on which it was called. This can sometimes be seen in use with object oriented APIs that are designed as a fluent interface that encourages method cascading. So, for example,
>>> class Counter(object):
... def __init__(self, start=1):
... self.val = start
... def increment(self):
... self.val += 1
... return self
... def decrement(self):
... self.val -= 1
... return self
...
>>> c = Counter()
Now we can use method cascading:
>>> c.increment().increment().decrement()
<__main__.Counter object at 0x1020c1390>
Notice, the last call to decrement() returned <__main__.Counter object at 0x1020c1390>, which is self.
Now:
>>> c.val
2
>>>
Notice, you cannot do this if you did not return self:
>>> class Counter(object):
... def __init__(self, start=1):
... self.val = start
... def increment(self):
... self.val += 1
... # implicitely return `None`
... def decrement(self):
... self.val -= 1
... # implicitely return `None`
...
>>> c = Counter()
>>> c.increment().increment()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'increment'
>>> c
<__main__.Counter object at 0x1020c15f8>
>>> c.val
2
>>>
Notice, not everyone is a fan of "method cascading" design. Python built-ins do not tend do this, so, list for example:
>>> x = list()
>>> x.append(1).append(2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'append'
>>>
The one place you do often see this is when your class implements the iterator protocol, where iter on an iterator returns self by convention, although this is suggested by the docs:
Having seen the mechanics behind the iterator protocol, it is easy to
add iterator behavior to your classes. Define an __iter__() method
which returns an object with a __next__() method. If the class
defines __next__(), then __iter__() can just return self:
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration
self.index = self.index - 1
return self.data[self.index]
Notice, this in effect makes your iterator only useful for a single pass (as it should be to properly follow the iterator protocol):
>>> x = [1, 2, 3, 4]
>>> it = iter(x)
>>> list(it)
[1, 2, 3, 4]
>>> list(it)
[]
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
This is needlessly complex code. Pay little attention to it. There's no reason on earth to implement it this way.
That being said, what it does is this:
class Fib:
"""Implements the Fibonacci sequence."""
def __init__(self, max_):
self.max = max_
def __iter__(self):
"""Initializes and returns itself as an iterable."""
self.a = 0
self.b = 1
return self
def __next__(self):
"""What gets run on each execution of that iterable."""
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b # increment
return fib
This is all much easier to express as:
def fib(max_):
a, b = 0, 1
while b <= max_:
out = a
a, b = b, a+b
yield out
Examples:
>>> fib_obj = Fib(20)
>>> for n in fib_obj:
... print(n)
>>> for n in Fib(20):
... print(n)
>>> for n in fib(20):
... print(n)
# all give....
0
1
1
2
3
5
8
13

Why does my __init__ function need to be #classmethod?

Here is a code snippet I wrote as a test. I noticed that if I don't define the init method as a classmethod, the code doesn't run:
class A(object):
def __init__(self):
self.value = 0
self.add(1)
#classmethod
def add(self, arg):
self.value += arg
class B(A):
#classmethod
def add(self, arg):
self.value += arg * 2
if __name__ == '__main__':
a = A()
b = B()
print a.value
print b.value
This outputs:
Traceback (most recent call last):
File "inherit.py", line 17, in <module>
a = A()
File "inherit.py", line 4, in __init__
self.add(1)
File "inherit.py", line 8, in add
self.value += arg
AttributeError: type object 'A' has no attribute 'value'
However if I change my init function to be #classmethod, the code works as intended:
class A(object):
#classmethod
def __init__(self):
self.value = 0
self.add(1)
#classmethod
def add(self, arg):
self.value += arg
class B(A):
#classmethod
def add(self, arg):
self.value += arg * 2
if __name__ == '__main__':
a = A()
b = B()
print a.value
print b.value
Output:
1
2
I was under the impression init was by default a class method whose first argument must be self. What is going on?
The problem is that you have add marked as a classmethod, but it isn't. Take out the #classmethod from the adds and it should work.

Python assigning two variables on one line

class Domin():
def __init__(self , a, b) :
self.a=a , self.b=b
def where(self):
print 'face : ' , self.a , "face : " ,self.b
def value(self):
print self.a + self.b
d1=Domin(1 , 5)
d1=Domin(20 , 15)
I get this error:
Traceback (most recent call last):
File "test2.py", line 13, in <module>
d1=Domin(1 , 5)
File "test2.py", line 5, in __init__
self.a=a , self.b=b
TypeError: 'int' object is not iterable
You cannot put two statements on one line like that. Your code is being evaluated like this:
self.a = (a, self.b) = b
Either use a semicolon (on second thought, don't do that):
self.a = a; self.b = b
Or use sequence unpacking:
self.a, self.b = a, b
Or just split it into two lines:
self.a = a
self.b = b
I would do it the last way.

Python : How to "merge" two class

I want to add some attributes and methods into various class. The methods and attributes that I have to add are the same but not the class to assign them, so I want to construct a class who assign new methods and attributes for a class given in argument.
I try this but it's not working:
(I know that is a very wrong way to try to assign something to self, it's just to show what I want to do)
class A:
def __init__(self):
self.a = 'a'
def getattA(self):
return self.a
class B:
def __init__(self, parent) :
self = parent
# This is working :
print self.getattA()
def getattB(self):
return self.getattA()
insta = A()
instb = B(insta)
# This is not working :
print instb.getattB()
The result is :
a
Traceback (most recent call last):
File "D:\Documents and settings\Bureau\merge.py", line 22, in <module>
print instb.getattB()
File "D:\Documents and settings\Bureau\merge.py", line 16, in getattB
return self.getattA()
AttributeError: B instance has no attribute 'getattA'
And I expected to got 'a' for the call of instb.gettattB()
To resume I want to inherit class B from class A giving class A in argument of class B because my class B will be a subclass of various class, not always A.
The Best answer is in the comments, it was useful for me so I decided to show it in an answer (thank to sr2222):
The way to dynamicaly declare inherance in Python is the type() built-in function.
For my example :
class A(object) :
def __init__(self, args):
self.a = 'a'
self.args = args
def getattA(self):
return self.a, self.args
class B(object) :
b = 'b'
def __init__(self, args) :
self.b_init = args
def getattB(self):
return self.b
C = type('C', (A,B), dict(c='c'))
instc = C('args')
print 'attributes :', instc.a, instc.args, instc.b, instc.c
print 'methodes :', instc.getattA(), instc.getattB()
print instc.b_init
The code return :
attributes : a args b c
methodes : ('a', 'args') b
Traceback (most recent call last):
File "D:\Documents and settings\Bureau\merge2.py", line 24, in <module>
print instc.b_init
AttributeError: 'C' object has no attribute 'b_init'
My class C inerhite attributes and methods of class A and class B and we add c attribute. With the instanciation of C (instc = C('args')) The init for A is call but not for B.
Very useful for me because I have to add some attributes and methodes (the same) on different class.
I was having trouble with calling different constructors, using super doesn't necessarily make sense in a case like this, I opted to inherit and call each constructor on the current object manually:
class Foo(object):
def __init__(self, foonum):
super(Foo, self).__init__()
self.foonum = foonum
class Bar(object):
def __init__(self, barnum):
super(Bar, self).__init__()
self.barnum = barnum
class DiamondProblem(Foo, Bar):
# Arg order don't matter, since we call the `__init__`'s ourself.
def __init__(self, barnum, mynum, foonum):
Foo.__init__(self, foonum)
Bar.__init__(self, barnum)
self.mynum = mynum
How about this?
class A:
def __init__(self):
self.a = 'a'
def getatt(self):
return self.a
class B:
def __init__(self, parent) :
self.parent = parent
def __getattr__(self, attr):
return getattr(self.parent, attr)
def getattB(self):
return self.parent.getatt()
insta = A()
instb = B(insta)
print instb.getattB()
print instb.getatt()
But method in class A can not access attr in class B.
Another way:
import functools
class A:
def __init__(self):
self.a = 'a'
def getatt(self):
return self.a
class B:
def __init__(self, parent):
for attr, val in parent.__dict__.iteritems():
if attr.startswith("__"): continue
self.__dict__[attr] = val
for attr, val in parent.__class__.__dict__.iteritems():
if attr.startswith("__"): continue
if not callable(val): continue
self.__dict__[attr] = functools.partial(val, self)
def getattB(self):
return self.getatt()
insta = A()
instb = B(insta)
print instb.__dict__
print instb.getattB()
print instb.getatt()
Slow with init but call fast.
Since B is not a subclass of A, there is no path in B to getatt() in A
I guess i have a easier method
class fruit1:
def __init__(self):
self.name = "apple"
self.color = "blue"
class fruit2:
def __init__(self):
self.name = "banana"
self.size = 100
def merge(ob1, ob2):
ob1.__dict__.update(ob2.__dict__)
return ob1
f1 = fruit1()
f2 = fruit2()
fruit = merge(f1, f2)
print("name:",fruit.name," color:",fruit.color, " size:",fruit.size)
#output: name: banana color: blue size: 100
I'm not certain what you are trying to do, but the code below is giving my the output I think you are expecting. notice:
a is initialized outside the constructor in A
B is declared as a subclass of A
Code:
class A:
a='' #Initialize a
def __init__(self):
self.a = 'a'
def getatt(self):
return self.a
class B(A): #Declare B as subclass
def __init__(self, parent) :
self = parent
print self.getatt()
def getattB(self):
return self.getatt()
insta = A()
instb = B(insta)
print instb.getattB()
Helper function below conducts the merge of the dataclass instances, the attributes orders is derived from *args order:
from dataclasses import dataclass
#dataclass
class A:
foo: str
bar: str
def merge_dataclasses(*args):
if len({e.__class__.__name__ for e in args}) > 1:
raise NotImplementedError('Merge of non-homogeneous entries no allowed.')
data = {}
for entry in args[::-1]:
data.update(vars(entry))
return entry.__class__(**data)
print(merge_dataclasses(A(foo='f', bar='bar'), A(foo='b_foo', bar='b_bar')))
One easy way to merge two or more classes is through the tool set dyndesign:
from dyndesign import mergeclasses
class Base:
def __init__(self, init_value):
self.param = init_value
def m1(self):
print(f"Method `m1` of class `Base`, and {self.param=}")
def m2(self):
print(f"Method `m2` of class `Base`")
class Ext:
def m1(self):
print(f"Method `m1` of class `Ext`, and {self.param=}")
MergedClass = mergeclasses(Base, Ext)
merged_instance = MergedClass("INITIAL VALUE")
merged_instance.m1()
# Method `m1` of class `Ext`, and self.param='INITIAL VALUE'
merged_instance.m2()
# Method `m2` of class `Base`
Emphasizing ThorSummoner's's answer and Hong's comment; this method appears to be cleaner than the excepted answer. Notice Hong's use of super().init(self) in all but the last object added to the merge class.
class Foo(object):
def __init__(self, foonum):
super(Foo, self).__init__(self)
self.foonum = foonum
class Bar(object):
def __init__(self, barnum):
super(Bar, self).__init__(self)
self.barnum = barnum
class Oops(object):
def __init__(self, oopsnum):
super(Oops, self).__init__()
self.oopsnum = oopsnum
class DiamondProblem(Foo, Bar, Oops):
def __init__(self, mynum, foonum, barnum, oopsnum):
Foo.__init__(self, foonum)
Bar.__init__(self, barnum)
Oops.__init__(self, oopsnum)
self.mynum = mynum
def main():
dia = DiamondProblem(1, 10, 20, 30)
print(f"mynum: {dia.mynum}")
print(f"foonum: {dia.foonum}")
print(f"barnum: {dia.barnum}")
print(f"oopsnum: {dia.oopsnum}")

Categories

Resources