0

I want to write a class, an instance of which should be usable as an argument of os.mkdir(). Is there a magic method, which I could add to my class, so that this would work? The following implementation with __unicode__():

main.py:

import os

class MyClass(object):
    def __init__(self, string):
        self.__string = string
    def __unicode__(self):
        return unicode(self.__string)

obj = MyClass("/tmp/dir")
print unicode(obj)
os.mkdir(obj)

leads to the following error:

/tmp/dir
Traceback (most recent call last):
  File "main.py", line 11, in <module>
    os.mkdir(obj)
TypeError: coercing to Unicode: need string or buffer, MyClass found

I want to use instances of MyClass in contexts where otherwise a str or unicode is expected. E. g. in order to make "abc" + MyClass("def") work, I can implement the __radd__() magic method in MyClass.

Edited: New code example to explain my intention (MyClass from above corresponds to StringRef below):

import os

class StringRef(object):
    def __init__(self, string):
        self.__string = string
    def set_value(self, value):
        self.__string = value
    def __str__(self):
        return str(self.__string)
    def __repr__(self):
        return repr(self.__string)
    def __unicode__(self):
        return unicode(self.__string)
    def __add__(self, other):
        return self.__string + other
    def __radd__(self, other):
        return other + self.__string

class SomeClass(object):
    def __init__(self, directory):
        self.__directory = directory
    def use_dir1(self):
        print "directory: %s" % self.__directory
    def use_dir2(self):
        print "subdirectory:", self.__directory + "/subdir"
    def use_dir3(self):
        os.mkdir(self.__directory)
        os.rmdir(self.__directory)

print "* old *"
directory = "/tmp/dir1"
obj = SomeClass(directory)  # more objects can be created like this
obj.use_dir1()
directory = "/tmp/dir2"  # has no effect on the created objects
obj.use_dir1()

directory = "/tmp/dir1"
obj = SomeClass(directory)
obj.use_dir2()
directory = "/tmp/dir2"
obj.use_dir2()

directory = "/tmp/dir1"
obj = SomeClass(directory)
obj.use_dir3()
directory = "/tmp/dir2"
obj.use_dir3()


print "* new *"
directory = StringRef("/tmp/dir1")
obj = SomeClass(directory)  # more objects can be created like this
obj.use_dir1()
directory.set_value("/tmp/dir2")  # has effect on all created objects
obj.use_dir1()

directory = StringRef("/tmp/dir1")
obj = SomeClass(directory)
obj.use_dir2()
directory.set_value("/tmp/dir2")
obj.use_dir2()

directory = StringRef("/tmp/dir1")
obj = SomeClass(directory)
obj.use_dir3()
directory.set_value("/tmp/dir2")
obj.use_dir3()

Output:

* old *
directory: /tmp/dir1
directory: /tmp/dir1
subdirectory: /tmp/dir1/subdir
subdirectory: /tmp/dir1/subdir
* new *
directory: /tmp/dir1
directory: /tmp/dir2
subdirectory: /tmp/dir1/subdir
subdirectory: /tmp/dir2/subdir
Traceback (most recent call last):
  File "main.py", line 65, in <module>
    obj.use_dir3()
  File "main.py", line 27, in use_dir3
    os.mkdir(self.__directory)
TypeError: coercing to Unicode: need string or buffer, StringRef found

2nd Edit: StringRef(unicode) avoids TypeError but does not create /tmp/dir2:

import os

class StringRef(unicode):
    def __init__(self, string):
        self.__string = string
    def set_value(self, value):
        self.__string = value
    def __str__(self):
        return str(self.__string)
    def __repr__(self):
        return repr(self.__string)
    def __unicode__(self):
        return unicode(self.__string)
    def __add__(self, other):
        return self.__string + other
    def __radd__(self, other):
        return other + self.__string

class SomeClass(object):
    def __init__(self, directory):
        self.__directory = directory
    def use_directory(self):
        os.mkdir(self.__directory)

directory = StringRef("/tmp/dir1")
obj = SomeClass(directory)
obj.use_directory()
directory.set_value("/tmp/dir2")
obj.use_directory()

Output:

Traceback (most recent call last):
  File "main.py", line 29, in <module>
    obj.use_directory()
  File "main.py", line 23, in use_directory
    os.mkdir(self.__directory)
OSError: [Errno 17] File exists: '/tmp/dir1'
Makoto
  • 104,088
  • 27
  • 192
  • 230
user
  • 1
  • 2
  • you cannot create a directory if it already exists – Padraic Cunningham Feb 26 '15 at 11:56
  • I should have explained what the output of the second editing means: Line 29 in main.py is the second call of obj.use_directory() (not the first one). In this call /tmp/dir2 should be created, but the argument passed to os.mkdir() according to the error message "File exists: '/tmp/dir1'" shows that somehow (can anyone explain why?) is again /tmp/dir1. – user Feb 26 '15 at 22:24
  • What is still unclear to me is, what happens inside os.mkdir() (there is no Python source code for this method)? If the implementation of mkdir(path) would do something similar to unicode(path), then the __unicode__() method from my StringRef class should have returned its attribute __string. – user Feb 26 '15 at 22:30
  • are you expecting `directory.set_value("/tmp/dir2")` to change the value of the string passed to SomeClass? – Padraic Cunningham Feb 26 '15 at 22:53
  • SomeClass has a reference to the StringRef directory. directory.set_value("/tmp/dir2") changes directory.__string. I'm expecting that the call of os.mkdir(self.__directory) results inside the implementation of mkdir() in a call of one of the magic methods of StringRef. If I know which magic method this is, than I can override it to return directory.__string). – user Feb 27 '15 at 00:22
  • whoa! that StringRef of yours is definitely missing the point. Just leave all those things to the superclass. As to not blowing up on the mkdir, that is neither os.mkdir, nor StringRef's business. you can wrap your call in a "if not os.path.exists()" or do a try/catch, either approach works. btw, am guessing os.mkdir is out-sourced to... the OS itself via a C-level call. – JL Peyret Feb 27 '15 at 00:55
  • a long time ago, someone reviewed the failed efforts at writing the Chandler PIM in Python. He said something like "the problem of the Chandler team is that they were using Python to write Java code, rather than writing code as Python". don't overcomplicate your implementation. – JL Peyret Feb 27 '15 at 00:59
  • last, but not least, strings are immutable, so having a set_value on a string instance doesn't make a lot of sense. create a new StringRef for /tmp/dir2 and use that instead. – JL Peyret Feb 27 '15 at 01:06
  • @JLPeyret: I cannot leave the magic methods to the superclass, because the superclass does not use self.__string and has no set_value() functionality. I don't want to have an immutable string or multiple instances of StringRef, but just one instance the string value of which can be changed centrally, so that the next usage of the StringRef instance in a context that expects a string would return the new value. – user Mar 02 '15 at 13:28

3 Answers3

1

since you want this puppy to behave like a string, sorry unicode, just subclass it from unicode (the inbuilt unicode class) instead of object.

#as per Padraic's remark that string <> unicode
#class MyClass(str):

class MyClass(unicode):
    pass


obj = MyClass("/tmp/dir")
print unicode(obj)
os.mkdir(obj)

#surprised that assigning a new attribute here works
#I woulda thought MyClass
#would be using __slots__.  oh well, better for you.
obj.foo = 1
print "obj.foo:%s" % (obj.foo)
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • no, didn't think it was very important to mkdir. oh, well, easily remedied anyway. fwiw, I started with your exact same answer, then I thought "why not subclass the built-in?" – JL Peyret Feb 25 '15 at 23:28
  • I did not read too far into the question, I just saw a method not being called and the OP expecting something actually magic to happen. I deleted my answer so you might explain the difference between actually calling a method and just having the method in your class. – Padraic Cunningham Feb 25 '15 at 23:36
  • you shouldn't have. there might be many cases in which subclassing the unicode or str is not appropriate and your answer would be more suitable under those conditions. – JL Peyret Feb 25 '15 at 23:37
  • To be honest I don't fully get what the OP is trying to do. – Padraic Cunningham Feb 25 '15 at 23:38
  • @PadraicCunningham: If I had to guess, I'd say OP is creating a bad copy of 3.4+'s `pathlib`. – Kevin Feb 26 '15 at 00:12
  • @PadraicCunningham: Sorry, I should make it more clear what I'm trying to do: – user Feb 26 '15 at 09:17
  • In an existing code base, where a certain variable is currently passed into lots of objects as constructor argument, I want to replace the value of this variable, which is currently of type str by a value of type MyClass, so that current usages of this variable ("%s" % myvar, myvar + "somestring" and os.mkdir(myvar)) still work, but additionally MyClass shall be something like a string reference: – user Feb 26 '15 at 09:27
  • After creating all those objects where myvar was passed into their constructors, I want to be able to centrally change the string value of myvar, so that subsequent method calls on those objects will all use the new string value. – user Feb 26 '15 at 09:28
  • @JLPeyret: Thanks for the idea of inheriting MyClass from unicode/str. This change applied to the StringRef example code solves my problem. – user Feb 26 '15 at 10:25
  • @JLPeyret: Update: I have tried the idea of inheriting StringRef from str/unicode. It avoids the TypeError, but mkdir() does not use StringRef.__string. – user Feb 26 '15 at 10:41
  • well, if you want to pass in a variable and use it in-place, but switch it around then behind the scenes then you probably can't use a subclass of string, because strings are immutable. that's why Padraic shouldn't have deleted his answer. question: do _you_ have access to modify the execution context of os.mkdir()? if so, just use some variation of os.mkdir(obj()) or os.mkdir("%s" % obj). – JL Peyret Feb 27 '15 at 03:56
  • i know that SO prefers to see code, but in this case, maybe you should instead start out by describing what you are trying to solve and what your constraints are. because my answer about subclassing string was not predicated on what seems to be your actual requirements, although it meets your original formulation. if you don't control how os.mkdir is called say so. and if you want to switch values around behind the scenes and have os.mkdir "fooled", say so. best done in the actual question, not in the comments. it might be that monkey-patching os.mkdir is a better approach, not sure. – JL Peyret Feb 27 '15 at 04:02
  • @JLPeyret: Yes, changing the execution context of os.mkdir() and all other kind of usages of "directory" would be the best option, because it would make the StringRef implementation trivial. But my question was exactly about avoiding that effort, because the number of locations where "directory" is used is high, so I wanted to know, whether it could be done by replacing the str type of directory by a custom class (StringRef). – user Mar 02 '15 at 13:40
  • Monkey patching might be less effort than changing all execution contexts, because it would need a patch only for each type of execution context (os.mkdir(), "string" + StringRef, StringRef + "string", ...). And I don't know if monkey patching can achieve what __add__() and __radd__() can do. – user Mar 02 '15 at 13:40
0

what about this?

(file is testmkdir2.py) import os

class MonkeyPatch(object):
    def __init__(self):
        self.func = os.mkdir

    def __call__(self, sortastring):
        if hasattr(sortastring,"value"):
            self.func(sortastring.value)
        else:
            self.func(sortastring)

mk = MonkeyPatch()

os.mkdir = mk

os.mkdir("/tmp/foo")

class Dummy(object):
    pass

sneakystring = Dummy()

sneakystring.value = "/tmp/foo2"

os.mkdir(sneakystring)

sneakystring.value = "/tmp/foo3"

os.mkdir(sneakystring)

$ ls -l /tmp/ | grep foo $

$python testmkdir2.py

$ ls /tmp/ | grep foo

drwxr-xr-x 2 jluc wheel 68 26 Feb 20:19 foo

drwxr-xr-x 2 jluc wheel 68 26 Feb 20:19 foo2

drwxr-xr-x 2 jluc wheel 68 26 Feb 20:19 foo3

JL Peyret
  • 10,917
  • 2
  • 54
  • 73
0

I think the cleanest way to have a string reference that can have its value changed after having been passed to various objects is to use an object containing the string value and changing the execution contexts of the string reference to be different from how it would look like, if a string would be used instead of the string reference: The new execution contexts would be os.mkdir(s.value), "string" + s.value, s.value + "string" instead of os.mkdir(s), "string" + s, s + "string"):

class StringRef(object):
    def __init__(self, value):
        self.__value = value
    @property
    def value(self):
        return self.__value
    @value.setter
    def value(self, val):
        self.__value = val

class SomeClass(object):
    def __init__(self, s):
        self.__s = s
    def meth(self):
        print self.__s.value  # new execution context here

directory = StringRef("/tmp")
obj = SomeClass(directory)
obj.meth()
directory.value = "/var"
obj.meth()
user
  • 1
  • 2