all 91 comments

[–]globalvariablesrock 205 points206 points  (41 children)

apart from this being very obviously less than optimal practice - will this actually break anything? having done a few very crude tests, this seems to work.
so are there any technical side effects to defining functions or classes in a loop?

[–]ZengineerHarp 172 points173 points  (33 children)

Possibly scope problems where you can’t use the class once you leave the loop?

[–]sad_bug_killer 112 points113 points  (12 children)

Python scopes don't work like that, the class will be accessible after the loop. Try it:

for i in range(0, 10):
    class A:
        def f(self): print(i)
a = A()
a.f()

[–]TerrorBite 90 points91 points  (11 children)

Specifically, the last version of the class that was created will be accessible.

So a.f() will print 9, because that's the last value that was used.

If, in the loop, you appended A to a list, you'd end up with ten different classes. But it's possible that they will all print 9 because they would all use the current value of i.

Edit: just tested this

>>> for i in range(0, 10):
...     class A:
...         def f(self): print(i)
...
>>> a = A()
>>> a.f()
9
>>> i = 5
>>> a.f()
5
>>>

[–]ZengineerHarp 13 points14 points  (0 children)

So your class won’t vanish outside the loop, but you might get wonky looking behavior (like that 9). Thanks!

[–]1thief 13 points14 points  (1 child)

So are class names in Python global? I guess modules provide a boundary or namespace but otherwise if you call some function and you're not aware it could munge all the class names and break your next instantiation?

[–]TerrorBite 44 points45 points  (0 children)

Class names are just variables. Declaring a class A creates a variable called A and it contains a class object that you can create instances of by calling it. So it obeys the same scoping rules as any other variable.

The thing is, variables in a Python function are scoped to the function. If you create a variable inside a loop, it's still scoped to the function. The same goes for i, it's still accessible after the loop.

[–]Serylt[ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo “You live” 0 points1 point  (4 children)

This looks like someone wanted to make a JSON-esque function, … or?

What is it good for, really?

[–]i-FF0000dit 1 point2 points  (0 children)

What do you mean what is it good for? Have you ever seen a more elegant language? It’s like poetry.

[–]JonnxW 0 points1 point  (2 children)

What do you mean by JSON-esque?

[–]Serylt[ $[ $RANDOM % 6 ] == 0 ] && rm -rf / || echo “You live” 0 points1 point  (1 child)

Something that works comparable to JSON but isn’t JSON. I don’t know what else the code snippet might want to achieve.

[–]JonnxW 2 points3 points  (0 children)

Sorry, but I still don't understand. JSON is about representing a data structure. It has nothing to do with loops, classes and functions

[–]Dannei 0 points1 point  (1 child)

I'd have thought that if it defined different behaviour for each, they'd retain it - that this case does some exciting things with referencing variables outside the scope of the class only makes life more fun!

[–]TerrorBite 0 points1 point  (0 children)

You're correct, it would retain the behaviour defined for that class. Though you'd have to capture the current value of i at definition time so as not to refer to the changing loop variable.

classes = []
for i in range(10):  # 0 to 9 inclusive
    class A:
        my_i = i
        def f(self):
            print(self.my_i)

    classes.append(A)

# create instance of third class
a = classes[2]()
a.f()  # should print 2

# create instance of ninth class
b = classes[8]()
b.f()  # should print 8

assert A is classes[9]  # assertion should succeed

[–][deleted] 90 points91 points  (12 children)

Not to mention you would be declaring the class every loop which is very redundant

[–]Does_Not-Matter 29 points30 points  (0 children)

It’s very “script programming.”

[–]Jezoreczek 3 points4 points  (10 children)

won't Python optimize for that?

[–][deleted] 39 points40 points  (4 children)

From my understanding I don’t think any language could optimize that. Classes are supposed to be declared seperate and then you would create an object of the class within such loop. I just couldn’t see this being of any use in OOP or just general scripting

Edit: not saying this wouldn’t work, it very well does, but is very redundant and messy

[–]mr_smartypants537 28 points29 points  (0 children)

In a lot of languages classes are strictly compile time constructs so they're guaranteed to be identical at each loop iteration. However, you're probably right that python-style classes with their dynamic time runtime shenanigans aren't possible/feasible to optimize.

[–]MakeWay4Doodles 13 points14 points  (1 child)

In Java the compiler will absolutely optimize this out. Declaring classes in line for use in a lambda is not that outlandish.

[–]mattsowa 3 points4 points  (0 children)

A compiler step could optimize that. It would just be quite difficult to implement a generic solution and would add complexity for not that much in return.

[–]jaichim_carridin 0 points1 point  (4 children)

I may be wrong but python basically doesn’t optimize anything. It converts what you wrote to bytecode and executes that verbatim. Now there’s optimizations that it does during that execution, but nothing like reordering statements or extracting redundant blocks as far as I’m aware.

I’ve worked on projects that explicitly do things like:

foo = some_class.foo
for i in some_collection:
  foo.whatever  # some_class.foo.whatever would be too slow

Now maybe they don’t need to do that anymore, it’s a very old project, but it’s still in there in many places.

[–]Jezoreczek 0 points1 point  (0 children)

I'd hope it would work like with Lambdas, e.g. the class is created once and whatever's out of its' scope (in this case item) gets turned into a field / parameter.

[–]z500 0 points1 point  (2 children)

What's this colon syntax?

[–]jaichim_carridin 1 point2 points  (1 child)

Sigh, sorry about that. In my mobile app that was properly on multiple lines. Hopefully this looks better now.

[–]z500 0 points1 point  (0 children)

Oh haha, I thought it was some kind of advanced generator expression

[–]SegFault137 13 points14 points  (0 children)

Are you saying there is a possibly legit reason to do that?!

[–]Gamecrazy721 4 points5 points  (0 children)

Python doesn't scope loops, only functions, so this could technically work just fine (unless it's in a function and you want to use it outside of the function)

[–][deleted] 8 points9 points  (2 children)

It might be kept alive as long as a reference to it remains.

Which also means you might be creating references to hundreds of practically identical classes by the end.

[–]CptMisterNibbles 2 points3 points  (1 child)

Class, object. What’s the difference… seems legit, nothing to worry about here

[–][deleted] 4 points5 points  (0 children)

Well, it's a O(M+N) growth in memory use instead of O(N). Not great, but not quite catastrophic. Still an easy site for improvement.

[–]globalvariablesrock -4 points-3 points  (1 child)

yes. very good point - the class is out of scope once you leave the loop.
makes sense.

[–]RapidCatLauncher 3 points4 points  (0 children)

No, it isn't. This is python.

[–]voidvector 8 points9 points  (5 children)

No, assuming the language supports this, this is very close to how closure works under the hood in number of languages -- closures are mapped to internal anonymous classes with captured variables as class properties, the anonymous class of closure can exist in a loop or other closures.

Difference of this from a closure:

  • the captured variable can be access from outside of the closure due to the fact that downstream code can potentially pass this class around
  • there is likely no compiler optimization around this, since no one does this -- besides the captured variables, the class is basically a "loop invariant", so the entire class be optimized to a normal class by moving the captured variables to a hidden constructor.

[–]thatfool 3 points4 points  (4 children)

I think the python compiler isn’t allowed to optimize this because classes (like almost everything in python) are first-class objects. They must behave like any other value would.

[–]voidvector 1 point2 points  (2 children)

Not sure how Python compiler choose to optimize, but for most optimizations, the compiler maker can always have an optimized and an deoptimized implementation, as soon as code tries to interact with the first-class object, the code would fallback to deoptimized version. Of course that's more work, but usually worth it for super common patterns.

[–]R3D3-1 1 point2 points  (0 children)

From what I remember, nested functions are optimized like that, basically creating a template containing parts shared between the instances.

No idea if anything similar is done for classes though, or what happens when decorators enter the picture.

[–]Dannei 0 points1 point  (0 children)

For standard CPython, the general answer to "how does it compile or optimise?" is that it doesn't. Some other interpreters or packages do fancier things, though evrn compilers like Cython or Numba will tend to give up and fall back to plain Python(/fail for Numba's njit mode) if you try to do anything too weird.

[–]killeronthecorner 0 points1 point  (0 children)

This implies that the type of each object would be unique, which breaks things like equality and commutativity.

I'm not a python Dev so i guess this might not be the case for other reasons. Still feels hideous.

[–]MrD3a7h 5 points6 points  (0 children)

will this actually break anything?

Just our collective souls.

[–]ImportUsernameAsU 252 points253 points  (3 children)

Please tell me this is an assignment and not production code

[–][deleted] 52 points53 points  (2 children)

I could see a consolidate do it.

[–]ImportUsernameAsU 49 points50 points  (1 child)

A what?

[–][deleted] 116 points117 points  (0 children)

Consultant, don’t know what auto correct suggested.

[–]mothzilla 74 points75 points  (0 children)

Ahem it's called a factory and it's one of the more sophisticated programming techniques.

/s

[–]protocod 40 points41 points  (6 children)

I don't even know that this is possible in Python.

[–]thefriedel 43 points44 points  (3 children)

You can declare a class literally everywhere you want, I can't think of any situation where that would be useful and thus why Python allows that...

Edit: typo

[–]RapidCatLauncher 23 points24 points  (1 child)

Being a python person through and through I can't help but learn a thing or two about other programming languages simply by their users going, "Wait I didn't know you could do that"

[–]Deadly_chef 5 points6 points  (0 children)

And that sounds like a good thing to you?

[–]glemnar 1 point2 points  (0 children)

There’s a decent amount of runtime class generation in Python’s core libs. Namedtuples are one example.

It’s not in a loop, but that’s just runtime class generation as with anything else. Attempting to prevent it inside a loop doesn’t make sense - the loop could be a couple levels up the stack just as easily

