Re: [Python-Dev] defaultdict proposal round three
![](https://secure.gravatar.com/avatar/76825c36c7b6ffacac29fae9be486499.jpg?s=120&d=mm&r=g)
Greg Ewing wrote:
In other words, just because A inherits from B in Python isn't meant to imply that an A is a drop-in replacement for a B.
Hmm - this is interesting. I'm not arguing Liskov violations or anything ... However, *because* Python uses duck typing, I tend to feel that subclasses in Python *should* be drop-in replacements. If it's not a drop-in replacement, then it should probably not subclass, but just use duck typing (probably by wrapping). Subclassing implies a stronger relationship to me. Which is why I think I prefer using a wrapper for a default dict, rather than a subclass. Tim Delaney
![](https://secure.gravatar.com/avatar/3acb8bae5a2b5a28f6fe522a4ea9b873.jpg?s=120&d=mm&r=g)
Delaney, Timothy (Tim) wrote:
However, *because* Python uses duck typing, I tend to feel that subclasses in Python *should* be drop-in replacements. If it's not a drop-in replacement, then it should probably not subclass, but just use duck typing (probably by wrapping).
Inheritance is more about code reuse than about polymorphism. Regards, Martin
![](https://secure.gravatar.com/avatar/72ee673975357d43d79069ac1cd6abda.jpg?s=120&d=mm&r=g)
Delaney, Timothy (Tim) wrote:
However, *because* Python uses duck typing, I tend to feel that subclasses in Python *should* be drop-in replacements.
Duck-typing means that the only reliable way to assess whether two types are sufficiently compatible for some purpose is to consult the documentation -- you can't just look at the base class list. I think this should work both ways. It should be okay to *not* document autodict as being a subclass of dict, even if it happens to be implemented that way. I've adopted a convention like this in PyGUI, where I document the classes in terms of a conceptual interface hierarchy, without promising that they will be implemented that way. Greg
![](https://secure.gravatar.com/avatar/463a381eaf9c0c08bc130a1bea1874ee.jpg?s=120&d=mm&r=g)
Greg Ewing wrote:
Delaney, Timothy (Tim) wrote:
However, *because* Python uses duck typing, I tend to feel that subclasses in Python *should* be drop-in replacements.
Duck-typing means that the only reliable way to assess whether two types are sufficiently compatible for some purpose is to consult the documentation -- you can't just look at the base class list.
What's the API for that ? I've had problems in code that needs to treat strings, lists and dictionaries differently (assigning values to a container where all three need different handling) and telling the difference but allowing duck typing is *problematic*. Slightly-off-topic'ly-yours, Michael Foord
![](https://secure.gravatar.com/avatar/047f2332cde3730f1ed661eebb0c5686.jpg?s=120&d=mm&r=g)
On 2/21/06, Fuzzyman <fuzzyman@voidspace.org.uk> wrote:
I've had problems in code that needs to treat strings, lists and dictionaries differently (assigning values to a container where all three need different handling) and telling the difference but allowing duck typing is *problematic*.
Consider designing APIs that don't require you to mae that kind of distinction, if you're worried about edge cases and classifying arbitrary other objects correctly. It's totally possible to create an object that behaves like a hybrid of a string and a dict. If you're only interested in classifying the three specific built-ins you mention, I'd check for the presense of certain attributes: hasattr(x, "lower") -> x is a string of some kind; hasattr(x, "sort") -> x is a list; hasattr(x, "update") -> x is a dict. Also, hasattr(x, "union") -> x is a set; hasattr(x, "readline") -> x is a file. That's duck typing! -- --Guido van Rossum (home page: http://www.python.org/~guido/)
![](https://secure.gravatar.com/avatar/463a381eaf9c0c08bc130a1bea1874ee.jpg?s=120&d=mm&r=g)
Guido van Rossum wrote:
On 2/21/06, Fuzzyman <fuzzyman@voidspace.org.uk> wrote:
I've had problems in code that needs to treat strings, lists and dictionaries differently (assigning values to a container where all three need different handling) and telling the difference but allowing duck typing is *problematic*.
Consider designing APIs that don't require you to mae that kind of distinction, if you're worried about edge cases and classifying arbitrary other objects correctly. It's totally possible to create an object that behaves like a hybrid of a string and a dict.
Understood.
If you're only interested in classifying the three specific built-ins you mention, I'd check for the presense of certain attributes: hasattr(x, "lower") -> x is a string of some kind; hasattr(x, "sort") -> x is a list; hasattr(x, "update") -> x is a dict. Also, hasattr(x, "union") -> x is a set; hasattr(x, "readline") -> x is a file.
That's duck typing!
Sure, but that requires a "dictionary like object" to define an update method, and a "list like object" to define a sort method. The mapping and sequence protocols are so loosely defined that some arbitrary decision like this has to be made. (Any object that defines "__getitem__" could follow either or both and duck typing doesn't help you unless you're prepared to make an additional requirement that is outside the loose requirements of the protocol.) I can't remember how we solved it, but I think we decided that an object would be treated as a string if it passed isinstance, and a dictionary or sequence if it has _getitem__ (but isn't a string instance or subclass). If it has update as well as __getitem__ it is a "dictionary-alike". All the best, Michael Foord
-- --Guido van Rossum (home page: http://www.python.org/~guido/)
![](https://secure.gravatar.com/avatar/0b220fa4c0b59e883f360979ee745d63.jpg?s=120&d=mm&r=g)
On Tue, 21 Feb 2006, Guido van Rossum wrote: [...]
If you're only interested in classifying the three specific built-ins you mention, I'd check for the presense of certain attributes: hasattr(x, "lower") -> x is a string of some kind; hasattr(x, "sort") -> x is a list; hasattr(x, "update") -> x is a dict. Also, hasattr(x, "union") -> x is a set; hasattr(x, "readline") -> x is a file.
dict and set instances both have an .update() method. I guess "keys" or "items" is a better choice for testing dict-ness, if using "LBYL" at all. (anybody new to "LBYL" can google for that and EAFP -- latter does not stand for European Assoc. of Fish Pathologists in this context, though ;-)
That's duck typing!
hasattr(python, "quack") True
John
![](https://secure.gravatar.com/avatar/72ee673975357d43d79069ac1cd6abda.jpg?s=120&d=mm&r=g)
Fuzzyman wrote:
I've had problems in code that needs to treat strings, lists and dictionaries differently (assigning values to a container where all three need different handling) and telling the difference but allowing duck typing is *problematic*.
You need to rethink your design so that you don't have to make that kind of distinction. -- Greg Ewing, Computer Science Dept, +--------------------------------------+ University of Canterbury, | Carpe post meridiam! | Christchurch, New Zealand | (I'm not a morning person.) | greg.ewing@canterbury.ac.nz +--------------------------------------+
![](https://secure.gravatar.com/avatar/463a381eaf9c0c08bc130a1bea1874ee.jpg?s=120&d=mm&r=g)
Greg Ewing wrote:
Fuzzyman wrote:
I've had problems in code that needs to treat strings, lists and dictionaries differently (assigning values to a container where all three need different handling) and telling the difference but allowing duck typing is *problematic*.
You need to rethink your design so that you don't have to make that kind of distinction.
Well... to *briefly* explain the use case, it's for value assignment in ConfigObj. It basically accepts as valid values strings and lists of strings [#]_. You can also create new subsections by assigning a dictionary. It needs to be able to recognise lists in order to check each list member is a string. (See note below, it still needs to be able to recognise lists when writing, even if it is not doing type checking on assignment.) It needs to be able to recognise dictionaries in order to create a new section instance (rather than directly assigning the dictionary). This is *terribly* convenient for the user (trivial example of creating a new config file programatically) : from configobj import ConfigObj cfg = ConfigObj(newfilename) cfg['key'] = 'value' cfg['key2'] = ['value1', 'value2', 'value3'] cfg['section'] = {'key': 'value', 'key2': ['value1', 'value2', 'value3']} cfg.write() Writes out : key = value key2 = value1, value2, value3 [section] key = value key2 = value1, value2, value3 (Note none of those values needed quoting, so they aren't.) Obviously I could force the creation of sections and the assignment of list values to use separate methods, but it's much less readable and unnecessary. The code as is works and has a nice API. It still needs to be able to tell what *type* of value is being assigned. Mapping and sequence protocols are so loosely defined that in order to support 'list like objects' and 'dictionary like objects' some arbitrary decision about what methods they should support has to be made. (For example a read only mapping container is unlikely to implement __setitem__ or methods like update). At first we defined a mapping object as one that defines __getitem__ and keys (not update as I previously said), and list like objects as ones that define __getitem__ and *not* keys. For strings we required a basestring subclass. In the end I think we ripped this out and just settled on isinstance tests. All the best, Michael Foord .. [#] Although it has two modes. In the 'default' mode you can assign any object as a value and a string representation is written out. A more strict mode checks values at the point you assign them - so errors will be raised at that point rather than propagating into the config file. When writing you still need to able to recognise lists because each element is properly quoted.
![](https://secure.gravatar.com/avatar/72ee673975357d43d79069ac1cd6abda.jpg?s=120&d=mm&r=g)
Fuzzyman wrote:
cfg = ConfigObj(newfilename) cfg['key'] = 'value' cfg['key2'] = ['value1', 'value2', 'value3'] cfg['section'] = {'key': 'value', 'key2': ['value1', 'value2', 'value3']}
If the main purpose is to support this kind of notational convenience, then I'd be inclined to require all the values used with this API to be concrete strings, lists or dicts. If you're going to make types part of the API, I think it's better to do so with a firm hand rather than being half- hearted and wishy-washy about it. Then, if it's really necessary to support a wider variety of types, provide an alternative API that separates the different cases and isn't type-dependent at all. If someone has a need for this API, using it isn't going to be much of an inconvenience, since he won't be able to write out constructors for his types using notation as compact as the above anyway. -- Greg
![](https://secure.gravatar.com/avatar/463a381eaf9c0c08bc130a1bea1874ee.jpg?s=120&d=mm&r=g)
Greg Ewing wrote:
Fuzzyman wrote:
cfg = ConfigObj(newfilename) cfg['key'] = 'value' cfg['key2'] = ['value1', 'value2', 'value3'] cfg['section'] = {'key': 'value', 'key2': ['value1', 'value2', 'value3']}
If the main purpose is to support this kind of notational convenience, then I'd be inclined to require all the values used with this API to be concrete strings, lists or dicts. If you're going to make types part of the API, I think it's better to do so with a firm hand rather than being half- hearted and wishy-washy about it. [snip..]
Thanks, that's the solution we settled on. We use ``isinstance`` tests to determine types. The user can always do something like : cfg['section'] = dict(dict_like_object) Which isn't so horrible. All the best, Michael
-- Greg _______________________________________________ Python-Dev mailing list Python-Dev@python.org http://mail.python.org/mailman/listinfo/python-dev Unsubscribe: http://mail.python.org/mailman/options/python-dev/fuzzyman%40voidspace.org.u...
participants (6)
-
"Martin v. Löwis"
-
Delaney, Timothy (Tim)
-
Fuzzyman
-
Greg Ewing
-
Guido van Rossum
-
John J Lee