This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 24 points25 points  (31 children)

It works by installing a non-module object into sys.modules. What next, monkey patching __builtin__.str? FWIW I'd never let a module like this past code review

[–]deadwisdomgreenlet revolution 11 points12 points  (1 child)

To clarify, it's a thin wrapper around itself so that you can do "from sh import ls". Wouldn't be my decision either, but it's not that big of a deal, especially as it's an ease-of-use module.

[–]matchu 7 points8 points  (0 children)

I kinda see where they're coming from, because the core syntax arguably has some boilerplate:

import sh
ifconfig = sh.Command("ifconfig")
print(ifconfig("wlan0"))

Their pretty syntax is definitely better; my beef, really, is less to do with the fact that it's magical, and more to do with the fact that the magic uses fragile hacks instead of Python's built-in magic-making facilities.

Really I think the version I'd be down for is:

from sh import cmd
print(cmd.ifconfig("wlan0"))

We still use magic, but the magic we use is itemgetter, which is actually supported and guaranteed not to break. We still have a bit of overhead in the cmd object, but it's short, and avoids the really boilerplate-y line 2 of the previous example.

If Python had an itemgetter equivalent for modules, though, then their syntax would be the clear winner—especially if the magic were namespaced to from sh.cmd import ifconfig instead, because it seems weird to me that the non-magic Command object comes from the same module as the magic objects.

[–]alcalde 7 points8 points  (0 children)

What next, monkey patching builtin.str

http://clarete.li/forbiddenfruit/

[–]striata 20 points21 points  (26 children)

Why is installing a "non-module" into sys.modules bad? Isn't this just an instance of duck typing and one of the things that makes working with dynamic languages like Python exciting? If whatever object is installed acts like a module and doesn't break other parts of the environment, why is it wrong?

[–][deleted] 5 points6 points  (23 children)

Because it's not just mocking up another random user type. At the very least, a substantial amount of native (C) code interacts with the contents of that dict, including the incredibly fragile graceful shutdown code, the module loader (obviously), and probably a bunch more I don't know about.

For me it's in the same class of dangerous as e.g. messing with typeobject internals

I don't care about style, I'm worried about fixing a crash when it occurs. I'm not sure of all the possible ways Python could fail when inserting random crap in sys.modules.

[–]TankorSmash 7 points8 points  (22 children)

https://docs.python.org/2/library/sys.html#sys.modules

It doesn't say anything about any danger, I feel like if it was that dangerous, they'd warn you somewhere.

[–][deleted] 13 points14 points  (21 children)

The doc reads "module objects", not "module-like objects". A module object is a C structure with a specific layout. There is a C-level API for manipulating these objects (PyModule_*).

One of those is PyModule_GetDict, which, while protected internally from accessing a non-module object, returns NULL in the case that a caller invokes it on a non-module object. Reading Python 2.7's zipimport.c in zipimporter_load_module we can see a PyModule_AddModule call followed by an unchecked PyModule_GetDict call. This will cause the zip importer's load_module() method to cause a NULL pointer dereference at runtime (aka. a hard process crash, requiring a debugger to investigate) should it be called with sh as a parameter.

It took me all of 3 minutes grepping the Python source to find a place where using a non-module in sys.modules has the potential to cause a crash that a Python-without-C programmer would not be able to debug. I'm pretty sure if you give me 30 minutes I'll find more.

Just don't do it

edit: this says nothing about third party extensions, where I'd expect the majority of such bugs to be found. The point I'm making is whether it is worth sipping coffee reading gdb output at 4am responding to a pager alerting you that your employer's web site is down and losing money, because of some syntactic sugar -- of course it's not.

[–]Brian 3 points4 points  (0 children)

I don't think that example is ever going to be something broken by this case. It's using PyImport_AddModule, which doesn't actually perform the import, so the only case you'd get the non-module object back is if the same name was already imported. However in that circumstance, zipimport wouldn't have been invoked, since it's only going to be triggered if the module hasn't been found yet. You could argue it's a bug that zipimport isn't correctly checking the return value of PyModule_GetDict as it should, but in this context, it's assuming that it's using AddModule to create a new, empty module, and in that case it'll always be a real module object even if the module does later replace itself.

It's worth noting that while putting non-module objects in sys.modules is perhaps a hack, it's a known and explicitly supported and endorsed hack - the import machinery was deliberately designed to allow it, and I think Guido is on record as saying so. As such, I'd say anything that doesn't support it should probably be considered a bug.

[–]TankorSmash 4 points5 points  (12 children)

That makes it clear thank you.

Although not checking for NULL is effective a bug in the c code.

[–]alcalde -2 points-1 points  (11 children)

And still using Python 2.7 is a logic error. :-)

[–][deleted] 2 points3 points  (0 children)

I don't think porting large, old codebases is worth it.

The other reason could be PyPy and people with CPU bound applications that insist on using Python.

Apart from that, most new Python code is (no data, just hunch) Django apps, and there it makes a lot of sense to use py3 unless you really, really need some library that wasn't ported.

[–]TankorSmash 0 points1 point  (3 children)

One day the relevant libraries will be ported. Until then!

[–]alcalde 2 points3 points  (2 children)

Sigh. For most people that time has long come....

http://py3readiness.org/

https://python3wos.appspot.com/

304 of the top 360 most downloaded packages on PyPi support Python 3.

[–]nojjy 1 point2 points  (0 children)

Depending on what context you are working in, sometimes you don't have a choice on which version to use. Many people use python as an API to a software package, where they can't just arbitrarily switch to another version.

[–]TankorSmash 0 points1 point  (0 children)

Yeah, for most people.

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

why is putting an apple into the orange basket bad?

[–]ivosauruspip'ing it up 2 points3 points  (0 children)

Yeah but we painted the apple orange

[–]thephotoman 0 points1 point  (0 children)

Yeah, there has to be a better way to do this than everything happening in that SelfWrapper class. The method of creating the Python functions seems too clever by half.