Let's say I want to store some information about a conference schedule with a presentation time and a pause time. I can do this in a NamedTuple.
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
However, if I also want to store how much each block would take such that t_each = t_pause + t_present, I can't just add it as an attribute:
class BlockTime(NamedTuple):
t_present: float
t_pause: float
# this causes an error
t_each = t_present + t_pause
What is the correct way to do this in Python? If I make an __init__(self) method and store it as an instance variable there, but it would then be mutable.
In case it would be okay that it's not really stored but calculated dynamically you could use a simple property for it.
from typing import NamedTuple
class BlockTime(NamedTuple):
t_present: float
t_pause: float
#property
def t_each(self):
return self.t_present + self.t_pause
>>> b = BlockTime(10, 20)
>>> b.t_each # only available as property, not in the representation nor by indexing or iterating
30
That has the advantage that you can never (not even accidentally) store a wrong value for it. However at the expense of not actually storing it at all. So in order to appear as if it were stored you'd have to at least override __getitem__, __iter__, __repr__ which is likely too much trouble.
For example the NamedTuple approach given by Patrick Haugh has the downside that it's still possible to create inconsistent BlockTimes or lose parts of the namedtuple convenience:
>>> b = BlockTime.factory(1.0, 2.0)
>>> b._replace(t_present=20)
BlockTime(t_present=20, t_pause=2.0, t_each=3.0)
>>> b._make([1, 2])
TypeError: Expected 3 arguments, got 2
The fact that you actually have a "computed" field that has to be in sync with other fields already indicates that you probably shouldn't store it at all to avoid inconsistent state.
You can make a classmethod that builds BlockTime objects
class BlockTime(NamedTuple):
t_present: float
t_pause: float
t_each: float
#classmethod
def factory(cls, present, pause):
return cls(present, pause, present+pause)
print(BlockTime.factory(1.0, 2.0))
# BlockTime(t_present=1.0, t_pause=2.0, t_each=3.0)
EDIT:
Here's a solution using the new Python 3.7 dataclass
from dataclasses import dataclass, field
#dataclass(frozen=True)
class BlockTime:
t_present: float
t_pause: float
t_each: float = field(init=False)
def __post_init__(self):
object.__setattr__(self, 't_each', self.t_present + self.t_pause)
Frozen dataclasses aren't totally immutable but they're pretty close, and this lets you have natural looking instance creation BlockTime(1.0, 2.0)
Well.. You cant override __new__ or __init__ of a class whose parent is NamedTuple. But you can overide __new__ of a class, inherited from another class whose parent is NamedTuple.
So you can do something like this
from typing import NamedTuple
class BlockTimeParent(NamedTuple):
t_present: float
t_pause: float
t_each: float
class BlockTime(BlockTimeParent):
def __new__(cls, t_present, t_pause):
return super().__new__(cls, t_present, t_pause, t_present+ t_pause)
b = BlockTime(1,2)
print (b)
# BlockTime(t_present=1, t_pause=2, t_each=3)
Related
Is there any way to add a pointer to data class structure?
from dataclasses import dataclass
#dataclass
class Point:
x: int
y: int
pointer: pointer # Here
If you want to create a singly-linked list of Point objects then you can do
from dataclasses import dataclass
#dataclass
class Point:
x: int
y: int
next: 'Point' = None
And then you can do, for example:
a = Point(1, 1)
b = Point(4, 5)
a.next = b
Unless you have a specific need for a linked list, though, I would recommend using one of the standard Python data types like list. There's usually little need to roll your own, especially something like a linked list that takes a lot of management.
Assuming pointer is a class, sure, there's no restrictions.
#dataclass
class Point:
x: int
y: int
pointer: pointer
If you don't know (or care) about the concrete type, you can always use Any as the type. Dataclasses really only care about the value.
from typing import Any
#dataclass
class Point:
x: int
y: int
pointer: Any
Now pointer is a field in the dataclass which can contain anything at all.
class character():
class personalized:
def __init__(self, name, age, height, width, hair_color, eye_color):
# I want to do this
for attr in personalized:
personalized.attr = attr
#instead of this
personalized.name = name
personalized.age = age
personalized.height = height
If I am using classes with a lot of attributes I don't want to have to set it equal to a variable every time because it will take up a lot of space. Is there a way to write it like I did above but actually works. In essence I don't know how to retrieve attributes from the __init__ function.
I would recommend using dataclasses for this. In your case you would just add:
from dataclasses import dataclass
#dataclass
class personalized:
name: str
age: int
height: int
width: int
hair_color: str
eye_color: str
This will auto-construct a init for you with self-assigned attributes
You could use the __set__(..) Function (https://docs.python.org/3/howto/descriptor.html), but I do not suggest this because:
From a readability perspective this will harder to maintain over the long term (code is more often read than written)
everytime you want to access such an entry you first of all have to check if the descriptor/attribute is availible, thus making your down the road code worse.
see:
How to know if an object has an attribute in Python
Using vars() and setattr() can do what you want, but I recommend dataclasses as in the other answer if using Python 3.7+.
vars() returns a dictionary of local variables. At the top of __init__ dictionary contains self and any parameters as keys and along with their values.
setattr() will set an attribute value on an object.
class Personalized:
def __init__(self, name, age, height, width, hair_color, eye_color):
for key,value in vars().items():
if key != 'self':
setattr(self, key, value)
p = Personalized('name',5,10,12,'black','blue')
print(p.name,p.age,p.height,p.width,p.hair_color,p.eye_color)
Output:
name 5 10 12 black blue
I'm trying to subclass the IntEnum to start members' value at a certain value and then automatically set the value for subsequent members. This is my class:
class Abc(IntEnum):
def __init__(self, n=100):
super().__init__()
self._value_ = n + len(self.__class__.__members__)
A = () # 100
B = () # 101
Abc.A == Abc.B # expects False, but gets True
As shown above the comparison between the members is not correct. When printing out Abc.dict, I noticed that it _value2member_map_ does not look correct either.
mappingproxy({'A': <Abc.A: 100>,
'B': <Abc.B: 101>,
'__doc__': 'An enumeration.',
'__init__': <function __main__.Abc.__init__>,
'__module__': '__main__',
'__new__': <function enum.Enum.__new__>,
'_generate_next_value_': <function enum.Enum._generate_next_value_>,
'_member_map_': OrderedDict([('A', <Abc.A: 100>),
('B', <Abc.B: 101>)]),
'_member_names_': ['A', 'B'],
'_member_type_': int,
'_value2member_map_': {0: <Abc.B: 101>}})
Notice how '_value2member_map_' has key 0 instead of the expected values 100 and 101. I must be missing something in the init function, but I could not figure out how to properly do what I intended. Any help is appreciated.
Thank you.
First, there's a more idiomatic—and dead simple—way to do what you seem to be trying to do:
class Abc(IntEnum):
A = 100
B = auto()
Or, given that you're putting 100 and 101 in as comments anyway, live code is always better than comments:
class Abc(IntEnum):
A = 100
B = 101
The fact that you're not doing either of those is a signal to the reader that you're probably doing to do something more complicated. Except that, as far as I can tell, you aren't, so this is misleading.
Plus, you're combining two patterns that have directly opposite connotations: as the docs say, using the () idiom "signifies to the user that these values are not important", but using IntEnum obviously means that the numeric values of these enumeration constants are not just important but the whole point of them.
Not only that, but the user has to read through your method code to figure out what those important numeric values are, instead of just immediately reading them off.
Anyway, if you want to get this to work, the problem is that replacing _value_ after initialization isn't documented to do any good, and in fact it doesn't.
What you want to override is __new__, not __init__, as in the auto-numbering example in the docs.
But there are two differences here (both related to the fact that you're using IntEnum instead of Enum):
You cannot call object.__new__, because an IntEnum is an int, and object.__new__ can't be used on instances of builtin types like int. You can figure out the right base class dynamically from looking through cls's mro, or you can just hardcode int here.
You don't need an intermediate base class here to do the work. (You might still want one if you were going to create multiple auto-numbered IntEnums, of course.)
So:
class Abc(IntEnum):
def __new__(cls, n=100):
value = len(cls.__members__) + n
obj = int.__new__(cls, value)
obj._value_ = value
return obj
A = ()
B = ()
Say I have a Python class as follows:
class TestClass():
value = 20
def __str__(self):
return str(self.value)
The __str__ method will automatically be called any time I try to use an instance of TestClass as a string, like in print. Is there any equivalent for treating it as a number? For example, in
an_object = TestClass()
if an_object > 30:
...
where some hypothetical __num__ function would be automatically called to interpret the object as a number. How could this be easily done?
Ideally I'd like to avoid overloading every normal mathematical operator.
You can provide __float__(), __int__(), and/or __complex__() methods to convert objects to numbers. There is also a __round__() method you can provide for custom rounding. Documentation here. The __bool__() method technically fits here too, since Booleans are a subclass of integers in Python.
While Python does implicitly convert objects to strings for e.g. print(), it never converts objects to numbers without you saying to. Thus, Foo() + 42 isn't valid just because Foo has an __int__ method. You have to explicitly use int() or float() or complex() on them. At least that way, you know what you're getting just by reading the code.
To get classes to actually behave like numbers, you have to implement all the special methods for the operations that numbers participate in, including arithmetic and comparisons. As you note, this gets annoying. You can, however, write a mixin class so that at least you only have to write it once. Such as:
class NumberMixin(object):
def __eq__(self, other): return self.__num__() == self.__getval__(other)
# other comparison methods
def __add__(self, other): return self.__num__() + self.__getval__(other)
def __radd__(self, other): return self.__getval__(other) + self.__num__()
# etc., I'm not going to write them all out, are you crazy?
This class expects two special methods on the class it's mixed in with.
__num__() - converts self to a number. Usually this will be an alias for the conversion method for the most precise type supported by the object. For example, your class might have __int__() and __float__() methods, but __int__() will truncate the number, so you assign __num__ = __float__ in your class definition. On the other hand, if your class has a natural integral value, you might want to provide __float__ so it can also be converted to a float, but you'd use __num__ = __int__ since it should behave like an integer.
__getval__() - a static method that obtains the numeric value from another object. This is useful when you want to be able to support operations with objects other than numeric types. For example, when comparing, you might want to be able to compare to objects of your own type, as well as to traditional numeric types. You can write __getval__() to fish out the right attribute or call the right method of those other objects. Of course with your own instances you can just rely on float() to do the right thing, but __getval__() lets you be as flexible as you like in what you accept.
A simple example class using this mixin:
class FauxFloat(NumberMixin):
def __init__(self, value): self.value = float(value)
def __int__(self): return int(self.value)
def __float__(self): return float(self.value)
def __round__(self, digits=0): return round(self.value, digits)
def __str__(self): return str(self.value)
__repr__ = __str__
__num__ = __float__
#staticmethod
def __getval__(obj):
if isinstance(obj, FauxFloat):
return float(obj)
if hasattr(type(obj), "__num__") and callable(type(obj).__num__):
return type(obj).__num__(obj) # don't call dunder method on instance
try:
return float(obj)
except TypeError:
return int(obj)
ff = FauxFloat(42)
print(ff + 13) # 55.0
For extra credit, you could register your class so it'll be seen as a subclass of an appropriate abstract base class:
import numbers
numbers.Real.register(FauxFloat)
issubclass(FauxFloat, numbers.Real) # True
For extra extra credit, you might also create a global num() function that calls __num__() on objects that have it, otherwise falling back to the older methods.
In case of numbers it a bit more complicated. But its possible! You have to override your class operators to fit your needs.
operator.__lt__(a, b) # lower than
operator.__le__(a, b) # lower equal
operator.__eq__(a, b) # equal
operator.__ne__(a, b) # not equal
operator.__ge__(a, b) # greater equial
operator.__gt__(a, b) # greater than
Python Operators
Looks like you need __gt__ method.
class A:
val = 0
def __gt__(self, other):
return self.val > other
a = A()
a.val = 12
a > 10
If you just wanna cast object to int - you should define __int__ method (or __float__).
Can magic methods be overridden outside of a class?
When I do something like this
def __int__(x):
return x + 5
a = 5
print(int(a))
it prints '5' instead of '10'. Do I do something wrong or magic methods just can't be overridden outside of a class?
Short answer; not really.
You cannot arbitrarily change the behaviour of int() a builtin function (*which internally calls __int__()) on arbitrary builtin types such as int(s).
You can however change the behaviour of custom objects like this:
Example:
class Foo(object):
def __init__(self, value):
self.value = value
def __add__(self, other):
self.value += other
def __repr__(self):
return "<Foo(value={0:d})>".format(self.value)
Demo:
>>> x = Foo(5)
>>> x + 5
>>> x
<Foo(value=10)>
This overrides two things here and implements two special methods:
__repr__() which get called by repr()
__add__() which get called by the + operator.
Update: As per the comments above; techincally you can redefine the builtin function int; Example:
def int(x):
return x + 5
int(5) # returns 10
However this is not recommended and does not change the overall behaviour of the object x.
Update #2: The reason you cannot change the behaviour of bultin types (without modifying the underlying source or using Cuthon or ctypes) is because builtin types in Python are not exposed or mutable to the user unlike Homoiconic Languages (See: Homoiconicity). -- Even then I'm not really sure you can with Cython/ctypes; but the reason question is "Why do you want to do this?"
Update #3: See Python's documentation on Data Model (object.__complex__ for example).
You can redefine a top-level __int__ function, but nobody ever calls that.
As implied in the Data Model documentation, when you write int(x), that calls x.__int__(), not __int__(x).
And even that isn't really true. First, __int__ is a special method, meaning it's allowed to call type(x).__int__(x) rather than x.__int__(), but that doesn't matter here. Second, it's not required to call __int__ unless you give it something that isn't already an int (and call it with the one-argument form). So, it could be as if it's was written like this:
def int(x, base=None):
if base is not None:
return do_basey_stuff(x, base)
if isinstance(x, int):
return x
return type(x).__int__(x)
So, there is no way to change what int(5) will do… short of just shadowing the builtin int function with a different builtin/global/local function of the same name, of course.
But what if you wanted to, say, change int(5.5)? That's not an int, so it's going to call float.__int__(5.5). So, all we have to do is monkeypatch that, right?
Well, yes, except that Python allows builtin types to be immutable, and most of the builtin types in CPython are. So, if you try it:
>>> _real_float_int = float.__int__
>>> def _float_int(self):
... return _real_float_int(self) + 5
>>> _float_int(5.5)
10
>>> float.__int__ = _float_int
TypeError: can't set attributes of built-in/extension type 'float'
However, if you're defining your own types, that's a different story:
>>> class MyFloat(float):
... def __int__(self):
... return super().__int__() + 5
>>> f = MyFloat(5.5)
>>> int(f)
10