I am new to Python but not new to PHP. I've started porting PHP APIs to Python hoping to learn the Python language along the way, currently trying to grasp List, Dict, Set, Tuple. I tried writing PHP's array_push() in Python:
def array_push(array: list | dict, *args: any) -> int:
if isinstance(array, list):
for arg in args:
# i wonder why list.append() doesn't take *args
array.append(arg)
return len(array)
elif isinstance(array, dict):
# wouldn't surprise me if there's a significantly faster way to do this,
# but this the first solution I could think of that worked
key = -1
# i tried using key = max(array.keys()), but that breaks if the dict has string keys..
for existingkey in array.keys():
if isinstance(existingkey, int) and existingkey > key:
key = existingkey
for arg in args:
# loop should not be needed because we already started with the highest key?
# while key in array:
key += 1
array[key] = arg
return len(array)
else:
raise TypeError(
"array_push() expects a list or dict as first argument, got " + str(type(array)))
and some tests:
l = ["foo", "bar", "baz"]
array_push(l, 1, "foo", 9, 9)
print(l) # prints ['foo', 'bar', 'baz', 1, 'foo', 9, 9]
d = {5: "five already exist, terrific..", 6: "as does 6..",
"string key": "it has a string key too", 2: "2 comes after 5, hurray"}
array_push(d, 1, "foo", 9, 9)
print(d) # prints {5: 'five already exist, terrific..', 'string key': 'it has a string key too', 2: '2 comes after 5, hurray', 6: 1, 7: 'foo', 8: 9, 9: 9}
array_push(5, 5) # TypeError: array_push() expects a list or dict as first argument, got <class 'int'>
2 Answers 2
The list case. As noted in a comment, you can simplify to this:
array.extend(args)
The dict case. It looks like the goal is to find the max integer key, if
any, which can be done more easily using the max()
function. Once
you have that, you can build a range to represent the desired keys
and then zip them together with the values.
# Max integer key, or -9 if none.
gen = (k for k in array if isinstance(k, int))
max_int_key = max(gen, default = -9)
# Initial key for added items.
k = 1 + max(-1, max_int_key)
# Update.
rng = range(k, k + len(args))
array.update(zip(rng, args))
If you're writing code like this, build on top of a solid testing foundation. It's good that your question was framed with some example test cases. However, they are too informal, since they are based on printing and comments. Mimicking an API, even just for learning, requires some real diligence to explore edge cases. Do yourself a favor and write some code to execute those tests automatically. This could take the form of learning how to use a Python test framework (I would recommend pytest), or just writing some code of your own to execute the tests and to print loud debugging messages if the results don't equal expectations (I did the latter while experimenting with your code).
This PHP-inspired function is in tension with typical Python design. I rarely write
a Python function that mutates its arguments – indeed, I view it as an
anti-pattern and need some fairly strong reasons to deviate from that general
principle. If you have an object that has legitimate needs for mutation, do it
in a proper class, with methods. That, after all, is what the built-in list
and dict
classes do: xs.append(...)
, xs.extend(...)
, d.update(...)
, and
so forth – it's mutation via method calls. Functions, by contrast, are at
their best when they leave their arguments unmolested. Of course, there are
exceptions to every rule, and it's fine that you're doing
this mainly to practice. Just recognize that the thing you've
built is unusual from the perspective of an experienced Python
programmer.
Some points to supplement @FMc's answer (though we'll put aside for the moment its excellent advice against mutation):
- The way you compute keys when extending dictionaries can be tidied with
enumerate
, as I show below. - You may prefer
type(array).__name__
tostr(type(array))
. - I'd return once after the error, as it's always
len(array)
. To wit:
def array_push(array, *args):
if isinstance(array, list): array.extend(args)
elif isinstance(array, dict):
min_key = max((k for k in array if isinstance(k, int)), default=0)
for key, arg in enumerate(args, min_key): array[key] = arg
else: raise TypeError(
f'array_push() expects a list or dict as first argument, got {type(array).__name__}')
return len(array)
Feel free to replace (k for k in array if isinstance(k, int))
with filter(lambda k: isinstance(k, int), array)
.
["foo", "bar", "baz"].extend([1, "foo", 9, 9]) => ['foo', 'bar', 'baz', 1, 'foo', 9, 9]
\$\endgroup\$array.extend(args);
instead of that first loop, but i will keep the original code in the question for now, in case someone has started reviewing ^^ \$\endgroup\$