Question About Logic In Python

J

James H.

Greetings! I'm new to Python and am struggling a little with "and" and
"or" logic in Python. Since Python always ends up returning a value
and this is a little different from C, the language I understand best
(i.e. C returns non-zero as true, and zero as false), is there anything
I should be aware of given Python's different approach? Namely any
pitfalls or neat tricks that make the Python approach cool or save my
butt.

Thank you!

James
 
D

Dan Bishop

James said:
Greetings! I'm new to Python and am struggling a little with "and" and
"or" logic in Python. Since Python always ends up returning a value
and this is a little different from C, the language I understand best
(i.e. C returns non-zero as true, and zero as false), is there anything
I should be aware of given Python's different approach? Namely any
pitfalls or neat tricks that make the Python approach cool or save my
butt.

The most common use of this feature is "x = x or default_val" as a
shorthand for "if not x: x = default_val".

Also, you can emulate C's ternary operator (cond ? t : f) with
(cond and [t] or [f])[0].
 
M

mensanator

James said:
Greetings! I'm new to Python and am struggling a little with "and" and
"or" logic in Python. Since Python always ends up returning a value
and this is a little different from C, the language I understand best
(i.e. C returns non-zero as true, and zero as false), is there anything
I should be aware of given Python's different approach? Namely any
pitfalls or neat tricks that make the Python approach cool or save my
butt.

Thank you!

James

Booleans are a subtype of plain integers, so if you use them
in arithmetic expressions, they evaluate to 0 and 1.
print "Boolean algebra"
print "%9s %9s | %9s %9s %9s" % ('A','B','A and B','A or B','A xor B')
print "-"*51
for a in [False,True]:
for b in [False,True]:
print "%9s %9s | %9s %9s %9s" % (a,b,(a and b),(a or b),((a and not
b) or (not a and b)))
print
print
print "Arithmetic"
print "%9s %9s | %9s %9s %9s" % ('A','B','A + B','A * B','A - B')
print "-"*51
for a in [False,True]:
for b in [False,True]:
print "%9s %9s | %9s %9s %9s" % (a,b,(a + b),(a * b),(a - b))

Boolean algebra
A B | A and B A or B A xor B
---------------------------------------------------
False False | False False False
False True | False True True
True False | False True True
True True | True True False


Arithmetic
A B | A + B A * B A - B
 
S

sven

At said:
Greetings! I'm new to Python and am struggling a little with "and" and
"or" logic in Python. Since Python always ends up returning a value
and this is a little different from C, the language I understand best
(i.e. C returns non-zero as true, and zero as false), is there anything
I should be aware of given Python's different approach? Namely any
pitfalls or neat tricks that make the Python approach cool or save my
butt.

to make sure that an operation yields a boolean value wrap a bool()
around an expression.
None, 0 and objects which's len is 0 yield False.
so you can also do stuff like that:
>>> a = []
>>> b = [1,2,3]
>>> a or b
[1, 2, 3]
.... def __len__(self): return 0
........ def __len__(self): return 1
....<__main__.Bar instance at 0x7D289940>

sven.
 
S

Steven D'Aprano

to make sure that an operation yields a boolean value wrap a bool()
around an expression.
None, 0 and objects which's len is 0 yield False.
so you can also do stuff like that:

Are there actually any usage cases for *needing* a Boolean value? Any
object can be used for truth testing, eg:

if the_str

is to be preferred over:

if bool(the_str)

or even worse:

if bool(the_str != "")

Or wait, I have thought of one usage case: if you are returning a value
that you know will be used only as a flag, you should convert it into a
bool. Are there any other uses for bool()?
 
B

Bengt Richter

Are there actually any usage cases for *needing* a Boolean value? Any
object can be used for truth testing, eg:

if the_str

is to be preferred over:

if bool(the_str)

or even worse:

if bool(the_str != "")

