Class: RubyPython::RubyPyProxy

Inherits:
BlankObject
  • Object
show all
Includes:
Operators
Defined in:
lib/rubypython/rubypyproxy.rb

Overview

In most cases, users will interact with RubyPyProxy objects that hold references to active objects in the Python interpreter. RubyPyProxy delegates method calls to Python objects, wrapping and returning the results as RubyPyProxy objects.

The allocation, deallocation, and reference counting on RubyPyProxy objects is automatic: RubyPython takes care of it all. When the object is garbage collected, the instance will automatically decrement its object reference count.

NOTE:

All RubyPyProxy objects become invalid when the Python interpreter is halted.

Calling Methods With Blocks

Any method which is forwarded to a Python object may be called with a block. The result of the method will passed as the argument to that block.

RubyPython.run do
  sys = RubyPython.import 'sys'
  sys.version { |v| v.rubify.split(' ') }
end
# => [ "2.6.1", … ]

Passing Procs and Methods to Python Methods

RubyPython supports passing Proc and Method objects to Python methods. The Proc or Method object must be passed explicitly. As seen above, supplying a block to a method will result in the return value of the method call being passed to the block.

When a Proc or Method is supplied as a callback, then arguments that it will be called with will be wrapped Python objects. It will therefore typically be necessary to write a wrapper around any Ruby callback that requires native Ruby objects.

# Python Code: sample.py
def apply_callback(callback, argument):
  return callback(argument)

# IRB Session
>> RubyPython.start
=> true
>> sys = RubyPython.import 'sys'
=> <module 'sys' (built-in)>
>> sys.path.append('.')
=> None
>> sample = RubyPython.import 'sample'
=> <module 'sample' from './sample.pyc'>
>> callback = Proc.new { |arg| arg * 2 }
=> # <Proc:0x000001018df490@(irb):5>
>> sample.apply_callback(callback, 21).rubify
=> 42
>> RubyPython.stop
=> true

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Operators

#<=>, #[], #[]=, bin_op, #include?, operator_, rel_op, unary_op, update

Methods inherited from BlankObject

hide

Constructor Details

#initialize(pObject) ⇒ RubyPyProxy

Creates a Python proxy for the provided Ruby object.

Only the following Ruby types can be represented in Python:

  • String

  • Array

  • Hash

  • Fixnum

  • Bignum

  • Float

  • Symbol (as a String)

  • Proc

  • Method

  • true (as True)

  • false (as False)

  • nil (as None)



82
83
84
85
86
87
88
# File 'lib/rubypython/rubypyproxy.rb', line 82

def initialize(pObject)
  if pObject.kind_of? PyObject
    @pObject = pObject
  else
    @pObject = PyObject.new pObject
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ Object

Delegates method calls to proxied Python objects.

Delegation Rules

  1. If the method ends with a question-mark (e.g., nil?), it can only be a Ruby method on RubyPyProxy. Attempt to reveal it (RubyPyProxy is a BlankObject) and call it.

  2. If the method ends with equals signs (e.g., value=) it’s a setter and we can always set an attribute on a Python object.

  3. If the method ends with an exclamation point (e.g., foo!) we are attempting to call a method with keyword arguments.

  4. The Python method or value will be called, if it’s callable.

  5. RubyPython will wrap the return value in a RubyPyProxy object (unless legacy_mode has been turned on).

  6. If a block has been provided, the wrapped return value will be passed into the block.

Raises:

  • (NoMethodError)


142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/rubypython/rubypyproxy.rb', line 142

def method_missing(name, *args, &block)
  name = name.to_s

  if name =~ /\?$/
    begin
      RubyPyProxy.reveal(name.to_sym)
      return self.__send__(name.to_sym, *args, &block)
    rescue RuntimeError => exc
      raise NoMethodError.new(name) if exc.message =~ /Don't know how to reveal/
      raise
    end
  end

  kwargs = false

  if name =~ /=$/
    return @pObject.setAttr(name.chomp('='),
                            PyObject.convert(*args).first)
  elsif name =~ /!$/
    kwargs = true
    name.chomp! "!"
  end

  raise NoMethodError.new(name) if !@pObject.hasAttr(name)

  pFunc = @pObject.getAttr(name)

  if pFunc.callable?
    if args.empty? and pFunc.class?
      pReturn = pFunc
    else
      if kwargs and args.last.is_a?(Hash)
        pKeywords = PyObject.convert(args.pop).first
      end

      orig_args = args
      args = PyObject.convert(*args)
      pTuple = PyObject.buildArgTuple(*args)
      pReturn = if pKeywords
        pFunc.callObjectKeywords(pTuple, pKeywords)
      else
        pFunc.callObject(pTuple)
      end

      # Clean up unused Python vars instead of waiting on Ruby's GC to
      # do it.
      pFunc.xDecref
      pTuple.xDecref
      pKeywords.xDecref if pKeywords
      orig_args.each_with_index do |arg, i|
        # Only decref objects that were created in PyObject.convert.
        if !arg.kind_of?(RubyPython::PyObject) and !arg.kind_of?(RubyPython::RubyPyProxy)
          args[i].xDecref
        end
      end

      raise PythonError.handle_error if PythonError.error?
    end
  else
    pReturn = pFunc
  end

  result = _wrap(pReturn)

  if block
    block.call(result)
  else
    result
  end
