Jump to content
iqrf

Wrap TDictionary - TypeError: 'Object' object is not subscriptable

Recommended Posts

HI,

I modified the example of Demo 32. Added TColumn and fColumn: TDictionary<String,TColumn>;  property ColumnA: TDictionary<String,TColumn> read fColumn write fColumn;

  {$METHODINFO ON}
  TColumn = class(TPersistent)
  private
    FVisible: Boolean;
    FWidth: Integer;
  public
    property Visible: Boolean read FVisible write FVisible;
    property Width: Integer read FWidth write FWidth;
  end;

  TPoint = class(TPersistent)
    private
      fx, fy : Integer;
      fName : String;
      fColumn: TDictionary<String,TColumn>;
    public
      procedure OffsetBy( dx, dy : integer );
    published
      property x : integer read fx write fx;
      property y : integer read fy write fy;
      property Name : string read fName write fName;
      property ColumnA: TDictionary<String,TColumn> read fColumn write fColumn;
  end;
  {$METHODINFO OFF}
procedure TForm1.Button1Click(Sender: TObject);
var
  DelphiPoint : TPoint;
  p : PPyObject;
begin
  // Here's how you can create/read Python vars from Delphi with
  // Delphi/Python objects.
  DelphiPoint := TPoint.Create;
  DelphiPoint.x := 10;
  DelphiPoint.y := 20;

  DelphiPoint.ColumnA := TDictionary<String,TColumn>.Create;
  var value := TColumn.Create;
  value.Visible := True;
  value.Width := 455;
  DelphiPoint.ColumnA.Add('Note', value);
    
  // DelphiPoint will be owned and eventually destroyed by Python
  p := PyDelphiWrapper.Wrap(DelphiPoint, soOwned);

  PythonModule1.SetVar( 'myPoint', p );

  // Note, that you must not free the delphi point yourself.
  // Instead use the GetPythonEngine.Py_XDECREF(obj) method,
  // because the object may be used by another Python object.
  PythonEngine1.Py_DecRef(p);

  // Excecute the script
  PythonEngine1.ExecStrings( memo1.Lines );

end;

if i coll

print(spam.myPoint.ColumnA.Count)
print(spam.myPoint.ColumnA.ContainsKey("Note"))

OK 
1
True

print(spam.myPoint.ColumnA["Note"])  #TypeError: 'Object' object is not subscriptable

for key in spam.myPoint.ColumnA:	#TypeError: 'Object' object is not iterable
    print(key)

How to solve this please.
How to make this work

a = spam.myPoint.ColumnA.Column()
a.Visible = True
a.Width = 111
spam.myPoint.ColumnA.Add("Note1", a)

Thanks

Share this post


Link to post

You need to register the TDictionary<String,TColumn> in Form.Create,

 

PyDelphiWrapper.RegisterDelphiWrapper(TPyClassWrapper<TDictionary<String,TColumn>>).Initialize;

 

Have not tested,  Does it work?

Share this post


Link to post

It doesn't work, the class TPyClassWrapper  is not defined anywhere

Share this post


Link to post

Please use the latest version from https://github.com/pyscripter/python4delphi/.  Not the Getit version.

 

Here  

PyDelphiWrapper.RegisterDelphiWrapper(TPyClassWrapper<TDictionary<string, TColumn>>).Initialize;

 

works.

 

You could also subclass TDictionary<string, TColumn>.

 

type
  TColumnDict = class( TDictionary<string, TColumn>)
  end;

and in Form.Create

PyDelphiWrapper.RegisterDelphiWrapper(TPyClassWrapper<TColumnDict>).Initialize;

 

 

  • Thanks 1

Share this post


Link to post

Thank you very, very much.  I struggled with it for two days.
Please, how to make the TColumn class visible so it works

col = spam.myPoint.TColumn()
col.Visible = True
col.Width = 255
spam.myPoint.ColumnA.["Note1"] = col

or 
spam.myPoint.ColumnA.["Note1"] = spam.myPoint.TColumn(True, 255)

or do I have to make TPyColumn = class(TPyDelphiPersistent)? 
Thanks

Share this post


Link to post

Assuming your register the dictionary as shown above the following works.

from spam import myPoint

print(myPoint.ColumnA['Note'].Width)
print(myPoint.ColumnA['Note'].Visible)

 

Edited by pyscripter

Share this post


Link to post

Of course it works. I don't know how to put elements of type TColumn into a dictionary ColumnA in Python. 
This doesn't work

col = spam.myPoint.Column()
col.Visible = True
col.Width = 255
spam.myPoint.ColumnA.["Note1"] = col

or 
spam.myPoint.ColumnA.["Note1"] = spam.myPoint.Column(True, 255)

 

Share this post


Link to post

To be able to create columns you need to also to customize the TColumn wrapper:

 

  TColumnWrapper = class(TPyClassWrapper<TColumn>)
    constructor CreateWith(APythonType: TPythonType; args, kwds: PPyObject); override;
  end;
constructor TColumnWrapper.CreateWith(APythonType: TPythonType; args,
  kwds: PPyObject);