Or wait, I have thought of one usage case: if you are returning a value
that you know will be used only as a flag, you should convert it into a
bool. Are there any other uses for bool()?
making an index (it's an int subclass), as in
>>> things = None, 0, 1, 0.0, 5.0, '', 'a', [], [1], {}, {1:2}
>>> for thing in things:
... print 'if %-6r would act like if %s' % (thing, ('False','True')[bool(thing)])
...
if None would act like if False
if 0 would act like if False
if 1 would act like if True
if 0.0 would act like if False
if 5.0 would act like if True
if '' would act like if False
if 'a' would act like if True
if [] would act like if False
if [1] would act like if True
if {} would act like if False
if {1: 2} would act like if True

Regards,
Bengt Richter
 
R

Ron Adam

Steven said:
Are there actually any usage cases for *needing* a Boolean value? Any
object can be used for truth testing, eg:

if the_str

is to be preferred over:

if bool(the_str)

or even worse:

if bool(the_str != "")

Or wait, I have thought of one usage case: if you are returning a value
that you know will be used only as a flag, you should convert it into a
bool. Are there any other uses for bool()?

Of course if any of the default False or True conditions are
inconsistent with the logic you use, you need to do explicit truth testing.

if val > -1:

Where 0 would be True condition.


if arg != None:

Where '' could be a True condition.


Also... you need to be careful what order you do your comparisons in as..

(1 and 2) != (2 and 1) # they are both True, but not equal.

bool(1 and 2) == bool(2 and 1)

(1 and 2) * value != (2 and 1) * value
# except if value is False.

bool(1 and 2) * value == bool(2 and 1) * value


So..

bool(a and b) * value

Would return value or zero, which is usually what I want when I do this
type of expression.

Cheers,
Ron
 
S

Steven D'Aprano

Are there actually any usage cases for *needing* a Boolean value? Any
object can be used for truth testing, eg:
[snip]

making an index (it's an int subclass), as in
things = None, 0, 1, 0.0, 5.0, '', 'a', [], [1], {}, {1:2}
for thing in things:
... print 'if %-6r would act like if %s' % (thing, ('False','True')[bool(thing)])
...

That's a pretty artificial example though. Your general index ranges from
0 to n inclusive, where n is unlikely to be 1. That limits the usefulness
of the idiom sequence_or_mapping[bool(thing)] to a tiny set of cases.

As near as I can tell, explicitly converting objects to booleans is mostly
useful for demonstrating that booleans aren't needed for truth testing.
 
S

Steven D'Aprano

Steven said:
Are there actually any usage cases for *needing* a Boolean value? Any
object can be used for truth testing, eg:
[snip]

Of course if any of the default False or True conditions are
inconsistent with the logic you use, you need to do explicit truth testing.
[snip]

So..

bool(a and b) * value

Would return value or zero, which is usually what I want when I do this
type of expression.

That's all very interesting, and valuable advice for somebody who doesn't
understand how Python's logical operators work, but the question is, when
would you actually want that type of expression?

In practice, how often do you really care that your truth values have the
specific values 0 and 1 rather than anything false and anything true? In
what circumstances?
 
B

Bengt Richter

Steven said:
Are there actually any usage cases for *needing* a Boolean value? Any
object can be used for truth testing, eg:
[snip]

Of course if any of the default False or True conditions are
inconsistent with the logic you use, you need to do explicit truth testing.
[snip]

So..

bool(a and b) * value

Would return value or zero, which is usually what I want when I do this
type of expression.

That's all very interesting, and valuable advice for somebody who doesn't
understand how Python's logical operators work, but the question is, when
would you actually want that type of expression?

In practice, how often do you really care that your truth values have the
specific values 0 and 1 rather than anything false and anything true? In
what circumstances?
When you want to use the value as an index fed to something that has a
__getitem__ for which only the values 0 and 1 are valid, e.g., a list
or tuple of length 2, as I tried to illustrate before ;-)

Also, since values 0 and 1 are the values of a bit, you can shift it
and create a mask that encodes many logical values at once, which can
be handy for truth table stuff or perhaps indexing a 2**nbits table
rather than using a tree of nested if/elses to select values.

BTW, you asked
"Are there actually any usage cases for *needing* a Boolean value?"
^^^ ;-)
AFAIK, "one" is enough to make the answer "yes" ;-)

Of course you can use other expressions than bool(x) to get the boolean
value, but you may have to think more about whether (x and 1) will
do it, or whether you should write (x!=0) or, in case x can be None,
perhaps settle on (x and 1 or 0) as an idiom to play safe.
Well, bool(x) is safe, and less typing ;-) OTOH, it's not a hammer for all nails.

Regards,
Bengt Richter
 
R

Ron Adam

