12

I'd like to see if it's possible to run through a list of functions in a function. The closest thing I could find is looping through an entire module. I only want to use a pre-selected list of functions.

Here's my original problem:

  1. Given a string, check each letter to see if any of the 5 tests fulfill.
  2. If a minimum of 1 letter passes a check, return True.
  3. If all letters in the string fails the check, return False.
  4. For each letter in the string, we will check these functions: isalnum(), isalpha(), isdigit(), islower(), isupper()
  5. The result of each test should print to different lines.

Sample Input

    qA2

Sample Output (must print to separate lines, True if at least one letter passes, or false is all letters fail each test):

    True
    True
    True
    True
    True

I wrote this for one test. Of course I could just write 5 different sets of code but that seems ugly. Then I started wondering if I could just loop through all the tests they're asking for.

Code for just one test:

    raw = 'asdfaa3fa'
    counter = 0
    for i in xrange(len(raw)):
        if raw[i].isdigit() == True: ## This line is where I'd loop in diff func's
            counter = 1
            print True
            break
    if counter == 0:
        print False

My fail attempt to run a loop with all the tests:

    raw = 'asdfaa3fa'
    lst = [raw[i].isalnum(),raw[i].isalpha(),raw[i].isdigit(),raw[i].islower(),raw[i].isupper()]
    counter = 0
    for f in range(0,5):
        for i in xrange(len(raw)):
            if lst[f] == True: ## loop through f, which then loops through i
                print lst[f] 
                counter = 1
                print True
        break
        if counter == 0:
    print False

So how do I fix this code to fulfill all the rules up there?


Using info from all the comments - this code fulfills the rules stated above, looping through each method dynamically as well.

    raw = 'ABC'
    functions = [str.isalnum, str.isalpha, str.isdigit, str.islower,  str.isupper]

    for func in functions:
        print any(func(letter) for letter in raw)

getattr approach (I think this is called introspection method?)

    raw = 'ABC'

    meths = ['isalnum', 'isalpha', 'isdigit', 'islower', 'isupper']
    for m in meths: 
        print any(getattr(c,m)() for c in raw)

List comprehension approach:

    from __future__ import print_function ## Changing to Python 3 to use print in list comp

    raw = 'ABC'
    functions = [str.isalnum, str.isalpha, str.isdigit, str.islower, str.isupper]
    solution = [print(func(raw)) for func in functions]
Cimbali
  • 11,012
  • 1
  • 39
  • 68
jhub1
  • 611
  • 3
  • 7
  • 19
  • Why not wrap all of those tests into a big function called `isValid()`? – gowrath Sep 10 '16 at 04:51
  • Hmmm...let me re-write the code right now and see where that gets me. Will edit with the new version. – jhub1 Sep 10 '16 at 04:54
  • 1
    How is a character meant to pass both the islower() and isupper() test? Or did you mean return True if any character matches any of the tests? – gowrath Sep 10 '16 at 04:59
  • So let's say the string is 'sdgSGd'. The test for islower would show True. The test for isupper would show True. Both on different lines too. The check is for EACH letter, NOT the entire string. – jhub1 Sep 10 '16 at 05:00
  • So you want to make sure that each conditional function is satisfied by at least a single letter in the string? – gowrath Sep 10 '16 at 05:02
  • Yup exactly! 'alkdfjas35X' would show TRUE for the upper test since at 1 letter passed it. X. – jhub1 Sep 10 '16 at 05:03
  • 1
    @jhub1 I made your question much shorter (removed "the fluff" as we say here), to make it more straight to the point. However, I left in your "solutions from comments" though they really don't have a place in a question either. I only did so because they are not explicitly in an answer yet: I suggest you edit them out yourself and post them in an answer of your own (this is a great way to sum up answers). – Cimbali Oct 07 '16 at 13:14

9 Answers9

18

The way you are looping through a list of functions is slightly off. This would be a valid way to do it. The functions you need to store in the list are the generic string functions given by str.funcname. Once you have those list of functions, you can loop through them using a for loop, and just treat it like a normal function!

raw = 'asdfaa3fa'
functions = [str.isalnum, str.isalpha, str.isdigit, str.islower,  str.isupper]  # list of functions

for fn in functions:     # iterate over list of functions, where the current function in the list is referred to as fn
    for ch in raw:       # for each character in the string raw
        if fn(ch):        
            print(True)
            break

Sample outputs:

Input                     Output
===================================
"qA2"         ----->      True True True True True
"asdfaa3fa"   ----->      True True True True

Also I notice you seem to use indexing for iteration which makes me feel like you might be coming from a language like C/C++. The for in loop construct is really powerful in python so I would read up on it (y).