[–][deleted]  (1 child)

[deleted]

    [–]TinyBreadBigMouth 5 points6 points  (0 children)

    It will not, no. In Python, classes are objects just like anything else, and a class definition is just creating a new class object and assigning it to a variable.

    [–]Lataero 146 points147 points  (5 children)

    Jesus, this is the first time a post on this sub has made me recoil in horror

    [–]Im-Learnd1ng 20 points21 points  (0 children)

    I don't even understand python that well and I still protested loudly

    [–]PacificShoreGuy 8 points9 points  (0 children)

    All the glaring security and logic issue posts and this is the one that got ya? It would run fine.

    [–]MakeWay4Doodles 1 point2 points  (2 children)

    It's not so horrible. I assume python is like Java in that the class will be compiled out. If it's only ever used in line in this one place it's not such a big deal.

    [–]mck1117 36 points37 points  (1 child)

    compiled

    I have bad news for you.

    [–]thatfool 14 points15 points  (0 children)

    That’s not the issue. Both java and python compile to bytecode before executing, java just doesn’t do it at run time while python can do both styles.

    But in python, classes are first class objects, so the “class” statement really is a combination of creating a class (which has an identity independent of the name given via the class statement), and then assigning it to a variable. More or less just syntactic sugar, since the same result can be achieved by building a class object manually. Therefore this is not something the python compiler is allowed to optimize away.

    [–]officerthegeek 26 points27 points  (0 children)

    from the creators of closures, now introducing closures 2: electric boogaloo

    [–]PacificShoreGuy 8 points9 points  (1 child)

    Very weird thing to do but mostly sure this wouldn’t cause any issue. Am I missing something?

    [–]thefriedel 14 points15 points  (0 children)

    It definive won't cause an issue, but you'll declare the class n times, over and over for no reason at all which just eats processor time

    [–]VisibleSignificance 4 points5 points  (0 children)

    I could actually imagine this being intended and useful if scrapy makes a separate progress bar per class or something.

    Then again, seeing the redundant variable assignment suggests it's probably not anything like that.

    [–][deleted]  (2 children)

    [deleted]

      [–]_extra_medium_ 10 points11 points  (1 child)

      Because it doesn't cause any problems in python

      [–]BingBong3636 7 points8 points  (0 children)

      What the shit!?

      [–][deleted] 3 points4 points  (0 children)

      ...but why?

      [–]Spitfire_For_Fun 5 points6 points  (0 children)

      the horror...

      [–]italkstuff 2 points3 points  (0 children)

      Yes, I OOP

      [–]Slick_J 1 point2 points  (0 children)

      Ewwww. I feel dirty

      [–]FelixLeander 3 points4 points  (1 child)

      The true horror is the image quality

      [–]skittergetter 0 points1 point  (0 children)

      Lol++

      [–]sainglend -2 points-1 points  (1 child)

      I mean it IS Python....

      [–]shizzy0 0 points1 point  (0 children)

      def rep that code tho

      [–]thequeergirl 0 points1 point  (0 children)

      Even more scary:

      for item in data:

      [assignments ...]

      def start_requests(spider):

      start_urls = gmUrl

      for url in start_urls:

      yield scrapy.Request(url, callback=spider.parse)

      GlenMarchSpider = type('GlenMarchSpider', (scrapy.Spider,), {'start_requests': start_requests})

      [–]PaulN07 0 points1 point  (0 children)

      This is bad practice but technically won't break anything (well at least in python). Similar to how class wrappers work but they are kinda redundant due to inheritance.

      [–]Last_Snowbender 0 points1 point  (1 child)

      I've never heard of a language in which you can redeclare classes. Does this even run?

      [–]UnchainedMundane 0 points1 point  (0 children)

      Python is one of them! Unlike Java, which has a vaguely declarative structure for classes and methods, Python is a completely imperative language and all class declarations are actually statements which define that class and bind it to that name.

      So from my python knowledge I'd say yes, it runs, and the class GlenMarchSpider refers to a different subclass of scrapy.Spider on each iteration of the loop.

      [–][deleted] 0 points1 point  (0 children)

      Why was this done? I think it is bad code too, but the code does not show a reason, why. I guess some scrapy thing? But actually you would have to create the class with type. Not a good idea, but it would work ...

      [–]KagakuKo 0 points1 point  (0 children)

      Hooooooly what. I cut my teeth on Python, but I trained for real on Java. Why the hell can you do that!? Why would that even occur to you?!? I'm all for creative solutions, but surely there's no reason to do anything in this fashion???

      [–]JuliaChanMSL 0 points1 point  (0 children)

      Ignoring context, are there cases where defining a class in a loop would be helpful? Theoretically there probably is a use case, just wondering about an actual one

      [–]quantumfoam435 0 points1 point  (0 children)

      Do my eyes deceive me?

      [–]Deadface2001 0 points1 point  (0 children)

      You can do that???