5
\$\begingroup\$

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'>
\$\endgroup\$
2
  • 3
    \$\begingroup\$ Do you know about the extend method? ["foo", "bar", "baz"].extend([1, "foo", 9, 9]) => ['foo', 'bar', 'baz', 1, 'foo', 9, 9] \$\endgroup\$ May 7 at 21:56
  • 1
    \$\begingroup\$ @BjörnLindqvist nope! guess i should have used 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\$
    – hanshenrik
    May 7 at 22:04

2 Answers 2

4
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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__ to str(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).

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.