Above is a more pythonic way to do this but just as a learning tool, I wrote a working version that matches how you tried to do it as much as possible to show you where you went wrong specifically. Here it is with comments:

raw = 'asdfaa3fa'
lst = [str.isalnum, str.isalpha, str.isdigit, str.islower, str.isupper]   # notice youre treating the functions just like variables and aren't actually calling them. That is, you're writing str.isalpha instead of str.isalpha()
for f in range(0,5):
    counter = 0
    for i in xrange(len(raw)):
        if lst[f](raw[i]) == True:  # In your attempt, you were checking if lst[f]==True; lst[f] is a function so you are checking if a function == True. Instead, you need to pass an argument to lst[f](), in this case the ith character of raw, and check whether what that function evaluates to is true
            print lst[f] 
            counter = 1
            print True
            break
    if counter == 0:
        print False
gowrath
  • 3,136
  • 2
  • 17
  • 32
3

Okay, so the first question is easy enough. The simple way to do it is just do

def foo(raw):
  for c in raw:
    if c.isalpha(): return True
    if c.isdigit(): return True
    # the other cases
  return False

Never neglect the simplest thing that could work.

Now, if you want to do it dynamically -- which is the magic keyword you probably needed, you want to apply something like this (cribbed from another question):

meths = [isalnum, isalpha, isdigit, islower, isupper]
for c in raw:    
  for m in meths:
    getattr(c, m)()

Warning, this is untested code meant to give you the idea. The key notion here is that the methods of an object are attributes just like anything else, so, for example getattr("a", "isalpha")() does the following:

  • Uses getattr to search the attributes dictionary of "a" for a method named isalpha
  • Returns that method itself -- <function isalpha>
  • then invokes that method using the () which is the function application operator in Python.

See this example:

In [11]: getattr('a', 'isalpha')()
Out[11]: True
Community
  • 1
  • 1
Charlie Martin
  • 110,348
  • 25
  • 193
  • 263
  • This doesn't solve the problem OP wanted. He/she wasn't clear in the question; reread the comments to clarify. – gowrath Sep 10 '16 at 05:14
  • Actually, I believe it does: it tests each character in the string, and tests it against each named method. The addition of a flag variable and an if would complete the code, but it's no fun just giving out the answer. – Charlie Martin Sep 10 '16 at 05:17
  • I meant the first simple case. Sorry, I should've been more specific. – gowrath Sep 10 '16 at 05:22
  • 1
    Ah. I was trying to make a point: it's cool that you *can* do it dynamically, but unless you've got a really good reason to need to, the simple code is better. – Charlie Martin Sep 10 '16 at 15:07
  • @CharlieMartin I can't seem to get this code to work. Still trying to work it right now - any thoughts? Here's my implementation: https://repl.it/D4lD/4 – jhub1 Sep 11 '16 at 22:26
  • @CharlieMartin nevermind. I fixed it. It now works - I had to convert the list into strings! https://repl.it/D4lD/12 – jhub1 Sep 11 '16 at 23:06
  • @jhub1 yep. the `getattr` takes two *string* arguments. – Charlie Martin Sep 12 '16 at 00:34
3

All the other answers are correct, but since you're a beginner, I want to point out the problem in your code:

lst = [raw[i].isalnum(),raw[i].isalpha(),raw[i].isdigit(),raw[i].islower(),raw[i].isupper()]

First: Not sure which value i currently has in your code snipped, but it seems to point somewhere in the string - which results in single characters being evaluated, not the whole string raw.

Second: When you build your list, you are already calling the methods you want to insert, which has the effect that not the functions themself get inserted, but their return values (that's why you're seeing all those True values in your print statement).

Try changing your code as follows:

lst = [raw.isalnum, raw.isalpha, raw.isdigit, raw.islower, raw.isupper]
andreas-hofmann
  • 2,378
  • 11
  • 15
  • So my thinking was to create two loops. First loop, using f, looping through the functions. Second loop, using i, was to index the raw which is really return each letter to check against each function in the f loop. – jhub1 Sep 10 '16 at 05:24
  • @jhub1 I just updated my answer to explain where you went wrong in your specific attempt; you weren't actually far off! Do check it out. – gowrath Sep 10 '16 at 11:38
  • 1
    @gowrath I'm a slow coder - still going through all the concepts including your post (for loop constructions, re module, getattr, etc.). Found a neat use of any() so playing with that too. I know it's over complicating but it's fun trying out all this other stuff! – jhub1 Sep 10 '16 at 19:26
  • @gowrath how's this look? I combined your method and any to get to some really shortened code. https://repl.it/D4kO – jhub1 Sep 11 '16 at 22:14
  • @jhub1 Looks fantastic. Nicely done (y). Please mark a reply as an answer if you feel your question has been answered so it doesn't remain in the unanswered section. – gowrath Sep 11 '16 at 22:20