That's all very interesting, and valuable advice for somebody who doesn't
understand how Python's logical operators work, but the question is, when
would you actually want that type of expression?

It's a filter which returns a value or zero depending on conditions a and b.

Some examples...

High pass filter:

(value > 0) * value

Low pass filter:

(value < 0) * value

Band pass filter:

(min < value < max) * value


Changing and and or to return bools only, doesn't prevent us from doing
anything we can already do. It just means changing the context to
explicitly return a non bool when you want one as I did here.
In practice, how often do you really care that your truth values have the
specific values 0 and 1 rather than anything false and anything true? In
what circumstances?

We can separate these into two groups...

1. True and false condition testing in which the result of the
condition is not used in any further expressions.

You are correct in this case, it doesn't matter. Any True values would work.

2. Expressions that will be used in a calculation or another
expression.

This matters because if you aren't careful your results may not be what
you expect.

But group (2) can also be a part of group (1). So then again it may
matter there too.

This has more to do with clarity and separating function into forms that
have the potential for least surprises. Or to put it another way, forms
that are predictable with no exceptional circumstances.

In boolean math it is useful to add and subtract.2 # Non boolean result.
1 # Why not return True here as well?

This is like adding two integer types and getting a float.


There's the possibility of adding two (normally) True values and getting
a False result.
0


Should bool type act like bools as expressed here?

http://www.ee.surrey.ac.uk/Projects/Labview/boolalgebra/

# P1: X = 0 or X = 1
# P2: 0 . 0 = 0
# P3: 1 + 1 = 1
# P4: 0 + 0 = 0
# P5: 1 . 1 = 1
# P6: 1 . 0 = 0 . 1 = 0
# P7: 1 + 0 = 0 + 1 = 1

Table 1: Boolean Postulates

Python's bools work this way if you use 'and' and 'or' and always cast
any non bools to bools first. But it would be easier IMO if bool
operations gave bool results so I wouldn't need to do:

bool_result = a and bool(b)

or

bool_result = bool(a and b)

On one hand these seem like little things, but little things is
sometimes what will bite you the hardest as they are more likely to get
by your guard.

Cheers,
Ron
 
T

Terry Reedy

In practice, how often do you really care that your truth values have the
specific values 0 and 1 rather than anything false and anything true? In
what circumstances?
[/QUOTE]

Another example: you have an exam with N questions and score the answers
True or False. The number correct is the sum of the True/False scores. I
have done things like this in other languages with explicit 1/0 for
True/False.

Terry J. Reedy
 
S

Steven D'Aprano

It's a filter which returns a value or zero depending on conditions a and b.

Ah, that's a good example, thanks, except I notice you didn't actually
cast to bool in them, eg: (min < value < max) * value

You also said:
In boolean math it is useful to add and subtract.
2 # Non boolean result.

I presume you mean Boolean algebra by "Boolean math". I have to disagree
with you there. It is *not* useful to do addition, subtraction,
multiplication or division in Boolean algebra. Those operations aren't
defined on Booleans, because they return results which aren't Booleans. If
you wanted to extend arithmetic operations on Booleans, you would need to
either redefine addition and multiplication as XOR and AND (as
mathematicians do when they extend Boolean algebras to rings), or do your
arithmetic modulo 2.

I'm not saying that it can't be useful to treat Booleans as if they were
the integers 0 and 1, but in mathematics Booleans are abstract values
distinct from the integers (even when they use the symbols 0 and 1) and
the concept "True plus True is two" is meaningless.

It is useful to read the comments here:

http://www.python.org/doc/2.3/whatsnew/section-bool.html

eg "Python's Booleans were not added for the sake of strict type-checking.
A very strict language such as Pascal would also prevent you performing
arithmetic with Booleans, and would require that the expression in an if
statement always evaluate to a Boolean result."
Should bool type act like bools as expressed here?

http://www.ee.surrey.ac.uk/Projects/Labview/boolalgebra/

That is only one possible Boolean algebra, the simplest one. Strictly
speaking, Booleans aren't limited to two values. See
http://en.wikipedia.org/wiki/Boolean_algebra for more detail.

Python's bools aren't Booleans. They are merely aliases for 0 and 1.
 
R

Ron Adam

Steven said:
Ah, that's a good example, thanks, except I notice you didn't actually
cast to bool in them, eg: (min < value < max) * value