end

Instance Attribute Details

#pObjectObject (readonly)

Returns the value of attribute pObject.



65
66
67
# File 'lib/rubypython/rubypyproxy.rb', line 65

def pObject
  @pObject
end

Instance Method Details

#inspectObject

Returns the String representation of the wrapped object via a call to the object’s __repr__ method, or the repr method in PyMain.



221
222
223
224
225
# File 'lib/rubypython/rubypyproxy.rb', line 221

def inspect
  self.__repr__.rubify
rescue PythonError, NoMethodError
  RubyPython::PyMain.repr(self).rubify
end

#is_real_method?Object

The standard Ruby #respond_to? method has been renamed to allow RubyPython to query if the proxied Python object supports the method desired. Setter methods (e.g., foo=) are always supported.



114
# File 'lib/rubypython/rubypyproxy.rb', line 114

alias :is_real_method? :respond_to?

#methodsObject

Returns the methods on the Python object by calling the dir built-in.



288
289
290
# File 'lib/rubypython/rubypyproxy.rb', line 288

def methods
  pObject.dir.map { |x| x.to_sym }
end

#respond_to?(mname) ⇒ Boolean

RubyPython checks the attribute dictionary of the wrapped object to check whether it will respond to a method call. This should not return false positives but it may return false negatives. The built-in Ruby respond_to? method has been aliased to is_real_method?.

Returns:

  • (Boolean)


120
121
122
123
124
125
# File 'lib/rubypython/rubypyproxy.rb', line 120

def respond_to?(mname)
  return true if is_real_method?(mname)
  mname = mname.to_s
  return true if mname =~ /=$/
  @pObject.hasAttr(mname)
end

#rubifyObject

RubyPython will attempt to translate the wrapped object into a native Ruby object. This will only succeed for simple built-in type.



215
216
217
# File 'lib/rubypython/rubypyproxy.rb', line 215

def rubify
  @pObject.rubify
end

#to_aObject

Converts the wrapped Python object to a Ruby Array. Note that this only converts one level, so a nested array will remain a proxy object. Only wrapped objects which have an __iter__ method may be converted using to_a.

Note that for Python Dict objects, this method returns what you would get in Python, not in Ruby: a_dict.to_a returns an array of the dictionary’s keys.

List #to_a Returns an Array

>> RubyPython.start
=> true
>> list = RubyPython::RubyPyProxy.new([1, 'a', 2, 'b'])
=> [1, 'a', 2, 'b']
>> list.kind_of? RubyPython::RubyPyProxy
=> true
>> list.to_a
=> [1, 'a', 2, 'b']
>> RubyPython.stop
=> true

Dict #to_a Returns An Array of Keys

>> RubyPython.start
=> true
>> dict = RubyPython::RubyPyProxy.new({1 => '2', :three => [4,5]})
=> {1: '2', 'three': [4, 5]}
>> dict.kind_of? RubyPython::RubyPyProxy
=> true
>> dict.to_a
=> [1, 'three']
>> RubyPython.stop
=> true

Non-Array Values Do Not Convert

>> RubyPython.start
=> true
>> item = RubyPython::RubyPyProxy.new(42)
=> 42
>> item.to_a
NoMethodError: __iter__


275
276
277
278
279
280
281
282
283
284
# File 'lib/rubypython/rubypyproxy.rb', line 275

def to_a
  iter = self.__iter__
  ary = []
  loop do
    ary << iter.next()
  end
rescue PythonError => exc
  raise if exc.message !~ /StopIteration/
  ary
end

#to_enumObject

Creates a PyEnumerable for this object. The object must have the __iter__ method.



294
295
296
# File 'lib/rubypython/rubypyproxy.rb', line 294

def to_enum
  PyEnumerable.new(@pObject)
end

#to_sObject

Returns the string representation of the wrapped object via a call to the object’s __str__ method or the str method in PyMain.



229
230
231
232
233
# File 'lib/rubypython/rubypyproxy.rb', line 229

def to_s
  self.__str__.rubify
rescue PythonError, NoMethodError
  RubyPython::PyMain.str(self).rubify
end