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:[email protected](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_, python_interpreter_update, rel_op, unary_op

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

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

Raises:

  • (NoMethodError)

135
136
137
138
139
140
141
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
# File 'lib/rubypython/rubypyproxy.rb', line 135

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.new(args.pop))
  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.new args.pop
      end
      pReturn = _method_call(pFunc, args, pKeywords)
      pFunc.xDecref
    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.


216
217
218
219
220
# File 'lib/rubypython/rubypyproxy.rb', line 216

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.


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

alias :is_real_method? :respond_to?

#methodsObject

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


283
284
285
# File 'lib/rubypython/rubypyproxy.rb', line 283

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)

114
115
116
117
118
119
# File 'lib/rubypython/rubypyproxy.rb', line 114

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.


206
207
208
209
210
211
212
# File 'lib/rubypython/rubypyproxy.rb', line 206

def rubify
  converted = @pObject.rubify
  if converted.kind_of? ::FFI::Pointer
    converted = self.class.new converted
  end
  converted
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__

270
271
272
273
274
275
276
277
278
279
# File 'lib/rubypython/rubypyproxy.rb', line 270

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.


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

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.


224
225
226
227
228
# File 'lib/rubypython/rubypyproxy.rb', line 224

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