var
  W: integer;
  P: PPyObject;
begin
  Create(APythonType);
  GetPythonEngine.PyArg_ParseTuple(args, 'iO:CreateColumn', @W, @P);
  DelphiObject := TColumn.Create;
  DelphiObject.Width := W;
  DelphiObject.Visible := GetPythonEngine.PyObject_IsTrue(P) = 1;
end;

then register it in Form.Create

 

  PyDelphiWrapper.RegisterDelphiWrapper(TColumnWrapper).Initialize;
  PyDelphiWrapper.RegisterDelphiWrapper(TPyClassWrapper<TDictionary<string, TColumn>>).Initialize;  

Then the following code works:

 

from spam import myPoint, Column

print(myPoint.ColumnA['Note'].Width)
print(myPoint.ColumnA['Note'].Visible)

new_column = Column(100, False)
myPoint.ColumnA.Add('Note1', new_column)

print(myPoint.ColumnA['Note1'].Width)
print(myPoint.ColumnA['Note1'].Visible)


 

Edited by pyscripter
  • Like 1

Share this post


Link to post

Many thanks once again.

Question about TPyPoint. From python it is always called CreateWith, you cannot call Create. Why is that? I solved it like this.
To write p = spam.Point() , I changed ii: to |ii
PyArg_ParseTupleAndKeywords(args, kwds, '|ii:CreatePoint',
                 @KeyPointerArray[0], @fx, @fy)

Share this post


Link to post
1 hour ago, iqrf said:

Many thanks once again.

Question about TPyPoint. From python it is always called CreateWith, you cannot call Create. Why is that? I solved it like this.
To write p = spam.Point() , I changed ii: to |ii
PyArg_ParseTupleAndKeywords(args, kwds, '|ii:CreatePoint',
                 @KeyPointerArray[0], @fx, @fy)

You could allow for both Point() and Point(x, y) by modifying CreateWith

if PyArg_ParseTupleAndKeywords(args, kwds, '|ICreatePoint') then
  // no arguements

else if PyArg_ParseTupleAndKeywords(args, kwds, 'ii|:CreatePoint', @fx, @fy) then
// two integer arguements
begin

...

end;

 

Edited by pyscripter

Share this post


Link to post

Is there any way to iterate over TDictionary in Python?

for key in spam.myPoint.ColumnA.Keys:  #TypeError: 'Object' object is not iterable
    print(key)

for key, value in spam.myPoint.ColumnA.Items:    #TypeError: 'IndexedProperty' object is not iterable
    print(key, value)

 

Edited by iqrf

Share this post


Link to post
9 hours ago, iqrf said:

for key in spam.myPoint.ColumnA.Keys: #TypeError: 'Object' object is not iterable print(key😞

Use

for key in spam.myPoint.ColumnA.Keys.ToArray():
    print(key)

 

It is possible to allow for a more natural iteration, but it would require to hand craft the TDictionary wrapper.    See for instance how it is done in TPyDelphiStrings in WrapDelphiClasses.

Edited by pyscripter

Share this post


Link to post
On 2/28/2024 at 4:27 PM, pyscripter said:

To be able to create columns you need to also to customize the TColumn wrapper:

 


  TColumnWrapper = class(TPyClassWrapper<TColumn>)
    constructor CreateWith(APythonType: TPythonType; args, kwds: PPyObject); override;
  end;
constructor TColumnWrapper.CreateWith(APythonType: TPythonType; args,
  kwds: PPyObject);
var
  W: integer;
  P: PPyObject;
begin
  Create(APythonType);
  GetPythonEngine.PyArg_ParseTuple(args, 'iO:CreateColumn', @W, @P);
  DelphiObject := TColumn.Create;
  DelphiObject.Width := W;
  DelphiObject.Visible := GetPythonEngine.PyObject_IsTrue(P) = 1;
end;

then register it in Form.Create

 


  PyDelphiWrapper.RegisterDelphiWrapper(TColumnWrapper).Initialize;
  PyDelphiWrapper.RegisterDelphiWrapper(TPyClassWrapper<TDictionary<string, TColumn>>).Initialize;  

Then the following code works:

 


from spam import myPoint, Column

print(myPoint.ColumnA['Note'].Width)
print(myPoint.ColumnA['Note'].Visible)

new_column = Column(100, False)
myPoint.ColumnA.Add('Note1', new_column)

print(myPoint.ColumnA['Note1'].Width)
print(myPoint.ColumnA['Note1'].Visible)


 

It is possible to somehow distinguish whether the SetVisible function was called from

new_column.visible = True

or

myPoint.ColumnA['Note1'].Visible = True
procedure TColumn.SetVisible(const Value: Boolean);
begin
  FVisible := Value;
  ...
end;

Thanks for the advice.

Share this post


Link to post
On 3/6/2024 at 6:08 PM, iqrf said:

It is possible to somehow distinguish whether the SetVisible function was called from

TColumn.SetVisible is called, so inside that method Self is the TColumn object on which the method is called.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×