-3

The background is, there is a method most_common() for the class Counter but not a method least_common(). As an exercise, if I were to add it to the class:

import collections

def least_common(self, n=None):
    return (counter.most_common()[:-n-1:-1] if n != None 
                else counter.most_common()[::-1])

collections.Counter.least_common = least_common

but then it will contaminate the global space by creating an extra least_common function. Is there a way to use an IIFE like in JavaScript, or use a lambda? But lambda doesn't seem to take default arguments?

P.S. and update: Initially, I tried default parameter value like this:

collections.Counter.least_common = lambda self, n=None:
    return (counter.most_common()[:-n-1:-1] if n != None
            else counter.most_common()[::-1])

And it won't work. It seems like I have to move the second line to the end of the : on the first line, and I have to remove the return for it to work.

nonopolarity
  • 146,324
  • 131
  • 460
  • 740
  • 3
    Also *"`lambda` doesn't seem to take default arguments"* is demonstrably false. – jonrsharpe Mar 06 '16 at 16:57
  • 1
    This explains your questions. Basically you can do monkey patching like in your example. And that will change the class, globally. *But* monkey patching should be a last resort, when there is no other useful way to change something. Your example would be done better with a subclass. – Klaus D. Mar 06 '16 at 16:59
  • this may be similar to adding a function that otherwise could exist, a little like polyfill in JavaScript. If we subclass, then doesn't it mean for the exist code base of 200,000 lines, if they ever want to use `least_common()`, then they all have to change from using the class `Counter` to that subclass? – nonopolarity Mar 06 '16 at 17:02
  • Is there a reason why you cannot just make a subclass of `collections.Counter` and overwrite the `least_common()` method? – Patrick the Cat Mar 06 '16 at 17:02
  • 4
    There is no "global space" in Python. If you mean the module globals and that `least_common` annoys you, simply remove it with `del least_common` – Andrea Corbellini Mar 06 '16 at 17:02
  • @AndreaCorbellini There is a global scope in Python, just not the one OP is referring to. – Patrick the Cat Mar 06 '16 at 17:03
  • *"Is it obvious that I am trying to add a `least_common()` to the class `Counter`?"* - yes, but given that you've done that it's not clear what you're actually asking. – jonrsharpe Mar 06 '16 at 17:04
  • @Mai I am not trying to overwrite `least_common()`. It doesn't exist in the `Counter` class as far as I know – nonopolarity Mar 06 '16 at 17:04
  • While I appreciate that you are new to Python, it is clear you need to slow down a little here. Break down your problems into smaller subsets. You basically asked several questions in one here, with a large helping of misunderstandings to boot. – Martijn Pieters Mar 07 '16 at 08:38
  • There are questions here about keeping a namespace clean and how to use keyword arguments in a lambda, how to write a larger lambda. You *really* muddied the waters by having a uncommon end-goal; monkey patching a built-in type too, each separate problem could perhaps have been better illustrated with a simplified example. – Martijn Pieters Mar 07 '16 at 08:42
  • When people are trying to point out things are unclear, try to step back and simplify your problems a bit more. With a series of questions like these the regulars are getting quite frustrated with your style here. Things are *not* as clear to us as they are to you, which is why you see some irritation coming through in comments. – Martijn Pieters Mar 07 '16 at 08:45

2 Answers2

3

If I understand the question correctly, you want to know how to do the monkey-patching in the question without creating a function named least_common in the current namespace.

I think there are three possible solutions. The first is to use a lambda to create the function as an expression as you're assigning it. Python's lambdas are not as fully functioned as some other language's anonymous functions, but they're perfectly good enough for this. As jonrsharpe commented, they can be declared with default arguments, so your monkey patching code could be:

collections.Counter.least_common = lambda self, n=None: (counter.most_common()[:-n-1:-1]
                                                         if n != None else
                                                         counter.most_common()[::-1])

Another solution would be to use the code you're currently using, but then use a del statement to remove the function from the current namespace.

def least_common(self, n=None):
    return ...
collections.Counter.least_common = least_common
del leadst_common

The last option is to define the function and do the monkey patching in a namespace you don't care much about. An obvious one would be the local namespace of some other function, where the local variable name will go away as soon as the function ends:

def do_monkey_patching():
    def least_common(self, n=None):
        ...
    collections.Counter.least_common = least_common

But that's not the only option for an alternative namespace. An important thing to understand in Python is that there isn't one single global namespace. Rather, each module has its own global namespace, which other modules can access by importing it. So, perhaps you do not need to do anything about the extra name in "the global namespace" if its the global namespace of a module that doesn't contain any other important code.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • please read the P.S. in the original question... it seems that lambda can take default argument values but it must be written in a certain way – nonopolarity Mar 07 '16 at 06:21
  • Yes, that's the syntax for `lambda` expressions in Python. A lambda's body is a single expression, which is returned when it is called. You may want to read about the distinction between statements and expressions in Python. It's one of the way's Python is a bit different than other languages. – Blckknght Mar 07 '16 at 07:28
0

I am not sure if this is what you want, but:

from collections import Counter

class MyCounter(Counter):
    def least_common(self, n=None):
        return (counter.most_common()[:-n-1:-1] if n != None 
                    else counter.most_common()[::-1])

try:
    my_counter = MyCounter(some_iterable)
    print list(my_counter.least_common())
except Exception as e:
    # this should really be AttributeError, but I am too lazy to check
    print "bombed"

try:
    normal_counter = Counter(some_iterable)
    print list(normal_counter.least_common())
except Exception as e:
    print "bombed"

Normal counter is bombed and yours isn't. Is this what you want?

=================

In reply to one of the comments below, from collections import Counter is an idiom provided in Python to get references from a module without invoking member search through dot every time you need it. For example, the following codes are equivalent:

Code 1:

from collections import Counter
for it in some_iterables:
    counter = Counter(it)
    # do something to counter

Code 2:

import collections
counter_class_reference = collections.Counter
for it in some_iterables:
    counter = counter_class_reference(it)

However, the following code may have worse performance than the previous two:

import collections
for it in some_iterables:
    counter = collections.Counter(it)

This is because of the membership reference search invoked inside the loop, which is discussed more in https://wiki.python.org/moin/PythonSpeed/PerformanceTips#Avoiding_dots... https://blog.newrelic.com/2015/01/21/python-performance-tips/

Another point worth noticing is that activities in global scope in Python takes a bit longer because of the implementation of CPython. This suggests you wouldn't want to monkey-patch in a loop. More discussion found on Why does Python code run faster in a function?

And I still think not monkey-patching is the best way, if you don't have a problem with just subclassing, which is more pythonic.

Community
  • 1
  • 1
Patrick the Cat
  • 2,138
  • 1
  • 16
  • 33
  • must you `from collections import Counter`? what if you just `import collections`? Then how do you do it? And you are subclassing... what if we don't subclass? – nonopolarity Mar 06 '16 at 17:23
  • 2
    The first line is a short-hand syntax sugar to avoid typing `collections.Counter` every time I need it. You can do it your way. Also if subclassing works, I don't see a reason to avoid this natural solution in OOP paradigm. I suggest you read some `Python` tutorial before continuing your self-study. Even those in Chinese are pretty good. – Patrick the Cat Mar 06 '16 at 17:38
  • what is wrong with using `collections.Counter`? What if I already have a `Counter` class in my code and don't want to import `Counter`? And I know you can subclass, but my question is, if you DO want to add it to the class `collections.Counter`, how would you do it? This may be a special case because it is like you have a `.first()` but not `.last()`, and you are just told to "just reverse the list and do a first() and now you have last()" or subclass it. If we were to add `last()`, maybe even just for see how it would done in a language, maybe just as a 20 line exercise, I don't see why not. – nonopolarity Mar 07 '16 at 03:47
  • Usually, I get quite friendly and intellectual responses when I ask Ruby or CSS questions. There were 2 situations when it was not: when I asked PHP or Python questions. It is almost like asking "how do you grow organic vegetable in your backyard?" -- "WHY WOULD you want to do that? Just go to the supermarket and buy some." Or, "how do I make a steam engine use the steam power better to propel a vehicle?" and "JUST USE you feet. Why would you want to mess with steam?" "WHY WOULD you want to do that, use a horse." – nonopolarity Mar 07 '16 at 03:52
  • In order to have a meaningful discussion, there is a prerequisite on basic knowledge. The fact that you have the questions you are having demonstrates that you are behind the prerequisite line, which suggests you should probably read some `Python` tutorials first. It is not that we are mean to you, just that you really need to equip yourself better before asking those questions. – Patrick the Cat Mar 07 '16 at 03:55
  • Given the evidence of your previous history, I am sure we can have a much better discussion once you read some more. – Patrick the Cat Mar 07 '16 at 04:01
  • It seems that you are the one who doesn't even know that there is no `least_common` in the class `Counter`. You know, for question such as "How do you reverse a string in place in JavaScript?" You can also tell the person who ask the question, "blah blah blah, it seems obvious that you are behind the prerequisite line, blah blah blah, why don't you go read some tutorial first, blah blah blah" – nonopolarity Mar 07 '16 at 04:04
  • @太極者無極而生 It seems like we are getting into a heated discussion somehow. I apologize if I have misunderstood your questions and offended you. I still hold my point of view towards your questions, and sincerely suggest you to read more about `Python` syntax and idioms. – Patrick the Cat Mar 07 '16 at 05:16
  • @太極者無極而生 I hope you are satisfied with @Blckknght 's answer. I wish you the best continuing your journey self-learning `Python` on stackoverflow. – Patrick the Cat Mar 07 '16 at 05:19
  • It is not self-learning Python on StackOverflow. I am reading Fluent Python, Effective Python, Python in a Nutshell, and the online Python docs, etc. It is only when I have questions, I come here to ask. It is not "monkey picking" by the way. It is "monkey patching." Do you even know what it is? – nonopolarity Mar 07 '16 at 05:51
  • @太極者無極而生 Thanks for pointing that out, fixed it! – Patrick the Cat Mar 07 '16 at 05:53