2

To answer the original question:

raw = 'asdfa3fa'
functions = [str.isalnum, str.isalpha, str.isdigit, str.islower, str.isupper]
isanything = [func(raw) for func in functions]
print repr(isanything)
J Earls
  • 1,792
  • 8
  • 12
2

I'm going to guess that you're validating password complexity, and I'm also going to say that software which takes an input and says "False" and there's no indication why is user-hostile, so the most important thing is not "how to loop over nested char function code wizardry (*)" but "give good feedback", and suggest something more like:

raw = 'asdfaa3fa'

import re

def validate_password(password):
    """ This function takes a password string, and validates it
        against the complexity requirements from {wherever}
        and returns True if it's complex enough, otherwise False """

    if not re.search('\d', password):
        print("Error: password needs to include at least one number")
        return False

    elif not re.search('[a-z]', password):
        print("Error: password must include at least one lowercase letter")
        return False

    elif not re.search('[A-Z]', password):
        print("Error: password must include at least one uppercase letter")
        return False

    print("Password is OK")
    return True

validate_password(raw)

Try online at repl.it

And the regex searching checks ranges of characters and digits in one call, which is neater than a loop over characters.

(PS. your functions overlap; a string which has characters matching 'isupper', 'islower' and 'isnumeric' already has 'isadigit' and 'isalnum' covered. More interesting would be to handle characters like ! which are not upper, lower, digits or alnum).


(*) function wizardry like the other answers is normally exactly what I would answer, but there's so much of that already answered that I may as well answer the other way instead :P

TessellatingHeckler
  • 27,511
  • 4
  • 48
  • 87
  • Seconded on the regex but OP might be new to Python; throwing re might be overwhelming. Plus it's good practice to learn how to treat functions as first class citizens. – gowrath Sep 10 '16 at 05:19
  • This is actually great for password complexity actually. Didn't realize that. However, it's really just a DIY-learn-Python exercise to teach beginners like me how to code. Understanding the real implications of code like this in the real world is great though! – jhub1 Sep 10 '16 at 05:20
1

Since you are looping through a list of simple items and trying to find if all of the functions has any valid results, you can simply define the list of functions you want to call on the input and return that. Here is a rather pythonic example of what you are trying to achieve:

def checker(checks, value):
    return all(any(check(r) for r in value) for check in checks)

Test it out:

>>> def checker(checks, value):
...     return all(any(check(r) for r in value) for check in checks)
... 
>>> checks = [str.isalnum, str.isalpha, str.isdigit, str.islower, str.isupper]
>>> checker(checks, 'abcdef123ABC')
True
>>> checker(checks, 'abcdef123')
False
>>> 
metatoaster
  • 17,419
  • 5
  • 55
  • 66
1

You can use introspection to loop through all of an object's attributes, whether they be functions or some other type.

However you probably don't want to do that here, because str has lots of function attributes, and you're only interested in five of them. It's probably better to do as you did and just make a list of the five you want.

Also, you don't need to loop over each character of the string if you don't want to; those functions already look at the whole string.

Community
  • 1
  • 1
John Gordon
  • 29,573
  • 7
  • 33
  • 58
  • Sorry! I wrote a terrible description. The code prompt asks to check each letter which is why I was struggling with a bunch of loops. So for example - 'dasfsdfX' would return True for an upper check, 'asdfa' would return False. – jhub1 Sep 10 '16 at 06:32
1

Check out this one-line solution for your problem. That problem is from HackerRank. I loop through a list of functions using the built-in getattr function.

s='qA2'
[print(bool(list(filter(lambda x : getattr(x, func)(),s)))) for func in ['isalnum','isalpha','isdigit','islower','isupper']]
Cody Gray - on strike
  • 239,200
  • 50
  • 490
  • 574
Rajesh Samui
  • 167
  • 1
  • 2
  • 12
0

By using map(i, list(s)), where i represents the current function from the functions list, the code effectively applies the function i to each character in the string s. The resulting map object contains the boolean values indicating whether each character satisfies the property defined by the function.

The purpose of using map() here is to efficiently apply the functions to the characters in the string without the need for explicit loops. It allows for concise code and eliminates the need for repetitive conditional statements when checking multiple properties of a string.

s = 'qA2'
functions = [str.isalnum, str.isalpha, str.isdigit, str.islower, str.isupper]
for i in functions:
    true_list = map(i, list(s))
    if True in true_list:
        print(True)
    else:
        print(False)
kinkpunk
  • 1
  • 3
  • Answer needs supporting information Your answer could be improved with additional supporting information. Please [edit](https://stackoverflow.com/posts/76253854/edit) to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](https://stackoverflow.com/help/how-to-answer). – moken May 18 '23 at 13:34