Python and Eiffel: A Comparison in Conciseness
- Tags:
- python
- code conciseness
Introduction
Python is widely praised for its conciseness, which is one of the main reasons it has become so popular among developers. (Chat GPT lists some reasons) This article is intended to demonstrate that Eiffel can compare favourably to Python in terms of conciseness.
An Eiffel test set CONTAINER_STRUCTURE_TEST_SET has been partially ported to Python for comparison purposes. The test set uses a test class WIDGET with properties color
and width
. The color
attribute has values that are defined by an enumeration class COLOR_ENUM.
Test classes
Defining a Color Enumeration
Python
from enum import Enum, auto
class COLOR (Enum):
red = auto ()
blue = auto ()
green = auto ()
Eiffel
class
COLOR_ENUM
inherit
EL_ENUMERATION_NATURAL_8
rename
description_table as No_descriptions,
foreign_naming as eiffel_naming
end
create
make
feature -- Colors
blue: NATURAL_8
green: NATURAL_8
red: NATURAL_8
end
Comment on memory use
This code prints the size in memory of the COLOR
enumeration object and also it's numeric value.
print ('sizeof (COLOR.red) =', sys.getsizeof (COLOR.red))
print ('sizeof (COLOR.red.value) =', sys.getsizeof (COLOR.red.value))
OUTPUT:
sizeof (COLOR.red) = 56
sizeof (COLOR.red.value) = 28
This is to be expected as the Python int
type is a variable length numeric object and Color.red
is an enumeration object.
The WIDGET Class
Python
class WIDGET (object):
# Constants
Color_width_string = "color: %s; width %s"
def __init__ (self, color, width):
# require
assert self.valid_color (color), "valid color"
self.color = color.value; self.width = width
# ensure
assert isinstance (self.color, int), "integer value"
# Access
def color_name (self):
return COLOR (self.color).name
def __str__ (self):
return self.Color_width_string % (self.color_name (), self.width)
# Element change
def set_color (self, color):
# require
assert self.valid_color (color), "valid color"
self.color = color.value
# ensure
assert isinstance (self.color, int), "integer value"
def set_width (self, width):
self.width = width
# Status query
def is_color (self, color):
# require
assert self.valid_color (color), "valid color"
return self.color == color.value
def valid_color (self, color):
return isinstance (color, COLOR)
# end WIDGET
Eiffel
class
WIDGET
inherit
ANY
redefine
out
end
SHARED_COLOR_ENUM
rename
Color as Color_enum
end
create
make, make_2
convert
make ({TUPLE [NATURAL_8, INTEGER]})
feature {NONE} -- Initialization
make (tuple: TUPLE [color: NATURAL_8; width: INTEGER])
require
valid_color: valid_color (tuple.color)
do
color := tuple.color; width := tuple.width
end
make_2 (a_color: NATURAL_8; a_width: INTEGER)
do
color := a_color; width := a_width
end
feature -- Access
color: NATURAL_8
color_name: STRING_8
do
Result := Color_enum.name (color)
end
out: STRING
do
Result := Color_width_string #$ [color_name, width]
end
width: INTEGER
feature -- Element change
set_color (a_color: NATURAL_8)
require
valid_color: valid_color (a_color)
do
color := a_color
end
set_width (a_width: INTEGER)
do
width := a_width
end
feature -- Status query
is_color (a_color: NATURAL_8): BOOLEAN
do
Result := color = a_color
end
valid_color (a_color: NATURAL_8): BOOLEAN
do
Result := Color_enum.valid_value (a_color)
end
feature {NONE} -- Constants
Color_width_string: ZSTRING
once
Result := "color: %S; width %S"
end
end
Comments
The Python equivalent of the Eiffel out
routine is __str__
. The implementation of this routine is almost identical for Python and Eiffel, by substituting a tuple into a template string with %S
placeholders. (lowercase for Python)
Python does not have an equivalent to the Eiffel convert
keyword, making initialisation of WIDGET
arrays more verbose in Python.
Creating a WIDGET Array
Python
# Constants
Widget_list = [
WIDGET (COLOR.red, 200), WIDGET (COLOR.blue, 300), WIDGET (COLOR.green, 100),
WIDGET (COLOR.blue, 500), WIDGET (COLOR.red, 1200)
]
Eiffel
feature {NONE} -- Constants
Widget_list: EL_ARRAYED_LIST [WIDGET]
once
create Result.make_from_array (<<
[Color.red, 200], [Color.blue, 300], [Color.green, 100], [Color.blue, 500], [Color.red, 1200]
>>)
end
Comments
The Eiffel convert
keyword allows a more concise initialisation of arrays using tuples.
Test Derived Lists
Python
# Implementation
def widget_colors ():
result = []
for widget in Widget_list:
result.append (widget.color)
return result
# Test
def test_derived_list ():
width_list = [widget.width for widget in Widget_list]
assert len (width_list) == len (Widget_list), "same count"
for cursor_index, widget in enumerate (Widget_list, start = 0):
# print (str (widget))
assert widget.width == width_list [cursor_index]
#print ()
blue_width_list = [widget.width for widget in Widget_list if widget.is_color (COLOR.blue)]
assert len (blue_width_list) == 2, "2 results"
assert blue_width_list [0] == 300, "first is 300"
assert blue_width_list [-1] == 500, "last is 500"
assert [widget.color for widget in Widget_list] == widget_colors (), "same list"
Eiffel
feature {NONE} -- Widget Implementation
widget_colors: EL_ARRAYED_LIST [NATURAL_8]
do
create Result.make (10)
across Widget_list as list loop
Result.extend (list.item.color)
end
end
feature -- Test
test_derived_list
-- CONTAINER_STRUCTURE_TEST_SET.test_derived_list
note
testing: "[
covers/{EL_CONTAINER_STRUCTURE}.new_special,
covers/{EL_CONTAINER_STRUCTURE}.derived_list,
covers/{EL_CONTAINER_STRUCTURE}.derived_list_meeting,
covers/{EL_INITIALIZED_ARRAYED_LIST_FACTORY}.new_list,
covers/{EL_INITIALIZED_OBJECT_FACTORY}.new_generic_type_factory
]"
do
if attached {EL_ARRAYED_LIST [INTEGER]}
Widget_list.derived_list (agent {WIDGET}.width) as width_list
then
assert ("same count", width_list.count = Widget_list.count)
across Widget_list as widget loop
lio.put_line (widget.item.out)
assert ("same width", widget.item.width = width_list [widget.cursor_index])
end
else
failed ("create width_list")
end
if attached {EL_ARRAYED_LIST [INTEGER]}
Widget_list.derived_list_if (agent {WIDGET}.width, agent {WIDGET}.is_color (Color.blue))
as blue_width_list
then
assert ("2 results", blue_width_list.count = 2)
assert ("first is 300", blue_width_list.first = 300)
assert ("last is 500", blue_width_list.last = 500)
else
failed ("create width_list")
end
assert ("same list", Widget_list.derived_list (agent {WIDGET}.color) ~ widget_colors)
end
Comments
In Python using a for loop inside square brackets [..]
to generate a list is referred to as a "list comprehension".
Test Find Linear Position
Python
def test_find_linear_position ():
widget_iter = iter (Widget_list)
first_blue = next ((widget for widget in widget_iter if widget.is_color (COLOR.blue)), None)
if first_blue:
assert first_blue.width == 300, "first blue is 300"
else:
failed ("found")
next_blue = next ((widget for widget in widget_iter if widget.is_color (COLOR.blue)), None)
if next_blue:
assert next_blue.width == 500, "next blue is 500"
else:
failed ("found")
first_1200 = next ((widget for widget in Widget_list if widget.width == 1200), None)
if first_1200:
assert first_1200.is_color (COLOR.red), "first 1200 width has color red"
else:
failed ("found")
assert Widget_list.index (Widget_list [2]) == 2, "3rd position"
Eiffel
test_find_linear_position
note
testing: "[
covers/{EL_LINEAR}.index_of,
covers/{EL_LINEAR}.find_first_true,
covers/{EL_LINEAR}.find_next_true,
covers/{EL_LINEAR}.find_first_equal
]"
do
Widget_list.find_first_true (agent {WIDGET}.is_color (Color.blue))
assert_found ("first blue is 300", Widget_list, Widget_list.item.width = 300)
Widget_list.find_next_true (agent {WIDGET}.is_color (Color.blue))
assert_found ("next blue is 500", Widget_list, Widget_list.item.width = 500)
Widget_list.find_first_equal (1200, agent {WIDGET}.width)
assert_found ("first 1200 width has color red", Widget_list, Widget_list.item.color = Color.red)
Widget_list.start
assert ("3rd position", Widget_list.index_of (Widget_list [3], 1) = 3)
assert ("index is 1", Widget_list.index = 1)
end
Comment
Iterating Object Attributes
Python
class MICROSOFT_COMPILER_OPTIONS (object):
# From "Microsoft SDKs\Windows\v7.1\Bin\SetEnv.Cmd" (See notes below)
Valid_architectures = ['x86', 'x64', 'ia64']
Valid_build_types = ['Debug', 'Release']
Valid_compatibility_options = ['2003', '2008', 'vista', 'win7', 'xp']
# Initialization
def __init__ (
self, architecture = 'x64', build_type = 'Release',
compatibility = 'win7', app_compat_flags = ''
):
# default switches: /win7 /x64 /Release
self.architecture = architecture
self.build_type = build_type
self.compatibility = compatibility
self.app_compat_flags = app_compat_flags
# Status query
def is_x86_architecture (self):
return self.architecture == self.Valid_architectures [0]
# Element change
def set_architecture (self, architecture):
assert (architecture in self.Valid_architectures)
self.architecture = architecture
def set_build_type (self, build_type):
assert (build_type in self.Valid_build_types)
self.build_type = build_type
def set_compatibility (self, compatibility):
# set compiler OS compability flag
assert (compatibility in self.Valid_compatibility_options)
self.compatibility = compatibility
def set_app_compat_flags (self, app_compat_flags):
# used to set compatibility mode registry entry value during installation
# HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
self.app_compat_flags = app_compat_flags
# Conversion
def as_switch_string (self):
# command switches string
# Get list of all attributes
option_list = list (self.__dict__.values ())
option_list.remove (self.app_compat_flags)
result = ' '.join (['/' + opt for opt in option_list])
return result
#end MICROSOFT_COMPILER_OPTIONS
Eiffel
class
MICROSOFT_COMPILER_OPTIONS
inherit
EL_REFLECTIVE
rename
field_included as is_any_field,
foreign_naming as eiffel_naming
end
create
make, make_default
feature {NONE} -- Initialization
make (a_architecture, a_build_type, a_compatibility, a_app_compat_flags: STRING)
do
set_architecture (a_architecture); set_build_type (a_build_type)
set_compatibility (a_compatibility)
app_compat_flags := a_app_compat_flags
end
make_default
do
make ("x64", "Release", "win7", "")
end
feature -- Access
app_compat_flags: STRING
architecture: STRING
build_type: STRING
compatibility: STRING
feature -- Element change
set_app_compat_flags (a_app_compat_flags: STRING)
do
app_compat_flags := a_app_compat_flags
end
set_architecture (a_architecture: STRING)
require
valid_architecture: Valid_architectures.has (a_architecture)
do
architecture := a_architecture
end
set_build_type (a_build_type: STRING)
require
valid_build_type: Valid_build_types.has (a_build_type)
do
build_type := a_build_type
end
set_compatibility (a_compatibility: STRING)
require
valid_compatibility: Valid_compatibility_options.has (a_compatibility)
do
compatibility := a_compatibility
end
feature -- Conversion
as_switch_string: STRING
local
s: EL_STRING_8_ROUTINES
do
create Result.make_empty
if attached {EL_ARRAYED_LIST [STRING]} value_list_for_type ({STRING}) as value_list then
value_list.prune (app_compat_flags)
if attached {LIST [STRING]} value_list.derived_list (agent as_switch) as switch_list then
Result := s.joined_list (switch_list, ' ')
end
end
end
feature -- Constants
Valid_architectures: EL_STRING_8_LIST
once
Result := "x86, x64, ia64"
end
Valid_build_types: EL_STRING_8_LIST
once
Result := "Debug, Release"
end
Valid_compatibility_options: EL_STRING_8_LIST
once
Result := "2003, 2008, vista, win7, xp"
end
feature {NONE} -- Implementation
as_switch (option: STRING): STRING
do
Result := "/" + option
end
end
Comments
A very useful standard class routine in Python is __dict__
which gives you a hash table (dictionary) of all object attributes. This is emulated in Eiffel with the routine EL_REFLECTIVE.value_list_for_type
.
value_list_for_type (field_type: TYPE [ANY]): EL_ARRAYED_LIST [ANY]
-- list of field values in `Current' for fields with type `field_type'
do
Result := field_table.value_list_for_type (current_reflective, field_type)
end
The field_table
attribute is of type EL_FIELD_TABLE. See class EL_REFLECTIVE and EL_STRING_X_ROUTINES for details of routines in as_switch_string
.
The Python version of the MS compiler option class has a real world use in an extension to the the Scons build system for Eiffel.
Note that because of the use of the convert
facility in Eiffel, the "validity lists" do not need be initialised from a string array. They can be all listed in one string. Extra point to Eiffel !
CONTAINER Extension Classes
The Python like functionality in Eiffel comes from the classes EL_CUMULATIVE_CONTAINER_ARITHMETIC and EL_CONTAINER_STRUCTURE. The class EL_CONTAINER_WRAPPER is a wrapper for CONTAINER so that any Eiffel container can benefit from the extra routines.
Hierarchy
EL_CUMULATIVE_CONTAINER_ARITHMETIC* [G]
EL_CONTAINER_STRUCTURE EL_CONTAINER_ARITHMETIC EL_LINEAR EL_FILE_GENERAL_LINE_SOURCE EL_PLAIN_TEXT_LINE_SOURCE EL_CHAIN EL_ARRAYED_LIST EL_ARRAYED_MAP_LIST EL_KEY_INDEXED_ARRAYED_MAP_LIST EL_CONTAINER_WRAPPER EL_HASH_SET EL_HASH_TABLE
Project to Upgrade Eiffel-Loop
If you would like to help support a project to upgrade the Eiffel-Loop libraries for the most recent GPL version of the EiffelStudio compiler there are a number of ways to do this.
- Share a link on social media to either of my two software products My Ching or Matryoska.
- Write a short review for either of the software products My Ching or Matryoska. (You can ask me for a complementary license for the full version)
- Purchase a license for either of the products My Ching or Matryoska
- Donate via Paypal