It wasn't needed in these particular examples. But it could be needed
if several comparisons with 'and' between them are used.

It just seems odd to me that:

3 and 2 and 1 -> 1
1 and 2 and 3 -> 3

But that may be because I learned boolean algebra as part of an
electronics logic (computer tech) course dealing with gates in the early
80's.

I presume you mean Boolean algebra by "Boolean math". I have to disagree
with you there. It is *not* useful to do addition, subtraction,
multiplication or division in Boolean algebra. ....
(clip)

Yes, but not quite as strict as True BA would be and not with the strict
type checking other languages have.

I'm not saying that it can't be useful to treat Booleans as if they were
the integers 0 and 1, but in mathematics Booleans are abstract values
distinct from the integers (even when they use the symbols 0 and 1) and
the concept "True plus True is two" is meaningless.

It is useful to read the comments here:

http://www.python.org/doc/2.3/whatsnew/section-bool.html

Thanks and the PEP link from there was useful too.

eg "Python's Booleans were not added for the sake of strict type-checking.
A very strict language such as Pascal would also prevent you performing
arithmetic with Booleans, and would require that the expression in an if
statement always evaluate to a Boolean result."

It doesn't need to be that strict. But a few changes could resolve and
reduce the chance of errors especially for beginners, while not limiting
more advanced uses. These would be Python 3k changes most likely if at all.

1. 'and', 'or', and 'not' always return bool values.

Lots of discussion on this one already. From the look of it, I don't
think it will change. But Guido seemed to suggest its possible with the
addition of a trinary operation. But even with that it wouldn't be done
any time soon.

2. bool == value to be the same as bool == value.__nonzero__()

By doing this comparisons with Bool types will match the behavior of if
conditions without restricting if to strictly bools.

3. Math with bools as both arguments should return bools.

This wouldn't prevent adding bools and ints, or doing other operations
on them. But bools would remain bool types until an operation with a
non bool type.

True * True -> True instead of 1
True * False -> False instead of 0
False * True -> False instead of 0
False * False -> False instead of 0
True * 10 -> 10
False * 10 -> 0
True * 0 -> 0

True + True -> True instead of 2 **changed**
True + False -> True instead of 1
False + True -> True instead of 1
False + False -> False instead of 0
True + 1 = 2
False + 0 = 0

-True -> False instead of -1 **changed**
-False -> True instead of 0 **changed**
1-True -> 0
1-False -> 1
2-True -> 1
2-False -> 2
True-1 -> 0
False-1 -> -1

Notice there is only three places above where the change would be
significantly different than now. All other cases would just exchange
True of 1 or False for 0.

Some operation would need a trinary operation in place of the current
and/or.

person.name = (if name then name else 'default name')

Not my favorite syntax, but I can live with it. It might be possible to
have a short form.

person.name = (name else 'default name')

That is only one possible Boolean algebra, the simplest one. Strictly
speaking, Booleans aren't limited to two values. See
http://en.wikipedia.org/wiki/Boolean_algebra for more detail.

I look at those earlier and was surprised at how complex some Boolean
algebra concepts were. Interesting though, and I'll probably go back
and study it a bit more.

Python's bools aren't Booleans. They are merely aliases for 0 and 1.

Yes, and according to the PEP they were introduced to help reduce
errors. ;-)

Cheers,
Ron
 
S

Steve Holden

Ron said:
Steven D'Aprano wrote:




It's a filter which returns a value or zero depending on conditions a and b.

Some examples...

High pass filter:

(value > 0) * value

Low pass filter:

(value < 0) * value

Band pass filter:

(min < value < max) * value


Changing and and or to return bools only, doesn't prevent us from doing
anything we can already do. It just means changing the context to
explicitly return a non bool when you want one as I did here.




We can separate these into two groups...

1. True and false condition testing in which the result of the
condition is not used in any further expressions.

You are correct in this case, it doesn't matter. Any True values would work.

2. Expressions that will be used in a calculation or another
expression.
By which you appear to mean "expressions in which Boolean values are
used as numbers".
This matters because if you aren't careful your results may not be what
you expect.
Well yes, but then I wouldn't necessarily expect good results if I tried
to use a nail file as a tyre-lever, either. If you abuse the intent of
anything sufficiently you should expect trouble. But then, you seem to
like trouble ;-)
But group (2) can also be a part of group (1). So then again it may
matter there too.

