Can a map be used in the 'def __init__' ? And how to read errors - oop

I've created a class for a Counter that I'm wanting to build, and I want this object to figure out player scores. I'm expecting to see 3 when I test it, however what I get is the following error:
TypeError: unsupported operand type(s) for += 'method' and 'int' I'm not sure what that means, so I'm not sure if the issue is the map, my object itself, or my syntax. Any and all advice is welcome!
class Counter:
def __init__(self):
self.pointValue = {'Item_1': 3,
'Item_2': 5,
'Item_3': -3
}
def playerScore(self, itemFound ):
self.playerScore += (self.pointValue[itemFound])
return self.playerScore
gameCounter = Counter()
x = 'Item_1'
print (gameCounter.playerScore(x))

You can absolutely use a map (in Python it's called a dict) in __init__; it is not that part of the code that is throwing the error.
The message unsupported operand type(s) for += 'method' and 'int' refers to the line
self.playerScore += (self.pointValue[itemFound])
where self.playerScore is the method you are currently calling; it makes no sense to add an integer to a method, so an error is raised.
I think you actually want something like:
class ScoreCounter:
POINT_VALUE = {'Item_1': 3,
'Item_2': 5,
'Item_3': -3}
def __init__(self):
self.score = 0
def find_item(self, item):
self.score += self.POINT_VALUE.get(item, 0)
return self.score
Which you could use like:
>>> c = ScoreCounter()
>>> c.find_item("Item_1")
3
Note that the POINT_VALUE is a class attribute, shared by all ScoreCounter instances, whereas the score is an instance attribute, separate for each one. Also note that score is a different name to find_item, so you don't end up trying to assign to the method, and I have avoided the name Counter as it's used elsewhere. Finally, I have adopted naming conventions from the Python style guide.

Related

Python 3.10 - How to properly type hint __add__ in a dataclass that inherits Generic[TypeVar]?

I have this package called totemp that provides convertions between temperature scales. It's currently in development and I just don't know how to type hint the add magic method.
There are 8 dataclasses, Celsius, Fahrenheit, Delisle, Kelvin, Newton, Rankine, Réaumur and Romer.
Celsius class with dunder add and one of the convertion methods (#no_type_check to prevent mypy from not letting me commit).
from dataclasses import dataclass, field
from typing import Generic, TypeVar, no_type_check
TEMP = TypeVar('TEMP', int, float) # TEMP must be int or float
#dataclass
class Celsius(Generic[TEMP]):
"""..."""
__value: TEMP
__symbol: str = field(compare=False, repr=False, default='ºC')
def __str__(self) -> str:
return f'{self.__value}{self.__symbol}'
def __repr__(self) -> str:
return f'Celsius({self.__value})'
#no_type_check
def __add__(self, other):
match str(type(other)):
case "<class 'totemp.temperature_types.Celsius'>":
return Celsius(self.__value + other.value)
case _:
try:
other = other.to_celsius()
return Celsius(self.__value + other.value)
except AttributeError as error:
print(
f'\033[31;1mAttributeError: {error}.\n ' +
f"Cause: '{other}' is not a temperature scale.\033[m"
)
def to_fahrenheit(self) -> 'Fahrenheit[TEMP]':
"""
Returns a Fahrenheit object which contains the class attribute "value"
with the result from the conversion typed the same as the attribute.
Returns
-------
Fahrenheit[TEMP]
"""
fahrenheit = type(self.__value)(self.__value * 9 / 5 + 32)
return Fahrenheit(fahrenheit)
I tried creating an abstract class, but that didn't worked well, didn't even know what I was doing tbh. Tried read Python docs, but I had no response for what i'm trying to do, couldn't find any solution for myself. Thought about NewType, but after reading about in the docs, didn't understood it very well too.
Thought about doing something like:
GenericType = NewType('Temp', Union[Celsius, Fahrenheit, ...])
But ended up with more mypy errors that I couldn't deal with.
How do I properly type hint dunder add and other magic methods (such as gt, lt, e.g.)?
I got lost in more and more things I thought might be optimized with the code, so I ended up placing a merge request for your current dev branch.
To quickly answer the basic idea relevant for this particular question:
I don't think the class should be generic. I don't understand the purpose of that.
A base class is definitely a good idea (considering the DRY principle). Making it abstract, you could enforce a convert_to method for all subclasses, which you could then utilize in your numeric operand dunder methods like __add__.
Then typing would become fairly easy with bounded type variables:
from abc import ABCMeta, abstractmethod
from typing import Any, ClassVar, TypeVar
T = TypeVar("T", bound="AbstractTemperature")
class AbstractTemperature(metaclass=ABCMeta):
def __init__(self, value: float) -> None:
self._value = value
#property
def value(self) -> float:
return self._value
#abstractmethod
def convert_to(self, temp_cls: type[T]) -> T:
...
def __add__(self: T, other: Any) -> T:
cls = self.__class__
try:
if isinstance(other, AbstractTemperature):
return cls(self._value + other.convert_to(cls).value)
return cls(self._value + other)
except TypeError:
return NotImplemented

How do I take a reference to new?

Suppose I have the following code:
my constant #suits = <Clubs Hearts Spades Diamonds>;
my constant #values = 2..14;
class Card {
has $.suit;
has $.value;
# order is mnemonic of "$value of $suit", i.e. "3 of Clubs"
multi method new($value, $suit) {
return self.bless(:$suit, :$value);
}
}
It defines some suits and some values and what it means to be a card.
Now, to build a deck, I essentially need to take the cross product of the suits and the values and apply that to the constructor.
The naiive approach to do this, would of course be to just iterate with a loop:
my #deck = gather for #values X #suits -> ($v, $c) {
take Card.new($v, $c);
}
But this is Raku, we have a cross function that can take a function as an optional argument!, so of course I'm gonna do that!
my #deck = cross(#values, #suits, :with(Card.new));
# Unexpected named argument 'with' passed
# in block <unit> at .\example.raku line 36
... wait no.
What about this?
my #deck = cross(#values, #suits):with(Card.new);
# Unexpected named argument 'with' passed
# in block <unit> at .\example.raku line 36
Still nothing. Reference maybe?
my #deck = cross(#values, #suits):with(&Card.new);
# ===SORRY!=== Error while compiling D:\Code\Raku/.\example.raku
# Illegally post-declared type:
# Card used at line 36
I read somewhere I can turn a function into an infix operator with []
my #deck = cross(#values, #suits):with([Card.new]);
# Unexpected named argument 'with' passed
# in block <unit> at .\example.raku line 36
That also doesn't work.
If classes are supposed to just be modules, shouldn't I then be able to pass a function reference?
Also why is it saying 'with' is that's unexpected? If I'm intuiting this right, what it's actually complaining about is the type of the input, rather than the named argument.
The error message is indeed confusing.
The :with parameter expects a Callable. Card.new is not a Callable. If you write it as :with( { Card.new($^number, $^suit) } ), it appears to work.
Note that I did not use $^value, $^suit, because they order differently alphabetically, so would produce the values in the wrong order. See The ^ twigil for more information on that syntax.
The error is LTA, this makes it a little bit better.
To get back to your question: you can find the code object that corresponds to Card.new with ^find_method. However, that will not work, as Card.new actually expects 3 arguments: the invocant (aka self), $value and $suit. Whereas the cross function will only pass the value and the suit.
The title of your question is “How do I take a reference to new?”, but that is not really what you want to do.
Raku being Raku, you can actually get a reference to new.
my $ref = Card.^lookup('new');
You can't use it like you want to though.
$ref(2,'Clubs'); # ERROR
The problem is that methods take a class or instance as the first argument.
$ref(Card, 2,'Clubs');
You could use .assuming to add it in.
$ref .= assuming(Card);
$ref(2,'Clubs');
But that isn't really any better than creating a block lambda
$ref = { Card.new( |#_ ) }
$ref(2,'Clubs');
All of these work:
cross( #values, #suits ) :with({Card.new(|#_)}) # adverb outside
cross( #values, #suits, :with({Card.new(|#_)}) ) # inside at end
cross( :with({Card.new(|#_)}), #values, #suits ) # inside at beginning
#values X[&( {Card.new(|#_)} )] #suits # cross meta-op with fake infix op
do {
sub new-card ($value,$suit) { Card.new(:$value,:$suit) }
#values X[&new-card] #suits
}
do {
sub with ($value,$suit) { Card.new(:$value,:$suit) }
cross(#values,#suits):&with
}

"Invocant of method 'ASSIGN-KEY' must be an object instance" when using assignment operator

Hash with typed keys…
use v6;
class Foo {}
my Hash[Foo, Foo] $MAP;
my $f1 = Foo.new;
my $f2 = Foo.new;
$MAP{$f1} = $f2;
produces the error:
Invocant of method 'ASSIGN-KEY' must be an object instance of type 'Hash[Foo,Foo]', not a type object of type 'Hash[Foo,Foo]'. Did you forget a '.new'?
I find it misleading; what's the real error and what do I have to write instead?
I already tried the % sigil for the hash variable, that doesn't work, either.
In the way you have defined it, $MAP is actually a role. You need to instantiate (actually, pun) it:
class Foo {}
my Hash[Foo, Foo] $MAP;
my $map = $MAP.new;
my $f1 = Foo.new;
my $f2 = Foo.new;
$map{$f1} = $f2;
say $map;
Dead giveaway here was that classes can't be parametrized, roles do.
Also:
say $MAP.DEFINITE; # False
say $map.DEFINITE; # True
But actually the error message was pretty informative, up to and including the suggestion to use .new, as I do here.
We can shorten it down to:
class Foo {}
my %map = Hash[Foo, Foo].new ;
%map{Foo.new} = Foo.new;
%map.say;
By doing the punning from the definition, we don't need the $MAP intermediate class.
TL;DR JJ's answer is right, but the explanation left me confused. I currently view the problem you showed as an autovivification error/bug and/or LTA error message.
say my Any $Any; # (Any)
say my Hash $Hash; # (Hash)
say my Hash[Int] $Hash-Int; # (Hash[Int])
$Any<a> = 42; # OK
$Hash<a> = 42; # OK
$Hash-Int.new<a> = 42; # OK
$Hash-Int<a> = 42; # must be an object instance, not a type object
Imo this is a bug or pretty close to one.
A bug/problem applies for arrays too in the same scenario:
say my Any $Any; # (Any)
say my Array $Array; # (Array)
say my Array[Int] $Array-Int; # (Array[Int])
$Any[42] = 42; # OK
$Array[42] = 42; # OK
$Array-Int.new[42] = 42; # OK
$Array-Int[42] = 42; # Type check failed ... expected Array[Int] but got Array
If it's best considered notabug, then perhaps the error message should be changed. While I agree with JJ that the error message is actually on point (when you understand how raku works and figure out what's going on), I think it's nevertheless an LTA error message if we don't change raku(do) to dwim.
On the gripping hand, it's not obvious to me how one could best improve the error message. And now we have this SO. (cf my point about that in Is the ... error message LTA? in a recent answer I wrote.)
Another solution
I already tried the % sigil for the hash variable, that doesn't work, either.
JJ has provided a solution that initializes with a value with an explicit .new. But that drops the constraint from the variable. To retain it:
class Foo {}
constant FooFoo = Hash[Foo:D,Foo:D];
my %foo is FooFoo;
%foo{Foo.new} = Foo.new;
Ideally the constant wouldn't be needed, and perhaps one day it won't, but I think trait parsing is limited.

PyGObject, hex color to Gdk.RGBA

i found this code
gdk_rgba_parse ()
which should allow me to do something like
Gdk.RGBA.parse(#7F7F7F)
Error:
TypeError: unbound method parse() must be called with RGBA instance as first argument (got str instance instead)
using the RGBA color ( in percent, from 0 to 1 )
Docs:
https://developer.gnome.org/gdk3/stable/gdk3-RGBA-Colors.html#gdk-rgba-parse
http://www.crategus.com/books/cl-cffi-gtk/pages/gdk_fun_gdk-rgba-parse.html
But I'm kinda lost, i struggle to translate from C to PyGOBject and understanding the arguments of the function.. any help would be appreciated!
Since i didn't find the right solution, i made this converter:
def hex_to_rgba(value):
value = value.lstrip('#')
if len(value) == 3:
value = ''.join([v*2 for v in list(value)])
(r1,g1,b1,a1)=tuple(int(value[i:i+2], 16) for i in range(0, 6, 2))+(1,)
(r1,g1,b1,a1)=(r1/255.00000,g1/255.00000,b1/255.00000,a1)
return (r1,g1,b1,a1)
It works..
The function requires an instance of a GdkRGBA struct:
gboolean
gdk_rgba_parse (GdkRGBA *rgba,
const gchar *spec);
This translates to Python as a method on a Gdk.RGBA instance which mutates the structs contents:
color = Gdk.RGBA()
color.parse('#7F7F7F')
color.to_string() # 'rgb(127,127,127)'
It's not a very nice API for Python but every once in a while you have to deal with these kind of things with introspection based bindings. lazka's docs should be more helpful than the C ones:
http://lazka.github.io/pgi-docs/#Gdk-3.0/structs/RGBA.html#Gdk.RGBA.parse

Does MATLAB support "callable" (i.e. function-like) classes?

Is it possible to define a MATLAB class such that the objects from this class can be called like any other function?
IOW, I'm asking whether one can write in MATLAB the equivalent of something like the following Python class:
# define the class FxnClass
class FxnClass(object):
def __init__(self, template):
self.template = template
def __call__(self, x, y, z):
print self.template % locals()
# create an instance of FxnClass
f = FxnClass('x is %(x)r; y is %(y)r; z is %(z)r')
# call the instance of FxnClass
f(3, 'two', False)
...
[OUTPUT]
x is 3; y is 'two'; z is False
Thanks!
I do not know, whether MATLAB directly supports what you want, but MATLAB does support first-class functions; closures might therefore provide a useable substitute, for instance:
function f = count_call(msg)
calls = 0;
function current_count()
disp(strcat(msg, num2str(calls)));
calls = calls + 1;
end
f = #current_count;
end
In this case, current_count closes over calls (and msg). That way you can express functions that depend on some internal state. You would use it this way:
g = count_call('number of calls: ') % returns a new function ("__init__")
g() % "__call__"
I will be interested to see if this is possible without simply creating a java method in Matlab. I know you can do the following
classdef ExampleObject
properties
test;
end
methods
function exampleObject = ExampleObject(inputTest)
exampleObject.test=inputTest;
end
function f(exampleObject,funcInput)
disp(funcInput+exampleObject.test);
end
end
end
>> e=ExampleObject(5);
>> f(e,10)
15
But as far as my knowledge goes, if you tried to override the call function you'd run into a conflict with Matlab's parenthetical subscript reference subsref. You can find a reference here showing how to overwrite that, and you might be able to get it to do what you want...but it doesn't seem like good form to do so. Not sure how Matlab would handle a call to an object (as opposed to a function) without it getting confused with this.
One way is to override the feval function for your class:
classdef FxnClass < handle
properties
template
end
methods
function obj = FxnClass(t)
obj.template = t;
end
function out = feval(obj, varargin)
out = sprintf(obj.template, varargin{:});
end
end
end
This would be used as:
>> f = FxnClass('x = %f, y = %s, z = %d');
>> feval(f, 3,'two',false)
ans =
x = 3.000000, y = two, z = 0
Now if you want to provide additional syntactic sugar, you could redefine the subsref function for your class as #Salain suggested. Add the following to the previous class definition:
classdef FxnClass < handle
...
methods
function out = subsref(obj, S)
switch S(1).type
case '.'
% call builtin subsref, so we dont break the dot notation
out = builtin('subsref', obj, S);
case '()'
out = feval(obj, S.subs{:});
case '{}'
error('Not a supported subscripted reference');
end
end
end
end
Now you could simply write:
>> f = FxnClass('x = %f, y = %s, z = %d');
>> f(3,'two',false)
ans =
x = 3.000000, y = two, z = 0
Personally I don't particularly like overriding the subsref or subsasgn functions. They are used for too many cases, and its sometimes hard to get them write. For example all the following will eventually call the subsref method with different input:
f(..)
f.template
f.template(..)
f(..).template
f(..).template(..)
There is also the case of the end keyword which could appear in indexing, so you might have to also override it as well in some cases. Not to mention that objects can also be concatenated into arrays, which makes things even more complicated:
>> ff = [f,f];
>> ff(1) % not what you expect!
That said, I think #Frank's suggestion to use nested functions with closures is more elegant in this case:
function f = FxnClass(t)
f = #call;
function out = call(varargin)
out = sprintf(t, varargin{:});
end
end
which is called as before:
>> f = FxnClass('x = %f, y = %s, z = %d');
>> f(3, 'two', false)
If you mean that you want a class to hold a method which you use like a normal function (eg. defined in an m-file), then yes, Matlab does support static methods.
A static method runs independently of any instances of that class, in fact, you don't even need to instantiate a class to use its static methods.
Matlab does not support static fields, however, so you would have to instantiate such a class first, and then set its fields before using the functions (which presumably make use of these fields, since you are asking this question).
Given the limitation with static members, you might be better off with closures, as described by Frank.