[py2rb] 辞書クラスの拡張2

# はじめに
移植やってます。
( from python 3.7 to ruby 2.7 )
# 辞書クラスの拡張 (Python)
```python
from collections import defaultdict

class BasicComposition(defaultdict):
    def __init__(self, *args, **kwargs):
        defaultdict.__init__(self, int)
        for k, v in list(self.items()):
            if not v:
                del self[k]

    def __missing__(self, key):
        return 'nil'

    def __setitem__(self, key, value):
        if isinstance(value, float):
            value = int(round(value))
        elif not isinstance(value, int):
            print('Error')
        if value:
            super(BasicComposition, self).__setitem__(key, value)
        elif key in self:
            del self[key]

d = BasicComposition()
d.update({'A': 1, 'B': 2.1, 'C': 0})
d['D'] = 0
d['E'] = 1.2
for k, v in d.items():
    print(k, v)

print(d['F'])

# output
A 1
B 2.1
C 0  
E 1  
nil
```
https://qiita.com/superrino130/items/e7406c612b55afc450c2#comment-7a780507ebf92d0e6b3c

コメント欄にてご指摘いただいた通り、非整数を代入することが可能です。
運用で逃げようとも思いましたが、別件の多重継承をどうするかという話もありますので、考えてみました。
# 継承案 (Ruby)
```ruby
class BasicComposition < Hash
  def initialize(*args, **kwargs)
    self.default = 0

    if args
      args.each do |x|
        self[x] += 1
      end
    end
    if kwargs
      kwargs.each do |k, v|
        self[k] = v
      end
    end
  end

  def []=(key, value)
    if value.instance_of?(Float)
      value = value.round
    elsif value.instance_of?(Integer).!
      raise TypeError, 'IntegerもしくはFloat以外が指定されました。'
    end
    if value != 0 # reject 0's
      self.merge!({key => value})
    elsif self.include?(key)
      self.delete(key)
    end
  end
end

d = BasicComposition.new
d.merge!({'A' => 1, 'B' => 2.1, 'C' => 0})
d['D'] = 0
d['E'] = 1.2
d.each do |k, v|
  p [k, v]
end

puts d['F']

# output
["A", 1]
["B", 2.1]
["C", 0]  
["E", 1]  
0
```
これは、単に移植しただけですので、非整数の代入が可能です。
# 委譲案1 (Ruby)
```ruby
class BasicComposition
  def initialize(*args, **kwargs)
    @h = Hash.new(0)

    if args
      args.each do |x|
        @h[x] += 1
      end
    end
    if kwargs
      kwargs.each do |k, v|
        @h[k] = v
      end
    end
  end

  def []=(key, value)
    if value.instance_of?(Float)
      value = value.round
    elsif value.instance_of?(Integer).!
      raise TypeError, 'IntegerもしくはFloat以外が指定されました。'
    end
    if value != 0 # reject 0's
      @h.merge!({key => value})
    elsif self.include?(key)
      @h.delete(key)
    end
  end

  def merge!(**kwargs)
    if kwargs
      kwargs.each do |k, v|
        self[k] = v
      end
    end
  end

  def include?(key)
    @h.include?(key)
  end

  def [](key)
    @h[key]
  end

  def h
    @h
  end
end

d = BasicComposition.new
d.merge!('A' => 1, 'B' => 2.1, 'C' => 0)
d['D'] = 0
d['E'] = 1.2
d.h.each do |k, v|
  p [k, v]
end

puts d['F']

# output
["A", 1]
["B", 2]
["E", 1]
0  
```
https://www.shoeisha.co.jp/book/detail/9784798168845

独習Ruby 450p に委譲の説明があります。

```ruby
# class BasicComposition < Hash

    @h = Hash.new(0)
```
`Hash`を`継承`するのではなく、内部に`保持(has)`します。
`d.h.each`のところが格好悪いのですが、`'B'`の値が整数となり、`'C' => 0`が削除されました。
# 委譲案2 (Ruby)
```ruby
  def merge!(**kwargs)
    # if kwargs
    #   kwargs.each do |k, v|
    #     self[k] = v
    #   end
    # end
  end

# output
["E", 1]
0
```
委譲案1のコードで、`def merge!`を空のメソッドにします。
結果として、`merge!`メソッドからの値変更をスルーすることにより、非整数の代入を防ぎます。
# forwardable (Ruby)
```ruby
require 'forwardable'

class BasicComposition
  extend Forwardable

  def initialize(*args, **kwargs)
    @h = Hash.new(0)

    if args
      args.each do |x|
        @h[x] += 1
      end
    end
    if kwargs
      kwargs.each do |k, v|
        @h[k] = v
      end
    end
  end

  def_delegators :@h, :[], :include?, :each

  def []=(key, value)
    if value.instance_of?(Float)
      value = value.round
    elsif value.instance_of?(Integer).!
      raise TypeError, 'IntegerもしくはFloat以外が指定されました。'
    end
    if value != 0 # reject 0's
      @h.merge!({key => value})
    elsif self.include?(key)
      @h.delete(key)
    end
  end

  def merge!(**kwargs)
    if kwargs
      kwargs.each do |k, v|
        self[k] = v
      end
    end
  end
end

d = BasicComposition.new
d.merge!('A' => 1, 'B' => 2.1, 'C' => 0)
d['D'] = 0
d['E'] = 1.2
d.each do |k, v|
  p [k, v]
end

puts d['F']

# output
["A", 1]
["B", 2]
["E", 1]
0 
```
https://docs.ruby-lang.org/ja/latest/library/forwardable.html

`require 'forwardable'`つよいですね。
`d.each`になってスッキリしています。

@Nabetani さん、ご指摘ありがとうございました。
# メモ
+ Python の 辞書クラスの拡張2 を学習した
+ 百里を行く者は九十里を半ばとす