This has more to do with clarity and separating function into forms that
have the potential for least surprises. Or to put it another way, forms
that are predictable with no exceptional circumstances.

In boolean math it is useful to add and subtract.
2 # Non boolean result.
Quite.

1 # Why not return True here as well?
Why not return 42? Why not return a picture of a banana?
This is like adding two integer types and getting a float.
No it isn't. It's like trying to multiply April 2 1994 by June 5 2005.
The operation isn't defined. So you choose an arbitrary definition and
say "it would be nice if it worked like this instead of how it actually
does work".

When in fact it doesn't really "work" at all, except for the most
tenuous possible value of "work". It's an accident, because Guido
decided that least code breakage was good when Booleans were introduced.
There's the possibility of adding two (normally) True values and getting
a False result.

0
Which is yet another reason why it makes absolutely no sense to apply
arithmetic operations to Boolean values.
Should bool type act like bools as expressed here?

http://www.ee.surrey.ac.uk/Projects/Labview/boolalgebra/

# P1: X = 0 or X = 1
# P2: 0 . 0 = 0
# P3: 1 + 1 = 1
# P4: 0 + 0 = 0
# P5: 1 . 1 = 1
# P6: 1 . 0 = 0 . 1 = 0
# P7: 1 + 0 = 0 + 1 = 1

Table 1: Boolean Postulates

Python's bools work this way if you use 'and' and 'or' and always cast
any non bools to bools first. But it would be easier IMO if bool
operations gave bool results so I wouldn't need to do:

bool_result = a and bool(b)

or

bool_result = bool(a and b)
You are, of course, ignoring the huge amount of code breakage this
little change you'd find so convenient would cause.
On one hand these seem like little things, but little things is
sometimes what will bite you the hardest as they are more likely to get
by your guard.
Kindly think again about the vast number of times that Python
programmers have relied on the documented property of "and", which says
that it returns the left operand (without evaluating the right one)
unless the left operand is equivalent to False, in which case it returns
the right operand.

You talk about "least surprises" and "in my opinion" as though your
opinions are the only ones that anyone would dream of holding. This is
in itself quite surprising to me.

regards
Steve
 
R

Ron Adam

Steve said:
Ron Adam wrote:
By which you appear to mean "expressions in which Boolean values are
used as numbers".

Or compared to other types, which is common.

Well yes, but then I wouldn't necessarily expect good results if I tried
to use a nail file as a tyre-lever, either. If you abuse the intent of
anything sufficiently you should expect trouble. But then, you seem to
like trouble ;-)

Steve... You seem to agree, but think if any one has a problem with
this, it's a misuse or an abuse. Ok. fair enough.

Its not so much I like trouble, It's more that I tend to get stuck on
contradictions and inconsistencies. They bug me. (sometimes I bug
myself as well) ;-)

Why not return 42? Why not return a picture of a banana?

My question still stands. Could it be helpful if bools were preserved
in more cases than they are now?

My feelings is that it isn't needed as long as you strictly use your own
modules, functions, and classes, and can depend on the behavior you
decide on. But if you are using routines and object written by others,
you can't always be sure if a values should be treated as a bool or as
an integer. Preserving bools when possible, may help in that regard.

No it isn't. It's like trying to multiply April 2 1994 by June 5 2005.
The operation isn't defined. So you choose an arbitrary definition and
say "it would be nice if it worked like this instead of how it actually
does work".

Sure, but if we didn't suggest changes, then nothing would change. We
would just have extremely detailed documentation of everything and
strict rules of use so that we don't misuse them. ;-)

When in fact it doesn't really "work" at all, except for the most
tenuous possible value of "work". It's an accident, because Guido
decided that least code breakage was good when Booleans were introduced.

Good point, but it could be changed in Python 3000 since it will drop
the backwards compatible requirement. This doesn't mean that it should
though. The reasons for doing so still need to be sufficient.

Which is yet another reason why it makes absolutely no sense to apply
arithmetic operations to Boolean values.

Again you agree, yet disagree. Ok, I see your point about breaking
code, but should this be changed, either throw an exception or by
changing to a boolean operation in Python 3000? You already consider it
an abuse.

You are, of course, ignoring the huge amount of code breakage this
little change you'd find so convenient would cause.

I'm not suggesting anything be changed before Python 3000 which has as a
purpose of changing things that haven't been changed because of
backwards compatibility. And I don't think I'm the only one who has
suggested these.

Kindly think again about the vast number of times that Python
programmers have relied on the documented property of "and", which says
that it returns the left operand (without evaluating the right one)
unless the left operand is equivalent to False, in which case it returns
the right operand.

The shortcut behavior would still work, but the returned values would be
either True or False. I agree the above is useful behavior, but I also
see the boolean behavior as desirable. I would like both in clear and
separate contexts if possible.

Guido said the other day on python-dev, he would consider having 'and'
and 'or' return True and False in order to reduce bugs, if a trinary was
also added. It's not as short to type, but the same functionality would
still be present in the language.

Another possibility would be to just add bool operators '||' and '&&'
which would always return True and False and leave 'and' and 'or' as
they are.

You talk about "least surprises" and "in my opinion" as though your
opinions are the only ones that anyone would dream of holding. This is
in itself quite surprising to me.

No, "in my opinion" means exactly that, It's my personal opinion. You
are free and welcome to express "your opinion" as well.

And "least surprises" is, I believe, a good thing to strive for.
Weather or not this would fit that I'm not sure. It's quite possible
that some of these changes would create more surprises not less.

Also these ideas aren't mine, they are fairly standard concepts that
other languages use as well, nothing really new here.

Cheers,
Ron
 
T

Terry Reedy

Steve Holden said:
Which is yet another reason why it makes absolutely no sense to apply
arithmetic operations to Boolean values.

Except for counting the number of true values. This and other legitimate
uses of False/True as 0/1 (indexing, for instance) were explicitly
considered as *features* of the current design when it was entered. The
design was not merely based on backwards compatibility, but also on
actually use cases which Guido did not want to disable. There was lots of
discussion on c.l.p.

Terry J. Reedy
 
T

Terry Hancock

My question still stands. Could it be helpful if bools were preserved
in more cases than they are now?

No. "*" is "multiplication".
The multiplication operator is undefined for boolean values. It only
makes sense if they are interpreted as numbers. As it happens, both
can be coerced to 1, so the result is 1*1. This makes perfect sense
to me.
True

Also makes sense (and this is indeed what happens).

Cheers,
Terry
 
B

Bengt Richter

Except for counting the number of true values. This and other legitimate
uses of False/True as 0/1 (indexing, for instance) were explicitly
considered as *features* of the current design when it was entered. The
design was not merely based on backwards compatibility, but also on
actually use cases which Guido did not want to disable. There was lots of
discussion on c.l.p.
OTOH ISTM choosing to define bool as a subclass of int was a case of
practicality beats purity, but I'm not sure it wasn't an overeager coercion in disguise.

I.e., IMO a boolean is really not an integer, but it does belong to an ordered set,
or enumeration, that has a very useful correspondence to the enumeration of binary digits,
which in turn corresponds to the beginning subset of the ordered set of the non-negative integers.
IIRC Pascal actually does use an enumeration to define its bools, and you get the integer values
via an ord function.

BTW, for counting you could always use sum(1 for x in boolables if x) ;-)

And giving the bool type an __int__ method of its own might have covered a lot of coercions
of convenience, especially if __getitem__ of list and tuple would do coercion (you
could argue about coercing floats, etc. in that context, as was probably done ;-)

Regards,
Bengt Richter
 
R

Ron Adam

Terry said:
No. "*" is "multiplication".
The multiplication operator is undefined for boolean values. It only
makes sense if they are interpreted as numbers. As it happens, both
can be coerced to 1, so the result is 1*1. This makes perfect sense
to me.

I'm not implying it doesn't make sense. It does to me as well. And it
would only make sense to return bools in this case if Python supported
boolean math.

If it did, coercion to ints (or floats) would still occur with mixed
types are used in expressions, but there are some situations where the
bool-bool results differ, so it's most likely an all or nothing move.

Both views are valid and there are benefits to both as well.

True

Also makes sense (and this is indeed what happens).

Only because True is the last value here. ;-)


Cheers,
Terry


Cheers,
Ron
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,264
Messages
2,571,323
Members
48,005
Latest member
ChasityFan

Latest Threads

Top