简体   繁体   中英

Lua: Class method skips first argument

I am trying to implement a Class in Lua which is supposed to be instantiated. However, I am getting an error where the input-parameters to the function BatchLoader.init are shifted. Am I missing to implement a self keyword as the argument input to any member function like in python, or is there anything else wrong with the class definitions?

The entire classcode looks like this (I stripped out the unimportant things).:

function BatchLoader:new()
    setmetatable({}, BatchLoader)
    self.epoch_done = false
    return self
end

function BatchLoader:init(X, y, sids, batch_size, argshuffle)    
    print("In batchloader")
    print(X:size())    -- Prints size(62000, 1) [which is 'y']
    print(y:size())    -- Prints size(62000, 1) [which is 'sids']
    print(sids)        -- Prints '10'           [which is 'batch_size']
end

But when I create this class, and then call the function BatchLoader.init it seems that y is interpreted as X, y is interpreted as sids and so on. As such, the input to batch_size is interpreted as sids with which every argument is 'shifted away' from the actual parameter.

The following code is what I call right before going into that BatchLoader.init . Everything here is printed as expected.

local BatchLoader = require "../datahandler/BatchLoader.lua"
local trainLoader = BatchLoader:new()
print(X_data[{{1, 62000}, {}, {}}]:size()) -- Prints size(62000, 8, 16)
print(y_data[{{1, 62000}, {}}]:size())     -- Prints size(62000, 1)
print(sid_data[{{1, 62000}, {}}]:size())   -- Prints size(62000, 1)

local X_train, y_train, sid_train = trainLoader.init(
    X_data[{{1, a}, {}, {}}],
    y_data[{{1, a}, {}}],
    sid_data[{{1, a}, {}}],
    10, true)

My question is: Is there anything wrong with the Class-Declaration there? This is my first OOP code in Lua, so any ideas or help is appreciated! :)

In Lua, this syntax:

o:f(x, y)

is syntactic sugar for this:

o.f(self, x, y)

This is true for both definition and invocation. Here you define init with the colon syntax and call it with the dot syntax, so it won't work because the first argument will become self and the other arguments will be off by one. A solution is to call init like this:

local X_train, y_train, sid_train = trainLoader:init(
    X_data[{{1, a}, {}, {}}],
    y_data[{{1, a}, {}}],
    sid_data[{{1, a}, {}}],
    10, true)

This solves this issue, but note that in your example the constructor is also completely broken . Because you define it with the colon syntax, it will always return the BatchLoader table itself and not a new instance. Since it is a class method, you should write it and call it with the dot syntax:

function BatchLoader.new()
    local self = setmetatable({}, BatchLoader)
    self.epoch_done = false
    return self
end

or simply:

function BatchLoader.new()
    return setmetatable({epoch_done = false}, BatchLoader)
end

Then you also call it with the dot syntax:

local trainLoader = BatchLoader.new()

To understand those things, if you haven't done it yet, I strongly recommend you buy a copy of the latest edition of Programming in Lua and read the section on Object Oriented programming.

Method syntax (using foo:bar instead of foo.bar ) automatically introduces a self parameter.

On function definition, function Foo:bar( ... ) is equivalent to function Foo.bar( self, ... ) . On function call, foo:bar( ... ) is roughly equivalent to foo.bar( foo, ... ) (the latter evaluates foo twice).

So let's dissect your code:

function BatchLoader:new()
    setmetatable({}, BatchLoader) -- creates a table & throws it away
    self.epoch_done = false       -- self is the hidden first argument
    return self
end

local trainLoader = BatchLoader:new()  -- i.e. BatchLoader.new( BatchLoader )

which means you're effectively running

function BatchLoader:new()
    setmetatable({}, BatchLoader)
    BatchLoader.epoch_done = false
    return BatchLoader
end

which certainly is not what you want. Several ways to do what you might want are

-- fixed creation scheme
function BatchLoader.new( )
   local self = { epoch_done = false }
   return setmetatable( self, BatchLoader )
end

-- acceptable uses:
local trainLoader = BatchLoader.new( )
local trainLoader = BatchLoader:new( ) -- ignores passed self

or

-- permit passing base object
function BatchLoader:new( )  -- equivalently: BatchLoader.new( self )
   self = self or { }  -- use passed table or create new
   self.epoch_done = false
   return setmetatable( self, BatchLoader )
end

-- acceptable uses:
local trainLoader = BatchLoader.new( )  -- creates from scratch
local trainLoader = BatchLoader.new { n = 23 } -- re-uses this table
-- WRONG uses:
local trainLoader = BatchLoader:new( )  -- reuses BatchLoader class as object

or many other options.

You've equally mixed up the method notation for the other call:

function BatchLoader:init(X, y, sids, batch_size, argshuffle)

is fine and equivalent to

function BatchLoader.init(self, X, y, sids, batch_size, argshuffle)

which you are then wrongly calling as

local X_train, y_train, sid_train = 
  trainLoader.init( X, y, ... )

(see why everything is shifted?)

Use trainLoader:init( X, y, ... ) to pass trainLoader as self .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM