Scala AWS Lambda function

By Brian Fitzgerald

Introduction

This is a step-by-step procedure on how to create Scala AWS Lambda implementation in Eclipse with Maven. The compiled classes will run in the cloud in AWS Lambda’s java runtime environment (JRE).

Eclipse

To begin with, install these packages into your Eclipse, or check that they are installed:

  • AWS Toolkit for Eclipse
  • Scala IDE for Eclipse

Begin with Hello from Lambda!

The instructions in this section are based on the AWS manual. Setup AWS Toolkit for Eclipse.

Setup the AWS Lambda project

Click AWS Toolkit for Eclipse,

aws.toolkit.for.eclipse

and select “New AWS Lambda Java project…” from the dropdown. In the dialog box, enter the project name, i.e. “FiboLam”, change “Input Type” from “S3 Event” to “Custom”, and press “Finish”.

new.aws.lam.proj

Test the existing java function

Right click the project and select “Amazon Web Services” -> “Upload Function to AWS Lambda…”. In the dialog box, select “Create a new Lambda function:”. Enter a name and press “Next”. If you have not done so previously, create your role and S3 bucket. Press “Finish”.

upload.func

Again, right click the project and select “Amazon Web Services” -> “Run function on AWS Lambda…”. Note that the Lambda Handler is in the form “package.class”, i.e. “com.amazonaws.lambda.demo.LambdaFunctionHandler”. Press “Invoke”. Check the console:

Skip uploading function code since no local change is found...
Invoking function...
==================== FUNCTION OUTPUT ====================
"Hello from Lambda!"
==================== FUNCTION LOG OUTPUT ====================
START RequestId: 45a20261-f9aa-4104-a0a3-b3a65cc37c07 Version: $LATEST
Input: {}END RequestId: 45a20261-f9aa-4104-a0a3-b3a65cc37c07
REPORT RequestId: 45a20261-f9aa-4104-a0a3-b3a65cc37c07	Duration: 0.74 ms	Billed Duration: 100 ms 	Memory Size: 512 MB	Max Memory Used: 82 MB	

Up to here, we have a working AWS Lambda Java project.

Delete the existing java code

Now wipe out the java code

Expand src/main/java. Delete file LambdaFunctionHandler.java. Delete package com.example.lambda.demo

Expand src/test/java. You may delete file LambdaFunctionHandlerTest.java. Fixing it is out of scope for now.

Expand Maven Dependencies. You will see no scala runtime libraries, so far.

Scala

Add scala runtime library

Right click on the project, select “Configure->Add Scala Nature”

Refresh Maven Dependencies and take note of the version number, i.e. 2.12.3

Right click on the project and select “Maven”->”Add Dependency”, In the dialog box enter:

Group Id: org.scala-lang

Artifact Id: scala-library

Version: the version. i.e 2.12.3

Press “OK”

add.maven.dependency

Expand Maven Dependencies. Note that the scala-library jar appears. A screenshot of the updated Maven tree appears later in this blog.

Create Scala Sources

Set scala perspective

Select Window->Perspective->Open Perspective->Other..” Scroll down and select “Scala”. Press “Open”

Create Scala classes

At “src/main/java”, you can create a new package, let’s say “com.yourcompany.fibo”. Then, create your scala source file. Select “New->Scala Class”, and enter the class name, i.e. “com.yourcompany.fibo.Fibo”.

Replace the code with:

import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler

class YourClass extends RequestHandler[Object, String] {

  def handleRequest(o: Object, cx: Context): String =
    yourCode

}

Method “handleRequest” is mandatory. In practice, input Object will be JSON serializable. The return must be of type String. For example, Fibo.scala:

package com.yourcompany.fibo

import com.amazonaws.services.lambda.runtime.Context
import com.amazonaws.services.lambda.runtime.RequestHandler
import com.yourcompany.fibo.FibTailRec.fib

class Fibo extends RequestHandler[Object, String] {

  def handleRequest(o: Object, cx: Context): String =
    fibTailRec(o.toString.toInt).toString

  def fibTailRec(n: Int): Int =
    fib(n, 0, 1)

}

press ctrl-S to save.

FibTailRec.scala

package com.yourcompany.fibo

import scala.annotation.tailrec

object FibTailRec {

  @tailrec def fib(i: Int, p: Int, f: Int): Int = i match {
    case 0 => {
      p
    }
    case _ => fib(i - 1, f, p + f)
  }
}

By the way, this function demonstrates Scala tail call optimization, and is examined in more detail here.

The project tree

Examine the Package Explorer. Expand src/main/java. Note that there is only your package, your sources, and no demo code. Expand Maven Dependencies. Note the scala library jar. Note that no folders or files are flagged with errors.

explorer

Upload the Lambda function

Right click the project and select “Amazon Web Services”->”Upload Function to AWS Lambda..”. Select the correct handler (there should be only one), i.e. “com.yourcompany.fibo.Fibo”. That way, AWS Lambda will automatically look to run method handleRequest. Choose existing Lambda Function: FiboLam

upload.scala

Run the Lambda function

Again, right click the project and select “Amazon Web Services” -> “Run function on AWS Lambda…”. Select your handler, i.e. “com.yourcompany.fibo.Fibo”. For the sake of this blog, we’re not going to delve into scala JSON parsing, so in the text pane, enter a nonnegative integer, i.e. 7. Press “Invoke”.

run.dialog

Output:

Skip uploading function code since no local change is found...
Invoking function...
==================== FUNCTION OUTPUT ====================
"13"
==================== FUNCTION LOG OUTPUT ====================
START RequestId: 0f0990fe-497d-4f78-9709-5c67085d7a78 Version: $LATEST
END RequestId: 0f0990fe-497d-4f78-9709-5c67085d7a78
REPORT RequestId: 0f0990fe-497d-4f78-9709-5c67085d7a78	Duration: 0.54 ms	Billed Duration: 100 ms 	Memory Size: 512 MB	Max Memory Used: 93 MB	

Interpretation: 13 is the 7th Fibonacci number.

The upload file

You can go to your S3 bucket and find your file there (FiboLam.zip). You can download and explore the zip file and find your class files and the library jar files.

Summary

I have implemented a Scala AWS Lambda Function using these tools:

  • Eclipse
    • AWS Toolkit
    • Scala IDE
  • AWS
    • Lambda Function
    • IAM role
    • S3 bucket
  • Maven

The finished Lambda is made from only Scala classes. Now you can replace the Fibonacci code with your own code, and replace the test event with events from other AWS components in your own project.

Serverless function learning environments across Amazon, Microsoft, and Google clouds

by Brian Fitzgerald

Introduction

If you want to dip your toe into serverless function programming, you will want to try it out in a simple web-based environment with all the needed syntax setup for you. That way, you can at least get to “Hello World!” without delay or error.

Across three cloud providers, Amazon, Microsoft, and Google, online edit availability varies across languages and operating systems. Here is a brief summary.

Amazon Web Services

AWS serverless functions, Lambda, are available in seven languages, C#, Go, Java, JavaScript, Powershell, Python, and Ruby. You can experiment with some simple coding by entering your choice of JavaScript, Python or Ruby code into the online code editor. If you want to use C#, Go, Java, or Powershell, you will have to develop and test your files outside Lambda, put them in a zip file, and upload the zip file. The Lambda console also accepts a jar file for upload. A Lambda java upload needs class and jar files, not java source files. Also, a jar file can contain bytecode compiled from other languages that run in a JRE, so, for example, you can write a Lambda in scala or clojure.

Saving code changes from the AWS Console is quick, usually under one second. Python code is saved without syntax checking. There is one quirk. Tabs in sources get copied to the clipboard as spaces. I refer to the Lambda Management console in Chrome on Windows.

You can export your function, and in that way, get your source files out after you have tested them.

A python Lambda function can return any data type that is JSON serializable, such as  dict, list, tuple, Boolean, scalars, None, and hierarchies of these, but not, for example, set, date, datetime, class, or object.

Azure

Azure Functions are offered in five languages: C#, Java, JavaScript, Powershell, and Python. Azure functions can be administered online in the Azure portal. Azure offers a choice of Windows or Linux for your function, but online edit is only available for the Windows Function Apps. Python runs on Linux only, which rules out online edit. Creating Java or Go functions is supported only by upload. Online edit, therefore, is available for C#, JavaScript, and Powershell.

An Azure function sits inside a FunctionApp. FunctionApp names must be unique across all Azure. You cannot name your Azure FunctionApp “spam” or “eggs”, and you cannot name your Azure FunctionApp “SpamAndEggs” unless I delete my Azure FunctionApp “SpamAndEggs”.

spamandeggs

FunctionApp creation can take more than 1 minute. When creation finishes, the function list displayed in the portal does not refresh when the function is ready, and you could miss the notification. Saving your code from the portal is almost instantaneous. Compile and run takes less than 1 second. You can zip and download your finished code by pressing Download app content.

Press tab in the online code results in saving space characters, which will be less of a problem, since you won’t be editing python source online.

Google

In Google Cloud Platform, you can create a Google Cloud Function. The language choices are Go, JavaScript, and Python, and you can enter all code using the online editor.

When you finish editing, you press “Deploy”, which can run for up to 1 minute.Syntax errors lead to failed deployment. While testing the code, you can view it read-only.  If you want to make a change, you have to go back to the edit screen. You may download your finished code as a zip file.

Google Cloud function return type is limited to string, tuple, Response instance, or WSGI callable.

Summary

Here is a summary of programming languages across cloud providers.

Language AWS Azure Google
C# upload only online edit not available
Go upload only not available online edit
Java upload only upload only not available
JavaScript online edit online edit online edit
Powershell upload only online edit not available
Python online edit upload only online edit
Ruby online edit not available not available

JavaScript is universally available for learning: You can quickly create a Hello World serverless function using an online editor on any cloud platform. On the other hand, if you are a hard-core java programmer, you are going to need to work out how to upload your code. You could upload code from your IDE, for example. If you want to learn C# or Powershell cloud programming, Azure is the place to be. If you want to explore Go, then go to Google.

 

Tail call optimization in Scala

By Brian Fitzgerald

Summary

We analyze a Scala function with a recursive tail call, and show that the compiler rewrites it as a nonrecursive loop. We use the @tailrec annotation to verify the optimization.Printing the stack at run time confirms the optimization. Bytecode analysis shows optimization implementation details, and how the java virtual machine manages the stack for the recursive and iterative cases. A bytecode decompiler reverse engineers the bytecode into possible source codes. While Scala supports tail call elimination, Java does not.

Recursion

Recursion is when a function calls itself, usually for the purpose of divide and conquer, meaning dividing a large problem into smaller pieces, and solving the smaller problems. Quicksort is a great example of divide and conquer, and is a problem that can be solved using a recursive algorithm.

Imperative languages make judicious use of recursion. We might use imperative languages without realizing that is what they are called. Imperative programming means executing a series of steps in order. For example:

restore database;
recover database;
alter database open resetlogs;

On the other hand, pure functional languages express actions as functions. Claimed advantages are that side-effect-free functions and immutable data lead to improved readability, maintainability, and concurrency.

In a pure functional language, iteration is implemented as recursion. Usually, recursion works by placing the caller’s return address and the calling arguments on the stack, and then jumping to the same function. In the call, the function accesses the values on the stack as local variables. In the return, the function result is loaded into the caller’s operand stack, and the current frame is discarded. Execution resumes in the caller. Ordinarily, stack resources would limit such an approach.

Enter the tail call. If the recursive call comes just before the return, then the recursion can be optimized as iteration. No stack frame is allocated. The return is implemented as a goto.

The objective of this blog post is to see how tail call optimization works.

Recursion Example

This example calculates the nth Fibonacci number, conventionally indexed from 0 as 0, 1, 1, 2, 3 etc. or from 1 as 1, 1, 2, 3, ,etc. I have divided the program into two files for later analysis.

object FibTailRec contains fib, the inner, recursive function. The arguments are i, p, and f

  • i: the counter, counting down from n
  • p: the previous Fibonacci number
  • f: the Fibonacci number
package fibo

object FibTailRec {

  def fib(i: Int, p: Int, f: Int): Int = i match {
    case 0 => p
    case _ => fib(i - 1, f, p + f)
  }
}

Here is the driver. It has two functions. main calls function fibTailRec with a value (“6”), and prints the result. fibTailResult calls fib with n and seed values for p and f.

import fibo.FibTailRec.fib

object RunFibTailRec {

  def main(args: Array[String]): Unit = {
    println(fibTailRec(6))
  }

  def fibTailRec(n: Int): Int =
    fib(n, 0, 1)

}

The output:

8

The @tailrec annotation

To make sure that the compiler optmized the tail call, we make two changes. This import

import scala.annotation.tailrec

and the @tailrec annotation.

package fibo
import scala.annotation.tailrec

object FibTailRec {

  @tailrec def fib(i: Int, p: Int, f: Int): Int = i match {
    case 0 => p
    case _ => fib(i - 1, f, p + f)
  }
}

The code compiles, so we know that the compiler optimized the tail call.

Displaying the stack

We can also display the stack at the innermost recursion depth by replacing

    case 0 => p

with

   case 0 => {
      new Exception().printStackTrace()
      p
    }

The modified function looks like this:

package fibo
import scala.annotation.tailrec

object FibTailRec {

  @tailrec def fib(i: Int, p: Int, f: Int): Int = i match {
    case 0 => {
      new Exception().printStackTrace()
      p
    }
    case _ => fib(i - 1, f, p + f)
  }
}

The output looks is below. Scala produces two class files for each of my source files. The code runs mainly in classes with “$” added to the original name. Notice that function fib appears only once, i.e., no stack frames were allocated as i counted down to 0.

java.lang.Exception
	at fibo.FibTailRec$.fib(FibTailRec.scala:8)
	at RunFibTailRec$.fibTailRec(RunFibTailRec.scala:10)
	at RunFibTailRec$.main(RunFibTailRec.scala:6)
	at RunFibTailRec.main(RunFibTailRec.scala)
8

Notice that we displayed the stack without the use of a debugger or external tracing tool, and without throwing an exception. Execution finished, and the value, 8, was displayed.

Code that cannot be tail-call optimized

To demonstrate code ineligible for tail call optimization we introduce a coding error. Replace

case _ => fib(i - 1, f, p + f)

with

case _ => {
      var r: Int = fib(i - 1, f, p + f)
      r
    }

The function now looks like this:

package fibo

object FibTailBad {

  def fib(i: Int, p: Int, f: Int): Int = i match {
    case 0 => p
    case _ => {
      var r: Int = fib(i - 1, f, p + f)
      r
    }
  }
}

The recursive call (fib) is no longer the last in the brace-delimited code block. If you try to use the @tailrec annotation:

package fibo
import scala.annotation.tailrec

object FibTailBad {

  @tailrec def fib(i: Int, p: Int, f: Int): Int = i match {
    case 0 => p
    case _ => {
      var r: Int = fib(i - 1, f, p + f)
      r
    }
  }
}

the compiler throws this error:

could not optimize @tailrec annotated method fib: it contains 
a recursive call not in tail position

No class file is produced. If you display the stack at the recursion termination condition, you see “fib” 7 times, 6 for the case when i > 0 and 1 for the case when i = 0.

java.lang.Exception
	at fibo.FibTailBad$.fib(FibTailBad.scala:8)
	at fibo.FibTailBad$.fib(FibTailBad.scala:12)
	at fibo.FibTailBad$.fib(FibTailBad.scala:12)
	at fibo.FibTailBad$.fib(FibTailBad.scala:12)
	at fibo.FibTailBad$.fib(FibTailBad.scala:12)
	at fibo.FibTailBad$.fib(FibTailBad.scala:12)
	at fibo.FibTailBad$.fib(FibTailBad.scala:12)
	at RunFibTailBad$.fibTailBad(RunFibTailBad.scala:10)
	at RunFibTailBad$.main(RunFibTailBad.scala:6)
	at RunFibTailBad.main(RunFibTailBad.scala)
8

Bytecode listing

It is interesting to compare and contrast the bytecode of the optimized code vs. the non-optimized code. The 9-line and 12-line Scala files (respectively) expand to 71-line and 78-line bytecode listings. If I had not isolated function fib, the listing would have been much longer. The optimized code:

public final class fibo/FibTailRec$ {
     <ClassVersion=52>
     <SourceFile=FibTailRec.scala>

     public static fibo.FibTailRec$ MODULE$;

     public static  { //  //()V
             new fibo/FibTailRec$
             invokespecial fibo/FibTailRec$.()V
             return
     }

