tags:

views:

49

answers:

3

I have some code in a reusable class that modifies some types. Here's a simplified version.

class Foo:
    def __init__(self):
        self.count = 0

    def increment(self):
        self.count += 1

# Add another method outside of the class definition.
# Pylint doesn't care about this, and rates this file 10/10.

Foo.__dict__["current_count"] = lambda self: self.count

In the real code, "current_count" is a variable, not a fixed string, which is why I didn't write:

Foo.current_count = lambda self: self.count # Cannot do in my scenario.

Now, when my clients come to use the new function, Pylint jumps up and down in horror.

import server_api

def main():
    foo_count = server_api.Foo()
    foo_count.increment()


    print foo_count.current_count()
    # Pylint complains here:
    #     E1101:  8:main: Instance of 'Foo' has no 'current_count' member
    # I don't want to have to tell pylint to disable that message in every client.

main()

Every class that uses this new function gets chastised, and I am forced to disable the message in every reference. I would RATHER put some code in the API to tell Pylint to chill when there are unknown references on this class.

Alas, the pylint documentation is... ummm... not of a quality conducive to my understanding, and I have been unable to find any suggestions there.

So boiling it down: Can I tell pylint in my API code to turn off the E1101 rule in relation to this class, whenever a client refers to it? Is there another solution?

A: 

Pragmatically speaking, why the assumption that pylint (or any lint) should be silent? Given a bias between false positives and false negatives, lints should prefer the former. It seems to me that because Pylint expresses its results as a score that people assume it should be maximized, but there is no prize for "winning".

On the other hand, the construct it is complaining about is certainly ugly. I understand that the server_api is simplified for our convenience, but do you really need to muck about with the module namespace? From your client code it appears that the current_count method name is hard coded, why not in the server?

msw
Each false positive is a source of noise, that makes reviewing logs more difficult. If you have many of them, you *do* want to silence as many false positive as possible, in order to focus only on the real issues.
Roberto Liffredo
There's two signal-to-noise ratios to optimise. The first is the positives:false positives amongst the warnings. I like to explicitly turn off a warning on the line that causes it, to show that I have evaluated what pylint has said, and determined that it is not applicable or is a worthwhile tradeoff. That stops me reevaluating the decision again and again, and means that any warnings from pylint need attention.The other is code:pylint pragmas. Additional pragmas make the code less readable and slower to produce. That is what I am working on here.
Oddthinking
In the real code, it is yet another variant on an Enumerated type class. I want to be able to refer to the types as TrafficLight.RED rather than TrafficLight["RED"] or even "RED" for both type-safety and efficiency reasons. Hence the run-time addition of identifiers to the class. (Doing it at "compile" time would violate DRY.)
Oddthinking
+1  A: 

Following one your comments, since you're going for an Enumerated type, why not have a look at this SO question, or this ActiveState cookbook recipe?

Out of personal preference, I'd choose adding the enumerated types into the class, just like one of the answers in SO question (copied shamelessly for context):

class Animal:
   def __init__(self, name):
       self.name = name

   def __str__(self):
       return self.name

   def __repr__(self):
       return "<Animal: %s>" % self

Animal.DOG = Animal("dog")
Animal.CAT = Animal("cat")
Yoni H
The ActiveState cookbook solution is interesting. It uses \_\__getattr\_\_() which seems to shut up Pylint's complaints. I could even uses a passthrough solution, to shut up Pylint while behaving like the built-in.
Oddthinking
The copied code is how I USED to do it, but it fails to meet a lot of my requirements. You repeat yourself (Animal appears 6 times, Dog appears twice. Multiply that out for each type and enum value...) There is no iteration through values. You can't check use "in" (although you can use isinstance, which is similar). There is no way to parse a string into the appropriate type. It'd be nicer if the repr() was eval-able.
Oddthinking
I think you are misinterpreting the intent of "Don't Repeat Yourself". This is not a lexical injunction, for if it were the use of `logging.debug()` would be criminal. It doesn't matter how often `Animal` appears; to adhere to DRY, it is sufficient that Animal.DOG have a single, authoritative representation. In the example it does. If you need `in` and `next` inherit `Animal(list)` or see http://stackoverflow.com/questions/3487434/overriding-append-method-after-inheriting-from-a-python-list/3488283#3488283
msw
I see that Animal.DOG has TWO representations: Animal.DOG and "dog". It has THREE if I am allowed to include "<Animal: dog>". If I decide that CANINE would be a better name, I need to change the declaration in two places.
Oddthinking
I am stilling mulling over the Animal(list) idea. Something to think about. As you suggest in the linked answer, I do prefer object composition over inheritance, and I don't see how an Animal instance (e.g. a Dog) "is-a" list (although, arguably, the Animal *class* itself might be...) Given I have multiple enumerated types in my project, I didn't want to be repeating the idea "This is how Enumerated types are implemented" across my code either. I *have* been using an off-the-shelf implementation for nearly 2 years, but eventually I realised it was making the client code unnecessarily complex.
Oddthinking
@Yoni, +1 for inspiring my answer, and for, quite sensibly, trying to talk me out of some ugly code.
Oddthinking
+1  A: 

Here is my solution inspired by the example in ActiveState cookbook recipe, provided in Yoni H's answer.

To the Foo class, I added this useless __getattr__ method.

def __getattr__(self, name):
    # This is only called when the normal mechanism fails, so in practice should never be called.
    # It is only provided to satisfy pylint that it is okay not to raise E1101 errors in the client code.
    raise AttributeError("%r instance has no attribute %r" % (self, name))

This should be nigh indistinguishable from the previous version. It should not be called in the normal course of events, but it is enough to persuade pylint to remain quiet about this error.

p.s. You are allowed to grumble that this code isn't very pretty. I share that opinion. But I think its benefits for the client outweigh its code smell.

Oddthinking