     public fib(int arg0, int arg1, int arg2) { //(III)I
         <localVar:index=0 , name=this , desc=Lfibo/FibTailRec$;, sig=null, start=L1, end=L2>
         <localVar:index=1 , name=i , desc=I, sig=null, start=L1, end=L2>
         <localVar:index=2 , name=p , desc=I, sig=null, start=L1, end=L2>
         <localVar:index=3 , name=f , desc=I, sig=null, start=L1, end=L2>

         L1 {
             f_new (Locals[4]: fibo/FibTailRec$, 1, 1, 1) (Stack[0]: null)
             iload1 // reference to arg0
             istore5
             iload5
             tableswitch 
                val: 0 -> L3
                default -> L4
         }
         L3 {
             f_new (Locals[6]: fibo/FibTailRec$, 1, 1, 1, 0, 1) (Stack[0]: null)
             iload2 // reference to arg1
             goto L5
         }
         L4 {
             f_new (Locals[6]: fibo/FibTailRec$, 1, 1, 1, 0, 1) (Stack[0]: null)
             iload1 // reference to arg0
             iconst_1
             isub
             iload3
             iload2 // reference to arg1
             iload3
             iadd
             istore3
             istore2 // reference to arg1
             istore1 // reference to arg0
             goto L1
         }
         L5 {
             f_new (Locals[6]: fibo/FibTailRec$, 1, 1, 1, 0, 1) (Stack[1]: 1)
             ireturn
         }
         L2 {
         }
     }

     private FibTailRec$() { //  //()V
         <localVar:index=0 , name=this , desc=Lfibo/FibTailRec$;, sig=null, start=L1, end=L2>

         L1 {
             aload0 // reference to self
             invokespecial java/lang/Object.()V
             aload0 // reference to self
             putstatic fibo/FibTailRec$.MODULE$:fibo.FibTailRec$
         }
         L3 {
             return
         }
         L2 {
         }
     }

Scala: [B@2ddd5edcScalaInlineInfo: [B@1a12f6f1}

The unoptimized code:

public final class fibo/FibTailBad$ {
     <ClassVersion=52>
     <SourceFile=FibTailBad.scala>

     public static fibo.FibTailBad$ MODULE$;

     public static  { //  //()V
             new fibo/FibTailBad$
             invokespecial fibo/FibTailBad$.()V
             return
     }

     public fib(int arg0, int arg1, int arg2) { //(III)I
         <localVar:index=5 , name=r , desc=I, sig=null, start=L1, end=L2>
         <localVar:index=0 , name=this , desc=Lfibo/FibTailBad$;, sig=null, start=L3, end=L4>
         <localVar:index=1 , name=i , desc=I, sig=null, start=L3, end=L4>
         <localVar:index=2 , name=p , desc=I, sig=null, start=L3, end=L4>
         <localVar:index=3 , name=f , desc=I, sig=null, start=L3, end=L4>

         L3 {
             iload1 // reference to arg0
             istore4
             iload4
             tableswitch 
                val: 0 -> L5
                default -> L6
         }
         L5 {
             f_new (Locals[5]: fibo/FibTailBad$, 1, 1, 1, 1) (Stack[0]: null)
             iload2 // reference to arg1
             goto L7
         }
         L6 {
             f_new (Locals[5]: fibo/FibTailBad$, 1, 1, 1, 1) (Stack[0]: null)
             aload0 // reference to self
             iload1 // reference to arg0
             iconst_1
             isub
             iload3
             iload2 // reference to arg1
             iload3
             iadd
             invokevirtual fibo/FibTailBad$.fib(III)I
         }
         L1 {
             istore5
         }
         L8 {
             iload5
         }
         L2 {
             goto L7
         }
         L7 {
             f_new (Locals[5]: fibo/FibTailBad$, 1, 1, 1, 1) (Stack[1]: 1)
             ireturn
         }
         L4 {
         }
     }

     private FibTailBad$() { //  //()V
         <localVar:index=0 , name=this , desc=Lfibo/FibTailBad$;, sig=null, start=L1, end=L2>

         L1 {
             aload0 // reference to self
             invokespecial java/lang/Object.()V
             aload0 // reference to self
             putstatic fibo/FibTailBad$.MODULE$:fibo.FibTailBad$
         }
         L3 {
             return
         }
         L2 {
         }
     }

Scala: [B@6cec5b09ScalaInlineInfo: [B@7eea59ce}

Comparison of stacks

The Fibonacci calculation is in block L4 (optimized) and block L6 (not optimized). The optimized code breakdown follows:

bytecode description depth
iload1 // reference to arg0 push i 1
iconst_1 push 1 2
isub pop I, pop 1, push 1 + 1 1
iload3 push f 2
iload2 // reference to arg1 push p 3
iload3 push f 4
iadd pop p, pop f, push p + f 3
istore3 pop and store p + f 2
istore2 // reference to arg1 pop and store p 1
istore1 // reference to arg0 pop and store I + 1 0
goto L1

Notice that the optimized code block ends with nothing on the stack and a goto to L1, the loop termination test.

The non-optimized code analysis (block L6) is:

bytecode description depth
aload0 // reference to self push return address on stack 1
iload1 // reference to arg0 push i 2
iconst_1 push 1 3
isub pop i, pop 1, push i – 1 2
iload3 push f 3
iload2 // reference to arg1 push p 4
iload3 push f 5
iadd pop p, pop f, push p + f 4
invokevirtual fibo/FibTailBad$.fib(III)I call fib

Leading up to the recursive call, the stack holds:

  • the return address
  • the decremented counter
  • the previous Fibonacci number
  • the new Fibonacci number

No tail-call optimization in java

If you try the above analysis on java code, you will find that it is not tail-code optimized. Java does not support tail-call optimization. For example:

	private static int fib(int i, int p, int f) {
		switch (i) {
		case 0:
			new Exception().printStackTrace();
			return p;
		default:
			return fib(i - 1, f, p + f);
		}
	}

 

Decompiling

It is impossible to recover the original source code from the bytecode. Furthermore, it is impossible to infer recursion from iteration. Finally, the decompiler returns Java code, not Scala. Bytecode Viewer has several decompilers to choose from. Here is the output from one of the decompilers.

package fibo;

public final class FibTailRec$ {
   public static FibTailRec$ MODULE$;

   static {
      new FibTailRec$();
   }

   public int fib(int i, int p, int f) {
      while(true) {
         switch(i) {
         case 0:
            return p;
         default:
            int var10000 = i - 1;
            int var10001 = f;
            f += p;
            p = var10001;
            i = var10000;
         }
      }
   }

   private FibTailRec$() {
      MODULE$ = this;
   }
}

The switch default block corresponds to bytecode block L4, analyzed earlier. I chose this listing because it shows the block-scope variables var10000 and var10001 that hold temporary results.

Remarks

For various reasons, people decide to code in Scala. When they do, interest in system implementation and application performance can arise. It can be helpful for administrators to be familiar with the technology and the underlying mechanisms.

This posting is outside my field. Comments, suggestions, and corrections are welcome and will be acknowledged.

Summary

  • Functional language programmers tend to implement iteration as recursion.
  • A common case of recursion is tail call recursion.
  • The optimizer can eliminate the tail call.
  • The @tailrec decorator can cause the optimizer to report which routines cannot be tail-call optimized.
  • It is possible to inadvertently defeat tail call optimization.
  • Exception().printStackTrace() can demonstrate tail-call optimized.
  • Like Java, the Scala compiler produces bytecode that is executable by the java virtual machine.
  • Bytecode Viewer, by Konloch, can display the optimization.
  • Hand tracing bytecode shows the internals of optimized (iterative) code compared to recursive code.
  • Bytecode Viewer decompiles the optimized bytecode to iterative java, not the original, recursive Scala.
  • Java does not support tail call optimization.

Python variable scope

By Brian Fitzgerald

Python variable scoping rules are different from other languages. If you are accustomed to java, the behavior may surprise you. Scope can be file, class, or function; however, control blocks such as if-else, try-except, or while do not define scope. A variable that is defined in a control block can be used after the control block.

A simple demonstration case is the if-else block. In function test, variable a is set in the if and else blocks, and is used after the if-else structure.

scope1

Not defining a variable before entering a control block is considered idiomatic python. IDE PyCharm reports no warnings. If you try to define variable a before the if-else block, as you would in java, PyCharm greys out a and reports it as an unused variable.

scope2.png

Finally, if statement “a = 0” is changed to “pass”, then in statement “a = None”, a is no longer reported as an unused variable, and is displayed in dark grey.

scope3.png

In conclusion, python scoping is different from java’s. You should not carry java variable declaration habits into python projects.